From fc9ad0abe6d9345875a6791a4fcf681fe4143b00 Mon Sep 17 00:00:00 2001 From: Banderos14 Date: Fri, 10 Apr 2026 13:10:03 +0200 Subject: [PATCH 1/3] feat: finaly_task_solution --- README.md | 14 +-- src/scripts/main.js | 247 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 253 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cca9c9f4a..8296bfce8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ 1. Replace `` with your GitHub username in the link - - [DEMO LINK](https://.github.io/js_employees_table_DOM/) + - [DEMO LINK](https://Banderos14.github.io/js_employees_table_DOM/) 2. Follow [this instructions](https://mate-academy.github.io/layout_task-guideline/) - Run `npm run test` command to test your code; - Run `npm run test:only -- -n` to run fast test ignoring linter; @@ -44,11 +44,11 @@ Start table: ``` - Add qa attributes for each input field: ``` - data-qa="name" - data-qa="position" - data-qa="office" - data-qa="age" - data-qa="salary" + data-qa="name" + data-qa="position" + data-qa="office" + data-qa="age" + data-qa="salary" ``` - Select should have 6 options: `Tokyo`, `Singapore`, `London`, `New York`, `Edinburgh`, `San Francisco`. - Use texts for labels and buttons from the screenshot below. @@ -64,7 +64,7 @@ Start table: - Notification titles and descriptions are up to you. - Add qa attribute for notification: `data-qa="notification"` and class `error`/`success` depending on the result. -##### Implement editing of table cells by double-clicking on them (optional). +##### Implement editing of table cells by double-clicking on them (optional). - Double click on the cell of the table, which should remove the text, and append input with `cell-input` class. - The input value should be replaced by the input text. - Only one cell can be edited at a time. diff --git a/src/scripts/main.js b/src/scripts/main.js index a765fdb1d..9e778f2c8 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -1,3 +1,248 @@ 'use strict'; -// write code here +const thead = document.querySelector('thead'); +const tbody = document.querySelector('tbody'); + +// -1 потому что мы изначально не делаем никакое действие сортировки +let currentColumn = -1; +// asc - ascending +let currentDirection = 'asc'; + +thead.addEventListener('click', (e) => { + if (e.target.tagName !== 'TH') { + return; + } + + const rows = Array.from(tbody.querySelectorAll('tr')); + const index = Array.from(e.target.parentNode.children).indexOf(e.target); + + if (currentColumn === index) { + currentDirection = currentDirection === 'asc' ? 'desc' : 'asc'; + } else { + currentColumn = index; + currentDirection = 'asc'; + } + + rows.sort((a, b) => { + let valueA = a.children[index].textContent.trim(); + let valueB = b.children[index].textContent.trim(); + + if (index === 3 || index === 4) { + valueA = Number(valueA.replace(/[$,]/g, '')); + valueB = Number(valueB.replace(/[$,]/g, '')); + } else { + valueA = valueA.toLowerCase(); + valueB = valueB.toLowerCase(); + } + + if (valueA < valueB) { + return currentDirection === 'asc' ? -1 : 1; + } + + if (valueA > valueB) { + return currentDirection === 'asc' ? 1 : -1; + } + + return 0; + }); + + tbody.append(...rows); +}); + +tbody.addEventListener('click', (e) => { + const row = e.target.closest('tr'); + + if (!row) { + return; + } + + const activeRow = tbody.querySelector('.active'); + + if (activeRow) { + activeRow.classList.remove('active'); + } + + row.classList.add('active'); +}); + +// Новое поле +const form = document.createElement('form'); + +form.classList.add('new-employee-form'); + +// Name +const nameLabel = document.createElement('label'); + +nameLabel.textContent = 'Name: '; + +const nameInput = document.createElement('input'); + +nameInput.name = 'name'; +nameInput.type = 'text'; +nameInput.dataset.qa = 'name'; + +nameLabel.append(nameInput); +form.append(nameLabel); + +// Position +const positionLabel = document.createElement('label'); + +positionLabel.textContent = 'Position: '; + +const positionInput = document.createElement('input'); + +positionInput.name = 'position'; +positionInput.type = 'text'; +positionInput.dataset.qa = 'position'; + +positionLabel.append(positionInput); +form.append(positionLabel); + +// Office +const officeLabel = document.createElement('label'); + +officeLabel.textContent = 'Office: '; + +const officeSelect = document.createElement('select'); + +officeSelect.name = 'office'; +officeSelect.dataset.qa = 'office'; + +const offices = [ + 'Tokyo', + 'Singapore', + 'London', + 'New York', + 'Edinburgh', + 'San Francisco', +]; + +offices.forEach((office) => { + const option = document.createElement('option'); + + option.value = office; + option.textContent = office; + + officeSelect.append(option); +}); + +officeLabel.append(officeSelect); +form.append(officeLabel); + +// Age +const ageLabel = document.createElement('label'); + +ageLabel.textContent = 'Age: '; + +const ageInput = document.createElement('input'); + +ageInput.name = 'age'; +ageInput.type = 'number'; +ageInput.dataset.qa = 'age'; + +ageLabel.append(ageInput); +form.append(ageLabel); + +// Salary +const salaryLabel = document.createElement('label'); + +salaryLabel.textContent = 'Salary: '; + +const salaryInput = document.createElement('input'); + +salaryInput.name = 'salary'; +salaryInput.type = 'number'; +salaryInput.dataset.qa = 'salary'; + +salaryLabel.append(salaryInput); +form.append(salaryLabel); + +const submitButton = document.createElement('button'); + +submitButton.type = 'submit'; +submitButton.textContent = 'Save to table'; + +form.append(submitButton); + +form.addEventListener('submit', (e) => { + e.preventDefault(); + + const nameEmployer = nameInput.value.trim(); + const position = positionInput.value.trim(); + const office = officeSelect.value; + const age = ageInput.value.trim(); + const salary = salaryInput.value.trim(); + + if (nameEmployer.length < 4) { + showNotification('Name is too short', 'error'); + + return; + } + + if (!position) { + showNotification('Position is required', 'error'); + + return; + } + + if (age < 18 || age > 90) { + showNotification('Age is invalid', 'error'); + + return; + } + + if (!salary) { + showNotification('Salary is required', 'error'); + + return; + } + + const tr = document.createElement('tr'); + + const tdName = document.createElement('td'); + + tdName.textContent = nameEmployer; + + const tdPosition = document.createElement('td'); + + tdPosition.textContent = position; + + const tdOffice = document.createElement('td'); + + tdOffice.textContent = office; + + const tdAge = document.createElement('td'); + + tdAge.textContent = age; + + const tdSalary = document.createElement('td'); + + tdSalary.textContent = `$${Number(salary).toLocaleString()}`; + + tr.append(tdName, tdPosition, tdOffice, tdAge, tdSalary); + + tbody.append(tr); + + form.reset(); + + // Notification + function showNotification(message, type) { + const old = document.querySelector('[data-qa="notification"]'); + + if (old) { + old.remove(); + } + + const notification = document.createElement('div'); + + notification.dataset.qa = 'notification'; + notification.classList.add('notification'); + notification.classList.add(type); + notification.textContent = message; + + document.body.append(notification); + } + + showNotification('Employee added', 'success'); +}); +document.body.append(form); From 9d88deedad732fe7054fe48f6052cb2c10becaa0 Mon Sep 17 00:00:00 2001 From: Banderos14 Date: Fri, 10 Apr 2026 13:25:00 +0200 Subject: [PATCH 2/3] fix: fix requirements --- src/scripts/main.js | 47 +++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/scripts/main.js b/src/scripts/main.js index 9e778f2c8..803cdeec5 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -65,6 +65,24 @@ tbody.addEventListener('click', (e) => { row.classList.add('active'); }); +// Notification +function showNotification(message, type) { + const old = document.querySelector('[data-qa="notification"]'); + + if (old) { + old.remove(); + } + + const notification = document.createElement('div'); + + notification.dataset.qa = 'notification'; + notification.classList.add('notification'); + notification.classList.add(type); + notification.textContent = message; + + document.body.append(notification); +} + // Новое поле const form = document.createElement('form'); @@ -170,7 +188,7 @@ form.addEventListener('submit', (e) => { const nameEmployer = nameInput.value.trim(); const position = positionInput.value.trim(); const office = officeSelect.value; - const age = ageInput.value.trim(); + const age = Number(ageInput.value); const salary = salaryInput.value.trim(); if (nameEmployer.length < 4) { @@ -185,6 +203,12 @@ form.addEventListener('submit', (e) => { return; } + if (!office) { + showNotification('Office is required', 'error'); + + return; + } + if (age < 18 || age > 90) { showNotification('Age is invalid', 'error'); @@ -220,29 +244,10 @@ form.addEventListener('submit', (e) => { tdSalary.textContent = `$${Number(salary).toLocaleString()}`; tr.append(tdName, tdPosition, tdOffice, tdAge, tdSalary); - tbody.append(tr); form.reset(); - - // Notification - function showNotification(message, type) { - const old = document.querySelector('[data-qa="notification"]'); - - if (old) { - old.remove(); - } - - const notification = document.createElement('div'); - - notification.dataset.qa = 'notification'; - notification.classList.add('notification'); - notification.classList.add(type); - notification.textContent = message; - - document.body.append(notification); - } - showNotification('Employee added', 'success'); }); + document.body.append(form); From 41e930b969bb3bb41673bc58d88dd44e518952a4 Mon Sep 17 00:00:00 2001 From: Banderos14 Date: Fri, 10 Apr 2026 13:34:37 +0200 Subject: [PATCH 3/3] fix: added logic for cell-input and chaged new requirements --- src/scripts/main.js | 55 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/scripts/main.js b/src/scripts/main.js index 803cdeec5..c79331c2a 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -126,6 +126,13 @@ const officeSelect = document.createElement('select'); officeSelect.name = 'office'; officeSelect.dataset.qa = 'office'; +const defaultOfficeOption = document.createElement('option'); + +defaultOfficeOption.value = ''; +defaultOfficeOption.textContent = 'Select office'; + +officeSelect.append(defaultOfficeOption); + const offices = [ 'Tokyo', 'Singapore', @@ -251,3 +258,51 @@ form.addEventListener('submit', (e) => { }); document.body.append(form); + +// Редактирование ячеек +let editingCell = null; + +tbody.addEventListener('dblclick', (e) => { + const cell = e.target.closest('td'); + + if (!cell) { + return; + } + + if (cell.querySelector('input')) { + return; + } + + if (editingCell) { + return; + } + + editingCell = cell; + + const initialValue = cell.textContent.trim(); + const input = document.createElement('input'); + + input.classList.add('cell-input'); + input.value = initialValue; + + cell.textContent = ''; + cell.append(input); + input.focus(); + + function saveCellValue() { + const newValue = input.value.trim(); + + cell.textContent = newValue || initialValue; + editingCell = null; + } + + input.addEventListener('blur', () => { + saveCellValue(); + }); + + input.addEventListener('keydown', (eventInput) => { + if (eventInput.key === 'Enter') { + input.blur(); + } + }); +});