Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
const STORAGE_KEY = 'shopping_list_items';
const initialProducts = [
{ id: 1, name: 'Помідори', quantity: 2, isBought: true },
{ id: 2, name: 'Печиво', quantity: 2, isBought: false },
{ id: 3, name: 'Сир', quantity: 1, isBought: false }
];

let products = JSON.parse(localStorage.getItem(STORAGE_KEY)) || initialProducts;
function saveState() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(products));
}

const inputNewItem = document.querySelector('.add-item input');
const btnAddItem = document.querySelector('.btn-add');
const itemsList = document.querySelector('.items-list');
const leftStatsContainer = document.querySelector('.stats-section .stats-card:nth-child(1) .stats-tags');
const rightStatsContainer = document.querySelector('.stats-section .stats-card:nth-child(2) .stats-tags');

function render() {
itemsList.innerHTML = '';
leftStatsContainer.innerHTML = '';
rightStatsContainer.innerHTML = '';

products.forEach(product => {
const li = document.createElement('li');
li.className = `item-row ${product.isBought ? 'bought' : ''}`;

if (product.isBought) {
li.innerHTML = `
<span class="item-name">${product.name}</span>
<div class="item-controls">
<span class="quantity-badge">${product.quantity}</span>
<button class="btn-status" data-tooltip="Скасувати покупку">Не куплено</button>
</div>
`;
li.querySelector('.btn-status').addEventListener('click', () => toggleStatus(product.id));
} else {
const isMinusDisabled = product.quantity === 1 ? 'disabled' : '';
li.innerHTML = `
<span class="item-name" data-tooltip="Натисніть для редагування">${product.name}</span>
<div class="item-controls">
<button class="btn-round minus ${isMinusDisabled}" data-tooltip="Зменшити">-</button>
<span class="quantity-badge">${product.quantity}</span>
<button class="btn-round plus" data-tooltip="Збільшити">+</button>
<button class="btn-status buy" data-tooltip="Позначити як куплено">Куплено</button>
<button class="btn-round delete" data-tooltip="Видалити">×</button>
</div>
`;
li.querySelector('.plus').addEventListener('click', () => changeQuantity(product.id, 1));
if (product.quantity > 1) {
li.querySelector('.minus').addEventListener('click', () => changeQuantity(product.id, -1));
}
li.querySelector('.buy').addEventListener('click', () => toggleStatus(product.id));
li.querySelector('.delete').addEventListener('click', () => deleteProduct(product.id));
const nameSpan = li.querySelector('.item-name');
nameSpan.addEventListener('click', () => activateEditMode(nameSpan, product.id));
}
itemsList.appendChild(li);
const tag = document.createElement('span');
tag.className = `tag ${product.isBought ? 'bought-tag' : ''}`;
tag.innerHTML = `${product.name} <span class="tag-num">${product.quantity}</span>`;

if (product.isBought) {
rightStatsContainer.appendChild(tag);
} else {
leftStatsContainer.appendChild(tag);
}
});
saveState();
}

function addProduct() {
const name = inputNewItem.value.trim();
if (!name) return;
const newProduct = {
id: Date.now(),
name: name,
quantity: 1,
isBought: false
};
products.push(newProduct);
inputNewItem.value = '';
inputNewItem.focus();

render();
}

btnAddItem.addEventListener('click', addProduct);
inputNewItem.addEventListener('keydown', (e) => {
if (e.key === 'Enter') addProduct();
});

function deleteProduct(id) {
products = products.filter(p => p.id !== id);
render();
}

function changeQuantity(id, amount) {
const product = products.find(p => p.id === id);
if (product) {
product.quantity += amount;
render();
}
}

function toggleStatus(id) {
const product = products.find(p => p.id === id);
if (product) {
product.isBought = !product.isBought;
render();
}
}

function activateEditMode(spanElement, id) {
const currentName = spanElement.textContent;
const input = document.createElement('input');
input.type = 'text';
input.className = 'item-name-edit';
input.value = currentName;
spanElement.replaceWith(input);
input.focus();

function saveNewName() {
const newName = input.value.trim();
const product = products.find(p => p.id === id);

if (product && newName) {
product.name = newName;
}
render();
}
input.addEventListener('blur', saveNewName);
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
input.blur();
}
});
}

render();
49 changes: 38 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
<!DOCTYPE html>
<html>
<head lang="uk">
<html lang="uk">
<head>
<meta charset="UTF-8">
<title>My Page</title>


<link rel="stylesheet" type="text/css" href="main.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BuyList - Shopping Cart</title>
<link rel="stylesheet" href="main.css">
</head>
<body>
<span class="product-item">
Apple
<span class="amount">4</span>
</span>

<main class="container">
<section class="items-section card">
<div class="add-item">
<input type="text" id="new-item-input" placeholder="Назва товару" aria-label="Назва товару">
<button class="btn-add" id="btn-add" data-tooltip="Додати новий товар">Додати</button>
</div>
<ul class="items-list" id="items-list"></ul>
</section>

<aside class="stats-section">
<div class="card stats-card">
<h3>Залишилося</h3>
<div class="stats-tags" id="stats-left">
</div>
</div>
<div class="card stats-card">
<h3>Куплено</h3>
<div class="stats-tags" id="stats-bought">
</div>
</div>
</aside>
</main>

<div class="badge-container">
<div class="badge">
<span class="badge-title">BuyList</span>
<span class="badge-author">Виконав Паламарчук Ярослав</span>
</div>
</div>

<script src="app.js"></script>
</body>
</html>
</html>
157 changes: 140 additions & 17 deletions main.css
Original file line number Diff line number Diff line change
@@ -1,17 +1,140 @@
.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;
}
:root {
--primary-blue: #4a86e8;
--danger-red: #d9534f;
--success-green: #5cb85c;
--bg-gray: #e0e0e0;
--purple: #6f42c1;
}

body {
font-family: Arial, sans-serif;
background-color: var(--bg-gray);
margin: 0;
padding: 20px;
}

.container {
display: flex;
max-width: 1000px;
margin: 50px auto;
gap: 20px;
align-items: flex-start;
}

.card {
background: white;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 20px;
}

.items-section { flex: 2; }
.stats-section { flex: 1; display: flex; flex-direction: column; gap: 20px; }

.add-item { display: flex; gap: 10px; margin-bottom: 20px; border-bottom: 1px solid #eee; padding-bottom: 20px; }
.add-item input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.btn-add { background: var(--primary-blue); color: white; border: none; padding: 10px 25px; border-radius: 4px; font-weight: bold; cursor: pointer; }

.item-row {
display: flex; justify-content: space-between; align-items: center;
padding: 15px 0; border-bottom: 1px solid #eee;
}
.item-name-edit { border: 1px solid #4a86e8; padding: 5px; width: 120px; }
.item-controls { display: flex; align-items: center; gap: 8px; }

.btn-round {
width: 32px; height: 32px; border-radius: 50%; border: none;
color: white; font-weight: bold; cursor: pointer;
}
.minus { background: #d9534f; }
.minus.disabled { background: #f2dede; color: #a94442; opacity: 0.6; }
.plus { background: #5cb85c; }
.delete { background: #d9534f; margin-left: 10px; }

.btn-status {
border: 1px solid #ddd; background: #f9f9f9; padding: 8px 15px;
border-radius: 4px; cursor: pointer; font-size: 14px;
}
.bought .item-name { text-decoration: line-through; color: #555; }

.quantity-badge { background: #eee; padding: 5px 12px; border-radius: 4px; font-weight: bold; min-width: 20px; text-align: center; }

.stats-card h3 { margin-top: 0; font-size: 22px; }
.stats-tags { display: flex; flex-wrap: wrap; gap: 8px; }
.tag { background: #eee; padding: 5px 10px; border-radius: 5px; font-size: 13px; color: #666; font-weight: bold; }
.tag-num { background: #e67e22; color: white; border-radius: 50%; padding: 1px 6px; font-size: 11px; margin-left: 5px; }
.bought-tag { text-decoration: line-through; }

.badge-container {
position: fixed;
bottom: 0;
left: 40px;
z-index: 1000;
}

.badge {
background: var(--purple);
color: white;
padding: 15px 25px;
border-radius: 15px 15px 0 0;
transform: translateY(40px);
transition: transform 0.5s ease, background 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
cursor: default;
}

.badge:hover {
transform: translateY(0);
background: #5a32a3;
}

.badge-author { margin-top: 10px; font-size: 12px; opacity: 0.9; }

@media (max-width: 650px) {
.container { flex-direction: column; padding: 10px; }
.items-section, .stats-section { width: 100%; }
}

[data-tooltip] { position: relative; }

[data-tooltip]::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%) translateY(10px) scale(0.8);
background: var(--purple);
color: white;
padding: 5px 10px;
border-radius: 8px;
font-size: 12px;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
pointer-events: none;
margin-bottom: 8px;
}

[data-tooltip]:hover::after {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(0) scale(1);
}

@media print {
body { background: white; }
.badge-container { position: absolute; bottom: 20px; left: 20px; }
.badge {
background: white !important;
border: 2px solid var(--purple);
color: var(--purple) !important;
transform: none !important;
}
.badge-title { display: none; }
.badge-author::before { content: "Паламарчук Ярослав"; font-size: 16px; font-weight: bold; }
.badge-author { font-size: 0; }
.btn-add, .btn-round, .delete { display: none; }
}