From cefbddd3c04369009fff90f23a2de2138e8508f8 Mon Sep 17 00:00:00 2001
From: Stanislav <168761269+Stas11k@users.noreply.github.com>
Date: Sat, 16 May 2026 22:31:39 +0300
Subject: [PATCH 01/10] feat: create basic buy list page structure
---
index.html | 85 +++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 75 insertions(+), 10 deletions(-)
diff --git a/index.html b/index.html
index 5971bf7d..6dc68e63 100644
--- a/index.html
+++ b/index.html
@@ -1,16 +1,81 @@
-
-
+
+
- My Page
-
-
-
+
+ BuyList
+
-
- Apple
- 4
-
+
+
+
+ Кошик покупок
+
+
+
+
+ -
+
Помідори
+
+
+
+
+ -
+
Печиво
+
+
+
+
+
+ -
+
Сир
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
From 4f2df4c2de30c141afafb210f4072ca63b5678ad Mon Sep 17 00:00:00 2001
From: Stanislav <168761269+Stas11k@users.noreply.github.com>
Date: Sat, 16 May 2026 22:40:19 +0300
Subject: [PATCH 02/10] feat: add author badge and tooltip attributes
---
index.html | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/index.html b/index.html
index 6dc68e63..9308b2d2 100644
--- a/index.html
+++ b/index.html
@@ -15,36 +15,37 @@ Кошик покупок
-
Помідори
-
+
-
Печиво
-
-
+
+
-
Сир
-
-
+
+
@@ -77,5 +78,10 @@ Куплено
+
+
\ No newline at end of file
From 0c085f04e8bb1f856408893a6278ea79582093d6 Mon Sep 17 00:00:00 2001
From: Stanislav <168761269+Stas11k@users.noreply.github.com>
Date: Sat, 16 May 2026 22:51:54 +0300
Subject: [PATCH 03/10] feat: add base page layout and product cards styling
---
main.css | 78 ++++++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 62 insertions(+), 16 deletions(-)
diff --git a/main.css b/main.css
index 00bc872e..6d96ff3a 100644
--- a/main.css
+++ b/main.css
@@ -1,17 +1,63 @@
-.product-item {
- background-color: gray;
- display: inline-block;
- height: 25px;
- padding: 5px;
- border-radius: 5px;
-}
-
-.amount {
- background-color: yellow;
- border-radius: 10px;
-
- display: inline-block;
- height: 20px;
- width: 20px;
- text-align: center;
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ min-height: 100vh;
+ font-family: Arial, sans-serif;
+ background-color: #d9d9d9;
+ color: #111;
+}
+
+.page {
+ width: min(100%, 980px);
+ margin: 0 auto;
+ padding: 28px 20px;
+ display: grid;
+ grid-template-columns: minmax(0, 1.45fr) minmax(180px, 0.75fr);
+ gap: 18px;
+ align-items: start;
+}
+
+.products-card, .summary-card {
+ background-color: white;
+ border-radius: 5px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
+}
+
+.products-card {
+ overflow: visible;
+}
+
+.summary-card {
+ overflow: hidden;
+}
+
+.products-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.product {
+ min-height: 72px;
+ padding: 16px;
+ border-top: 1px solid #ececec;
+ display: grid;
+ grid-template-columns: minmax(65px, 1fr) minmax(0, 0.5fr) 118px minmax(0, 0.5fr) 110px 34px;
+ column-gap: 8px;
+ row-gap: 8px;
+ align-items: center;
+ justify-items: stretch;
+}
+
+.product-bought {
+ grid-template-columns: minmax(65px, 1fr) minmax(0, 0.5fr) 118px minmax(0, 0.5fr) 110px 34px;
+}
+
+.product-name {
+ grid-column: 1;
+ margin: 0;
+ font-size: 16px;
}
\ No newline at end of file
From b4594432b429592c5365a5148f2ec36b70a48944 Mon Sep 17 00:00:00 2001
From: Stanislav <168761269+Stas11k@users.noreply.github.com>
Date: Sat, 16 May 2026 23:09:14 +0300
Subject: [PATCH 04/10] feat: add form controls and product action buttons
---
main.css | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 147 insertions(+)
diff --git a/main.css b/main.css
index 6d96ff3a..958b7ce6 100644
--- a/main.css
+++ b/main.css
@@ -34,6 +34,32 @@ body {
overflow: hidden;
}
+.add-form {
+ padding: 14px;
+ display: flex;
+}
+
+.add-form input {
+ flex: 1;
+ min-width: 0;
+ padding: 10px 14px;
+ font-size: 16px;
+ border: 1px solid #d7d7d7;
+ border-radius: 5px 0 0 5px;
+}
+
+.add-form button {
+ padding: 0 24px;
+ border: 1px solid #2a75ad;
+ border-radius: 0 5px 5px 0;
+ background: linear-gradient(#3498db, #2b84c6);
+ color: white;
+ font-size: 16px;
+ font-weight: bold;
+ transform: translateY(-2px);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 3px 0 #21679c, 3px 6px rgba(0, 0, 0, 0.18);
+}
+
.products-list {
margin: 0;
padding: 0;
@@ -60,4 +86,125 @@ body {
grid-column: 1;
margin: 0;
font-size: 16px;
+}
+
+.product-bought .product-name {
+ text-decoration: line-through;
+}
+
+.product-bought .counter {
+ grid-column: 3;
+ justify-self: center;
+}
+
+.editable {
+ width: 100%;
+ max-width: 160px;
+ min-width: 65px;
+ padding: 7px 10px;
+ border: 1px solid #58aef7;
+ border-radius: 4px;
+ background-color: white;
+ box-shadow: 0 0 0 2px rgba(88, 174, 247, 0.18), 0 0 8px rgba(88, 174, 247, 0.45);
+}
+
+.controls {
+ grid-column: 3;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ display: grid;
+ grid-template-columns: 34px 30px 34px;
+ gap: 10px;
+ justify-content: center;
+ align-items: center;
+}
+
+.counter {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 30px;
+ height: 30px;
+ background-color: #e8e8e8;
+ border-radius: 5px;
+ font-size: 15px;
+ font-weight: bold;
+}
+
+button {
+ position: relative;
+ min-height: 34px;
+ cursor: pointer;
+ border: 1px solid #d7d7d7;
+ border-radius: 5px;
+ font-size: 14px;
+ background-color: #f5f5f5;
+ appearance: none;
+ box-shadow: none;
+}
+
+.product>button:not(.delete), .product.product-bought>button:not(.delete) {
+ background: linear-gradient(#ffffff, #e7e7e7);
+ border: 1px solid #d4d4d4;
+ color: #555;
+ font-weight: bold;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 3px 0 #c4c4c4, 0 3px 6px rgba(0, 0, 0, 0.14);
+}
+
+.product>button:not(.delete) {
+ grid-column: 5;
+ justify-self: end;
+ width: auto;
+ min-width: 120px;
+ padding: 0 12px;
+ height: 40px;
+ white-space: nowrap;
+}
+
+.product.product-bought>button:not(.delete) {
+ grid-column: 5 / 7;
+ justify-self: end;
+ width: auto;
+ min-width: 120px;
+ padding: 0 12px;
+ height: 40px;
+ white-space: nowrap;
+}
+
+.product .delete {
+ grid-column: 6;
+}
+
+.minus, .plus, .delete {
+ width: 34px;
+ height: 34px;
+ border: none;
+ border-radius: 50%;
+ color: white;
+ font-size: 20px;
+ font-weight: bold;
+}
+
+.minus {
+ background: linear-gradient(#ef3434, #d71920);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 3px 0 #a9161b, 0 3px 6px rgba(0, 0, 0, 0.18);
+}
+
+.minus.disabled {
+ background: linear-gradient(#f7b0b0, #ef9999);
+ box-shadow: none;
+ opacity: 0.75;
+ cursor: not-allowed;
+}
+
+.plus {
+ background: linear-gradient(#25c858, #18a83f);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 3px 0 #128332, 0 3px 6px rgba(0, 0, 0, 0.18);
+}
+
+.delete {
+ border-radius: 5px;
+ background: linear-gradient(#ef3434, #d71920);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 3px 0 #a9161b, 0 3px 6px rgba(0, 0, 0, 0.18);
}
\ No newline at end of file
From 13433424b195f389c7440c89581dd083ddee5c3b Mon Sep 17 00:00:00 2001
From: Stanislav <168761269+Stas11k@users.noreply.github.com>
Date: Sat, 16 May 2026 23:19:45 +0300
Subject: [PATCH 05/10] feat: add summary section and purchased items styles
---
main.css | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/main.css b/main.css
index 958b7ce6..fa1c5fbd 100644
--- a/main.css
+++ b/main.css
@@ -207,4 +207,70 @@ button {
border-radius: 5px;
background: linear-gradient(#ef3434, #d71920);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 3px 0 #a9161b, 0 3px 6px rgba(0, 0, 0, 0.18);
+}
+
+.summary-section {
+ padding: 0;
+ border-bottom: 1px solid #e5e5e5;
+}
+
+.summary-section:last-child {
+ border-bottom: none;
+}
+
+.summary-section h2 {
+ margin: 0;
+ padding: 20px 16px 22px;
+ border-bottom: 1px solid #e5e5e5;
+ font-size: 24px;
+}
+
+.summary-list {
+ margin: 0;
+ padding: 20px 16px 22px;
+ list-style: none;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ align-items: center;
+}
+
+.summary-list li {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 12px;
+ background-color: #e8e8e8;
+ border-radius: 6px;
+ font-size: 14px;
+}
+
+.amount {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-width: 22px;
+ height: 22px;
+ padding: 0 7px;
+ background-color: #ff6f1a;
+ color: white;
+ border-radius: 50%;
+ font-size: 13px;
+ font-weight: bold;
+ position: relative;
+}
+
+.bought-label strong {
+ text-decoration: line-through;
+}
+
+.bought-label .amount::before {
+ content: "";
+ position: absolute;
+ left: 4px;
+ right: 4px;
+ top: 50%;
+ height: 2px;
+ background-color: white;
+ transform: translateY(-50%);
}
\ No newline at end of file
From 56d76885b2297a658fc504c7d40acefedfa65153 Mon Sep 17 00:00:00 2001
From: Stanislav <168761269+Stas11k@users.noreply.github.com>
Date: Sat, 16 May 2026 23:34:36 +0300
Subject: [PATCH 06/10] feat: add tooltips, author badge and responsive styles
---
main.css | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 143 insertions(+), 9 deletions(-)
diff --git a/main.css b/main.css
index fa1c5fbd..150a3ea9 100644
--- a/main.css
+++ b/main.css
@@ -57,7 +57,15 @@ body {
font-size: 16px;
font-weight: bold;
transform: translateY(-2px);
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 3px 0 #21679c, 3px 6px rgba(0, 0, 0, 0.18);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 3px 0 #21679c, 0 3px 6px rgba(0, 0, 0, 0.18);
+}
+
+.product>button:not(.delete), .product.product-bought>button:not(.delete) {
+ background: linear-gradient(#ffffff, #e7e7e7);
+ border: 1px solid #d4d4d4;
+ color: #555;
+ font-weight: bold;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 3px 0 #c4c4c4, 0 3px 6px rgba(0, 0, 0, 0.14);
}
.products-list {
@@ -144,14 +152,6 @@ button {
box-shadow: none;
}
-.product>button:not(.delete), .product.product-bought>button:not(.delete) {
- background: linear-gradient(#ffffff, #e7e7e7);
- border: 1px solid #d4d4d4;
- color: #555;
- font-weight: bold;
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 3px 0 #c4c4c4, 0 3px 6px rgba(0, 0, 0, 0.14);
-}
-
.product>button:not(.delete) {
grid-column: 5;
justify-self: end;
@@ -209,6 +209,10 @@ button {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 3px 0 #a9161b, 0 3px 6px rgba(0, 0, 0, 0.18);
}
+.summary-card {
+ overflow: hidden;
+}
+
.summary-section {
padding: 0;
border-bottom: 1px solid #e5e5e5;
@@ -273,4 +277,134 @@ button {
height: 2px;
background-color: white;
transform: translateY(-50%);
+}
+
+button[data-tooltip]::after {
+ content: attr(data-tooltip);
+ position: absolute;
+ left: 50%;
+ bottom: calc(100% + 8px);
+ transform: translateX(-50%) translateY(10px) scale(0.85);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-width: 130px;
+ min-height: 32px;
+ padding: 4px 12px;
+ background-color: #4b148c;
+ color: white;
+ border-radius: 12px;
+ text-align: center;
+ font-size: 11px;
+ line-height: 1.1;
+ white-space: normal;
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s ease;
+ z-index: 20;
+}
+
+button[data-tooltip]:hover::after {
+ opacity: 1;
+ visibility: visible;
+ font-weight: bold;
+ transform: translateX(-50%) translateY(0) scale(1);
+}
+
+.author-badge {
+ position: fixed;
+ left: 18px;
+ bottom: 0;
+ width: 155px;
+ height: 92px;
+ padding: 12px 10px;
+ text-align: center;
+ background-color: #4b148c;
+ color: white;
+ border-radius: 12px 12px 0 0;
+ transform: translateY(42px);
+ transition: 0.35s ease;
+ z-index: 20;
+}
+
+.author-badge:hover {
+ transform: translateY(0);
+ background-color: #6d20c7;
+}
+
+.badge-title, .badge-author {
+ margin: 0;
+ font-weight: bold;
+}
+
+.badge-title {
+ font-size: 26px;
+ margin-bottom: 12px;
+}
+
+.badge-author {
+ font-size: 14px;
+ line-height: 1.2;
+}
+
+.visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+ clip-path: inset(50%);
+ white-space: nowrap;
+}
+
+@media (max-width: 650px) {
+
+ .page {
+ grid-template-columns: 1fr;
+ padding: 16px;
+ }
+
+ .add-form {
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .add-form input, .add-form button {
+ width: 100%;
+ border-radius: 5px;
+ }
+
+ .product, .product-bought {
+ grid-template-columns: minmax(65px, 1fr) minmax(0, 0.5fr) 118px minmax(0, 0.5fr) 110px 34px;
+ }
+
+ .controls {
+ justify-content: center;
+ }
+}
+
+@media print {
+
+ body {
+ background-color: white;
+ }
+
+ .author-badge {
+ background-color: white;
+ color: #4b148c;
+ border: 2px solid #4b148c;
+ transform: none;
+ }
+
+ .badge-title {
+ display: none;
+ }
+
+ .add-form button, .minus, .plus, .delete, .product button {
+ box-shadow: none !important;
+ }
+
+ .add-form button {
+ transform: none;
+ }
}
\ No newline at end of file
From b792d6e9851ff7d7e5bbcca7336a101946617f27 Mon Sep 17 00:00:00 2001
From: Stanislav <168761269+Stas11k@users.noreply.github.com>
Date: Thu, 21 May 2026 23:52:34 +0300
Subject: [PATCH 07/10] style: refine add form button styling
---
main.css | 27 ++++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/main.css b/main.css
index 150a3ea9..25a3f765 100644
--- a/main.css
+++ b/main.css
@@ -49,15 +49,16 @@ body {
}
.add-form button {
- padding: 0 24px;
- border: 1px solid #2a75ad;
+ padding: 0 40px;
+ border: none;
border-radius: 0 5px 5px 0;
background: linear-gradient(#3498db, #2b84c6);
color: white;
font-size: 16px;
font-weight: bold;
- transform: translateY(-2px);
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 3px 0 #21679c, 0 3px 6px rgba(0, 0, 0, 0.18);
+ min-height: 46px;
+ transform: none;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 2px 0 #21679c, 0 2px 4px rgba(0, 0, 0, 0.16);
}
.product>button:not(.delete), .product.product-bought>button:not(.delete) {
@@ -116,6 +117,12 @@ body {
box-shadow: 0 0 0 2px rgba(88, 174, 247, 0.18), 0 0 8px rgba(88, 174, 247, 0.45);
}
+.editable:focus {
+ outline: none;
+ border-color: #58aef7;
+ box-shadow: 0 0 0 2px rgba(88, 174, 247, 0.18), 0 0 8px rgba(88, 174, 247, 0.45);
+}
+
.controls {
grid-column: 3;
margin: 0;
@@ -138,6 +145,7 @@ body {
border-radius: 5px;
font-size: 15px;
font-weight: bold;
+ color: #555;
}
button {
@@ -239,6 +247,11 @@ button {
align-items: center;
}
+.summary-list strong {
+ color: #555;
+ font-weight: bold;
+}
+
.summary-list li {
display: flex;
align-items: center;
@@ -271,10 +284,10 @@ button {
.bought-label .amount::before {
content: "";
position: absolute;
- left: 4px;
- right: 4px;
+ left: 7px;
+ right: 7px;
top: 50%;
- height: 2px;
+ height: 1.5px;
background-color: white;
transform: translateY(-50%);
}
From 9518c6ff9c05b97134cd5a53cafe293110385539 Mon Sep 17 00:00:00 2001
From: Stanislav <168761269+Stas11k@users.noreply.github.com>
Date: Thu, 21 May 2026 23:58:11 +0300
Subject: [PATCH 08/10] feat: add product creation and rendering
---
index.html | 1 +
main.js | 42 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+)
create mode 100644 main.js
diff --git a/index.html b/index.html
index 9308b2d2..cda58e61 100644
--- a/index.html
+++ b/index.html
@@ -5,6 +5,7 @@
BuyList
+
diff --git a/main.js b/main.js
new file mode 100644
index 00000000..f71db078
--- /dev/null
+++ b/main.js
@@ -0,0 +1,42 @@
+const form = document.querySelector(".add-form");
+const input = document.querySelector("#product-name");
+const productsList = document.querySelector(".products-list");
+
+let products = [];
+
+render();
+
+form.addEventListener("submit", (event) => {
+ event.preventDefault();
+ const name = input.value.trim();
+ if (!name) {
+ input.focus();
+ return;
+ }
+ products.push({
+ id: Date.now(),
+ name: name,
+ count: 1,
+ bought: false
+ });
+ input.value = "";
+ input.focus();
+ render();
+});
+
+function render() {
+ productsList.textContent = "";
+ products.forEach(product => {
+ productsList.append(createProductElement(product));
+ });
+}
+
+function createProductElement(product) {
+ const li = document.createElement("li");
+ li.classList.add("product");
+ const name = document.createElement("p");
+ name.classList.add("product-name");
+ name.textContent = product.name;
+ li.append(name);
+ return li;
+}
\ No newline at end of file
From b480a68a955d3fc99d5c2eab8a94a1e87589146c Mon Sep 17 00:00:00 2001
From: Stanislav <168761269+Stas11k@users.noreply.github.com>
Date: Sat, 23 May 2026 19:21:58 +0300
Subject: [PATCH 09/10] feat: add product controls and summary section
---
index.html | 6 +--
main.js | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 108 insertions(+), 4 deletions(-)
diff --git a/index.html b/index.html
index cda58e61..4d25bee8 100644
--- a/index.html
+++ b/index.html
@@ -23,7 +23,7 @@ Кошик покупок
Помідори
-
+
@@ -33,7 +33,7 @@ Кошик покупок
-
+
@@ -45,7 +45,7 @@ Кошик покупок
-
+
diff --git a/main.js b/main.js
index f71db078..cd17295d 100644
--- a/main.js
+++ b/main.js
@@ -1,6 +1,7 @@
const form = document.querySelector(".add-form");
const input = document.querySelector("#product-name");
const productsList = document.querySelector(".products-list");
+const summarySections = document.querySelectorAll(".summary-section");
let products = [];
@@ -24,19 +25,122 @@ form.addEventListener("submit", (event) => {
render();
});
+productsList.addEventListener("click", (event) => {
+ const button = event.target.closest("button");
+ if (!button) return;
+ const productElement = button.closest(".product");
+ const id = Number(productElement.dataset.id);
+ const product = products.find(item => item.id === id);
+
+ if (!product) return;
+ if (button.classList.contains("delete")) products = products.filter(item => item.id !== id);
+ if (button.classList.contains("plus")) product.count++;
+ if (button.classList.contains("minus") && product.count > 1) product.count--;
+ if (button.dataset.action === "buy") product.bought = true;
+ if (button.dataset.action === "unbuy") product.bought = false;
+
+ render();
+});
+
function render() {
productsList.textContent = "";
products.forEach(product => {
productsList.append(createProductElement(product));
});
+ renderSummary();
}
function createProductElement(product) {
const li = document.createElement("li");
li.classList.add("product");
+ li.dataset.id = product.id;
+ if (product.bought) {
+ li.classList.add("product-bought");
+ }
const name = document.createElement("p");
name.classList.add("product-name");
name.textContent = product.name;
- li.append(name);
+
+ const counter = document.createElement("output");
+ counter.classList.add("counter");
+ counter.textContent = product.count;
+
+ if (product.bought) {
+ const unbuyButton = document.createElement("button");
+ unbuyButton.type = "button";
+ unbuyButton.textContent = "Не куплено";
+ unbuyButton.dataset.action = "unbuy";
+ li.append(name, counter, unbuyButton);
+ return li;
+ }
+
+ const controls = document.createElement("menu");
+ controls.classList.add("controls");
+
+ const minusLi = document.createElement("li");
+ const minusButton = document.createElement("button");
+
+ minusButton.type = "button";
+ minusButton.textContent = "-";
+ minusButton.classList.add("minus");
+
+ if (product.count === 1) {
+ minusButton.disabled = true;
+ minusButton.classList.add("disabled");
+ }
+
+ minusLi.append(minusButton);
+
+ const counterLi = document.createElement("li");
+ counterLi.append(counter);
+
+ const plusLi = document.createElement("li");
+ const plusButton = document.createElement("button");
+
+ plusButton.type = "button";
+ plusButton.textContent = "+";
+ plusButton.classList.add("plus");
+
+ plusLi.append(plusButton);
+ controls.append(minusLi, counterLi, plusLi);
+
+ const buyButton = document.createElement("button");
+ buyButton.type = "button";
+ buyButton.textContent = "Куплено";
+ buyButton.dataset.action = "buy";
+
+ const deleteButton = document.createElement("button");
+ deleteButton.type = "button";
+ deleteButton.textContent = "×";
+ deleteButton.classList.add("delete");
+
+ li.append(name, controls, buyButton, deleteButton);
+ return li;
+}
+
+function renderSummary() {
+ const notBoughtList = summarySections[0].querySelector(".summary-list");
+ const boughtList = summarySections[1].querySelector(".summary-list");
+ notBoughtList.textContent = "";
+ boughtList.textContent = "";
+ products.forEach(product => {
+ const item = createSummaryItem(product);
+ if (product.bought) {
+ item.classList.add("bought-label");
+ boughtList.append(item);
+ } else {
+ notBoughtList.append(item);
+ }
+ });
+}
+
+function createSummaryItem(product) {
+ const li = document.createElement("li");
+ const name = document.createElement("strong");
+ name.textContent = product.name;
+ const amount = document.createElement("output");
+ amount.classList.add("amount");
+ amount.textContent = product.count;
+ li.append(name, amount);
return li;
}
\ No newline at end of file
From 6cca7c7a134d295594951176fa8e4eb8e3f039a7 Mon Sep 17 00:00:00 2001
From: Stanislav <168761269+Stas11k@users.noreply.github.com>
Date: Sat, 23 May 2026 22:27:05 +0300
Subject: [PATCH 10/10] feat: add product editing and local storage support
---
main.js | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 80 insertions(+), 8 deletions(-)
diff --git a/main.js b/main.js
index cd17295d..16d8fbfd 100644
--- a/main.js
+++ b/main.js
@@ -3,7 +3,8 @@ const input = document.querySelector("#product-name");
const productsList = document.querySelector(".products-list");
const summarySections = document.querySelectorAll(".summary-section");
-let products = [];
+const STORAGE_KEY = "buy-list-products";
+let products = loadProducts();
render();
@@ -22,12 +23,20 @@ form.addEventListener("submit", (event) => {
});
input.value = "";
input.focus();
- render();
+ saveAndRender();
});
productsList.addEventListener("click", (event) => {
const button = event.target.closest("button");
- if (!button) return;
+ const name = event.target.closest(".product-name");
+ if (name) {
+ startEditingName(name);
+ return;
+ }
+ if (!button) {
+ return;
+ }
+
const productElement = button.closest(".product");
const id = Number(productElement.dataset.id);
const product = products.find(item => item.id === id);
@@ -39,7 +48,7 @@ productsList.addEventListener("click", (event) => {
if (button.dataset.action === "buy") product.bought = true;
if (button.dataset.action === "unbuy") product.bought = false;
- render();
+ saveAndRender();
});
function render() {
@@ -70,6 +79,7 @@ function createProductElement(product) {
unbuyButton.type = "button";
unbuyButton.textContent = "Не куплено";
unbuyButton.dataset.action = "unbuy";
+ unbuyButton.dataset.tooltip = "Повернути до покупок";
li.append(name, counter, unbuyButton);
return li;
}
@@ -79,14 +89,15 @@ function createProductElement(product) {
const minusLi = document.createElement("li");
const minusButton = document.createElement("button");
-
minusButton.type = "button";
minusButton.textContent = "-";
minusButton.classList.add("minus");
+ minusButton.dataset.tooltip = "Зменшити кількість";
if (product.count === 1) {
- minusButton.disabled = true;
minusButton.classList.add("disabled");
+ minusButton.disabled = true;
+ minusButton.dataset.tooltip = "Мінімальна кількість";
}
minusLi.append(minusButton);
@@ -96,11 +107,10 @@ function createProductElement(product) {
const plusLi = document.createElement("li");
const plusButton = document.createElement("button");
-
plusButton.type = "button";
plusButton.textContent = "+";
plusButton.classList.add("plus");
-
+ plusButton.dataset.tooltip = "Збільшити кількість";
plusLi.append(plusButton);
controls.append(minusLi, counterLi, plusLi);
@@ -108,16 +118,44 @@ function createProductElement(product) {
buyButton.type = "button";
buyButton.textContent = "Куплено";
buyButton.dataset.action = "buy";
+ buyButton.dataset.tooltip = "Позначити як куплене";
const deleteButton = document.createElement("button");
deleteButton.type = "button";
deleteButton.textContent = "×";
deleteButton.classList.add("delete");
+ deleteButton.dataset.tooltip = "Видалити товар";
li.append(name, controls, buyButton, deleteButton);
+
return li;
}
+function startEditingName(nameElement) {
+ const productElement = nameElement.closest(".product");
+ const id = Number(productElement.dataset.id);
+ const product = products.find(item => item.id === id);
+
+ if (!product || product.bought) return;
+
+ const editInput = document.createElement("input");
+ editInput.classList.add("editable");
+ editInput.value = product.name;
+
+ nameElement.replaceWith(editInput);
+ editInput.focus();
+
+ editInput.addEventListener("blur", () => {
+ const newName = editInput.value.trim();
+ if (newName) product.name = newName;
+ saveAndRender();
+ });
+
+ editInput.addEventListener("keydown", (event) => {
+ if (event.key === "Enter") editInput.blur();
+ });
+}
+
function renderSummary() {
const notBoughtList = summarySections[0].querySelector(".summary-list");
const boughtList = summarySections[1].querySelector(".summary-list");
@@ -143,4 +181,38 @@ function createSummaryItem(product) {
amount.textContent = product.count;
li.append(name, amount);
return li;
+}
+
+function saveAndRender() {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(products));
+ render();
+}
+
+function loadProducts() {
+ const savedProducts = localStorage.getItem(STORAGE_KEY);
+
+ if (savedProducts) {
+ return JSON.parse(savedProducts);
+ }
+
+ return [
+ {
+ id: 1,
+ name: "Помідори",
+ count: 2,
+ bought: true
+ },
+ {
+ id: 2,
+ name: "Печиво",
+ count: 2,
+ bought: false
+ },
+ {
+ id: 3,
+ name: "Сир",
+ count: 1,
+ bought: false
+ }
+ ];
}
\ No newline at end of file