Kgshop commited on
Commit
01f3424
·
verified ·
1 Parent(s): 66efd2c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -12
app.py CHANGED
@@ -56,6 +56,8 @@ def load_data():
56
  p['price'] = 0
57
  if 'photo' not in p:
58
  p['photo'] = ''
 
 
59
  if 'stocks' not in p:
60
  p['stocks'] = {}
61
  if 'colors' in p:
@@ -134,6 +136,10 @@ def handle_post(req, data, role, emp):
134
  rolls = int(req.form.get('rolls', 0))
135
  except ValueError:
136
  rolls = 0
 
 
 
 
137
 
138
  data['products'].insert(0, {
139
  'id': str(time.time()),
@@ -141,8 +147,27 @@ def handle_post(req, data, role, emp):
141
  'description': req.form['description'],
142
  'price': price,
143
  'photo': photo,
 
144
  'stocks': {loc_id: rolls}
145
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
  elif action == 'delete' and role == 'admin':
148
  idx = int(req.form['index'])
@@ -259,7 +284,7 @@ MAIN_HTML = '''
259
  .product-info { padding: 12px; display: flex; flex-direction: column; flex: 1; }
260
  .product-title { font-size: 1rem; font-weight: 600; margin-bottom: 4px; line-height: 1.2; }
261
  .product-price { font-size: 1.1rem; font-weight: 700; color: var(--primary); margin-bottom: 4px; }
262
- .product-stock { font-size: 0.85rem; color: var(--text-muted); margin-bottom: 12px; }
263
  .product-actions { display: flex; gap: 8px; align-items: center; margin-top: auto; }
264
  .qty-input { width: 60px; padding: 8px; border: 1px solid var(--border); border-radius: 8px; text-align: center; font-weight: 600; outline: none; transition: border-color 0.2s; }
265
  .qty-input:focus { border-color: var(--primary); }
@@ -389,8 +414,9 @@ MAIN_HTML = '''
389
  <input type="text" name="name" class="form-control" placeholder="Название" required>
390
  <textarea name="description" class="form-control" placeholder="Описание"></textarea>
391
  <input type="number" name="price" class="form-control" placeholder="Цена (₸)" required>
 
392
  <label style="display:block; margin-bottom:8px; font-size:0.9rem; color:var(--text-muted);">Фото товара (не обязательно)</label>
393
- <input type="file" accept="image/*" class="form-control" onchange="processImage(this)">
394
  <input type="hidden" name="photo" id="photo_b64">
395
  <img id="photo_preview" style="display:none; width:100%; height:200px; object-fit:cover; border-radius:12px; margin-bottom:16px;">
396
  <select name="loc_id" id="add_loc_select" class="form-control" required></select>
@@ -400,6 +426,26 @@ MAIN_HTML = '''
400
  </div>
401
  </div>
402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  <div id="incomeModal" class="modal-overlay">
404
  <div class="modal-content">
405
  <button class="close-modal" onclick="closeModal('incomeModal')">×</button>
@@ -540,26 +586,32 @@ MAIN_HTML = '''
540
  <div class="product-title">${p.name}</div>
541
  <div class="product-price">${p.price} ₸</div>
542
  <div class="product-stock">В наличии: ${stock} шт</div>
 
543
  `;
544
 
545
  if (role !== 'public') {
546
  if (activeLoc !== 'all') {
547
  if (stock > 0) {
548
- html += `<div class="product-actions">
549
  <input type="number" id="amt_${pIdx}" class="qty-input" value="1" min="1" max="${stock}">
550
- <button class="btn btn-success" style="padding:8px; flex:1;" onclick="addToCart(${pIdx})">В корзину</button>
551
- </div>`;
 
552
  }
553
  if (role === 'admin') {
554
- html += `<div style="display:flex; gap:6px; margin-top:8px;">
555
  <button class="btn btn-primary" style="padding:6px; flex:1; font-size:0.8rem;" onclick="openTransfer(${pIdx}, ${stock})">Переместить</button>
 
556
  <button class="btn btn-danger" style="padding:6px; flex:1; font-size:0.8rem;" onclick="delProd(${pIdx})">Удалить</button>
557
  </div>`;
558
  }
559
  } else {
560
  html += `<div style="font-size:0.8rem; color:var(--text-muted); margin-top:auto;">Выберите конкретную локацию вверху для действий</div>`;
561
  if (role === 'admin') {
562
- html += `<div style="margin-top:8px;"><button class="btn btn-danger" style="padding:6px; width:100%; font-size:0.8rem;" onclick="delProd(${pIdx})">Удалить</button></div>`;
 
 
 
563
  }
564
  }
565
  }
@@ -570,9 +622,9 @@ MAIN_HTML = '''
570
  cat.innerHTML = htmlArr.join('');
571
  }
572
 
573
- function addToCart(pIdx) {
574
- let amt = parseInt(document.getElementById('amt_'+pIdx).value);
575
  let p = data.products[pIdx];
 
576
  let stock = p.stocks[activeLoc] || 0;
577
  if(amt <= 0 || isNaN(amt)) return alert('Неверное количество');
578
 
@@ -676,7 +728,27 @@ MAIN_HTML = '''
676
  openModal('transferModal');
677
  }
678
 
679
- function processImage(input) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
680
  if (input.files && input.files[0]) {
681
  const reader = new FileReader();
682
  reader.onload = function(e) {
@@ -693,8 +765,8 @@ MAIN_HTML = '''
693
  const ctx = canvas.getContext('2d');
694
  ctx.drawImage(img, 0, 0, w, h);
695
  const b64 = canvas.toDataURL('image/jpeg', 0.6);
696
- document.getElementById('photo_b64').value = b64;
697
- const p = document.getElementById('photo_preview');
698
  p.src = b64; p.style.display = 'block';
699
  }
700
  img.src = e.target.result;
 
56
  p['price'] = 0
57
  if 'photo' not in p:
58
  p['photo'] = ''
59
+ if 'box_qty' not in p:
60
+ p['box_qty'] = 1
61
  if 'stocks' not in p:
62
  p['stocks'] = {}
63
  if 'colors' in p:
 
136
  rolls = int(req.form.get('rolls', 0))
137
  except ValueError:
138
  rolls = 0
139
+ try:
140
+ box_qty = int(req.form.get('box_qty', 1))
141
+ except ValueError:
142
+ box_qty = 1
143
 
144
  data['products'].insert(0, {
145
  'id': str(time.time()),
 
147
  'description': req.form['description'],
148
  'price': price,
149
  'photo': photo,
150
+ 'box_qty': box_qty,
151
  'stocks': {loc_id: rolls}
152
  })
153
+
154
+ elif action == 'edit' and role == 'admin':
155
+ idx = int(req.form['index'])
156
+ if 0 <= idx < len(data['products']):
157
+ p = data['products'][idx]
158
+ p['name'] = req.form.get('name', p['name'])
159
+ p['description'] = req.form.get('description', p.get('description', ''))
160
+ try:
161
+ p['price'] = int(req.form.get('price', p.get('price', 0)))
162
+ except ValueError:
163
+ pass
164
+ try:
165
+ p['box_qty'] = int(req.form.get('box_qty', p.get('box_qty', 1)))
166
+ except ValueError:
167
+ pass
168
+ photo = req.form.get('photo', '')
169
+ if photo:
170
+ p['photo'] = photo
171
 
172
  elif action == 'delete' and role == 'admin':
173
  idx = int(req.form['index'])
 
284
  .product-info { padding: 12px; display: flex; flex-direction: column; flex: 1; }
285
  .product-title { font-size: 1rem; font-weight: 600; margin-bottom: 4px; line-height: 1.2; }
286
  .product-price { font-size: 1.1rem; font-weight: 700; color: var(--primary); margin-bottom: 4px; }
287
+ .product-stock { font-size: 0.85rem; color: var(--text-muted); margin-bottom: 4px; }
288
  .product-actions { display: flex; gap: 8px; align-items: center; margin-top: auto; }
289
  .qty-input { width: 60px; padding: 8px; border: 1px solid var(--border); border-radius: 8px; text-align: center; font-weight: 600; outline: none; transition: border-color 0.2s; }
290
  .qty-input:focus { border-color: var(--primary); }
 
414
  <input type="text" name="name" class="form-control" placeholder="Название" required>
415
  <textarea name="description" class="form-control" placeholder="Описание"></textarea>
416
  <input type="number" name="price" class="form-control" placeholder="Цена (₸)" required>
417
+ <input type="number" name="box_qty" class="form-control" placeholder="Штук в коробке" value="1" required>
418
  <label style="display:block; margin-bottom:8px; font-size:0.9rem; color:var(--text-muted);">Фото товара (не обязательно)</label>
419
+ <input type="file" accept="image/*" class="form-control" onchange="processImage(this, 'photo_b64', 'photo_preview')">
420
  <input type="hidden" name="photo" id="photo_b64">
421
  <img id="photo_preview" style="display:none; width:100%; height:200px; object-fit:cover; border-radius:12px; margin-bottom:16px;">
422
  <select name="loc_id" id="add_loc_select" class="form-control" required></select>
 
426
  </div>
427
  </div>
428
 
429
+ <div id="editModal" class="modal-overlay">
430
+ <div class="modal-content">
431
+ <button class="close-modal" onclick="closeModal('editModal')">×</button>
432
+ <h2>Изменить товар</h2>
433
+ <form method="POST" style="margin-top:16px;">
434
+ <input type="hidden" name="action" value="edit">
435
+ <input type="hidden" name="index" id="edit_index">
436
+ <input type="text" name="name" id="edit_name" class="form-control" placeholder="Название" required>
437
+ <textarea name="description" id="edit_desc" class="form-control" placeholder="Описание"></textarea>
438
+ <input type="number" name="price" id="edit_price" class="form-control" placeholder="Цена (₸)" required>
439
+ <input type="number" name="box_qty" id="edit_box_qty" class="form-control" placeholder="Штук в коробке" required>
440
+ <label style="display:block; margin-bottom:8px; font-size:0.9rem; color:var(--text-muted);">Новое фото (не обязательно)</label>
441
+ <input type="file" accept="image/*" class="form-control" onchange="processImage(this, 'edit_photo_b64', 'edit_photo_preview')">
442
+ <input type="hidden" name="photo" id="edit_photo_b64">
443
+ <img id="edit_photo_preview" style="display:none; width:100%; height:200px; object-fit:cover; border-radius:12px; margin-bottom:16px;">
444
+ <button type="submit" class="btn btn-success" style="width:100%; background:var(--primary);">Сохранить изменения</button>
445
+ </form>
446
+ </div>
447
+ </div>
448
+
449
  <div id="incomeModal" class="modal-overlay">
450
  <div class="modal-content">
451
  <button class="close-modal" onclick="closeModal('incomeModal')">×</button>
 
586
  <div class="product-title">${p.name}</div>
587
  <div class="product-price">${p.price} ₸</div>
588
  <div class="product-stock">В наличии: ${stock} шт</div>
589
+ <div class="product-stock" style="margin-bottom: 12px;">В коробке: ${p.box_qty || 1} шт</div>
590
  `;
591
 
592
  if (role !== 'public') {
593
  if (activeLoc !== 'all') {
594
  if (stock > 0) {
595
+ html += `<div class="product-actions" style="margin-bottom:8px;">
596
  <input type="number" id="amt_${pIdx}" class="qty-input" value="1" min="1" max="${stock}">
597
+ <button class="btn btn-success" style="padding:8px; flex:1; font-size:0.9rem;" onclick="addToCart(${pIdx}, false)">В корзину</button>
598
+ </div>
599
+ <button class="btn btn-primary" style="width:100%; padding:8px; font-size:0.9rem; margin-bottom:8px;" onclick="addToCart(${pIdx}, true)">Коробка + (${p.box_qty || 1} шт)</button>`;
600
  }
601
  if (role === 'admin') {
602
+ html += `<div style="display:flex; gap:6px; margin-top:auto;">
603
  <button class="btn btn-primary" style="padding:6px; flex:1; font-size:0.8rem;" onclick="openTransfer(${pIdx}, ${stock})">Переместить</button>
604
+ <button class="btn btn-success" style="padding:6px; flex:1; font-size:0.8rem; background:#eab308; color:#fff;" onclick="openEdit(${pIdx})">Изменить</button>
605
  <button class="btn btn-danger" style="padding:6px; flex:1; font-size:0.8rem;" onclick="delProd(${pIdx})">Удалить</button>
606
  </div>`;
607
  }
608
  } else {
609
  html += `<div style="font-size:0.8rem; color:var(--text-muted); margin-top:auto;">Выберите конкретную локацию вверху для действий</div>`;
610
  if (role === 'admin') {
611
+ html += `<div style="display:flex; gap:6px; margin-top:8px;">
612
+ <button class="btn btn-success" style="padding:6px; flex:1; font-size:0.8rem; background:#eab308; color:#fff;" onclick="openEdit(${pIdx})">Изменить</button>
613
+ <button class="btn btn-danger" style="padding:6px; flex:1; font-size:0.8rem;" onclick="delProd(${pIdx})">Удалить</button>
614
+ </div>`;
615
  }
616
  }
617
  }
 
622
  cat.innerHTML = htmlArr.join('');
623
  }
624
 
625
+ function addToCart(pIdx, isBox = false) {
 
626
  let p = data.products[pIdx];
627
+ let amt = isBox ? parseInt(p.box_qty || 1) : parseInt(document.getElementById('amt_'+pIdx).value);
628
  let stock = p.stocks[activeLoc] || 0;
629
  if(amt <= 0 || isNaN(amt)) return alert('Неверное количество');
630
 
 
728
  openModal('transferModal');
729
  }
730
 
731
+ function openEdit(pIdx) {
732
+ let p = data.products[pIdx];
733
+ document.getElementById('edit_index').value = pIdx;
734
+ document.getElementById('edit_name').value = p.name;
735
+ document.getElementById('edit_desc').value = p.description || '';
736
+ document.getElementById('edit_price').value = p.price;
737
+ document.getElementById('edit_box_qty').value = p.box_qty || 1;
738
+ document.getElementById('edit_photo_b64').value = '';
739
+
740
+ let preview = document.getElementById('edit_photo_preview');
741
+ if(p.photo) {
742
+ preview.src = p.photo;
743
+ preview.style.display = 'block';
744
+ } else {
745
+ preview.src = '';
746
+ preview.style.display = 'none';
747
+ }
748
+ openModal('editModal');
749
+ }
750
+
751
+ function processImage(input, b64Id, previewId) {
752
  if (input.files && input.files[0]) {
753
  const reader = new FileReader();
754
  reader.onload = function(e) {
 
765
  const ctx = canvas.getContext('2d');
766
  ctx.drawImage(img, 0, 0, w, h);
767
  const b64 = canvas.toDataURL('image/jpeg', 0.6);
768
+ document.getElementById(b64Id).value = b64;
769
+ const p = document.getElementById(previewId);
770
  p.src = b64; p.style.display = 'block';
771
  }
772
  img.src = e.target.result;