seller_dashboard.html.twig 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. {% extends "base.html.twig" %}
  2. {% block title %}{{ t.seller_panel }} - {{ site_title }}{% endblock %}
  3. {% block extra_css %}
  4. <style>
  5. /* --- SELLER DASHBOARD STYLES --- */
  6. .dashboard-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; border-bottom: 2px solid var(--border-color); padding-bottom: 15px; }
  7. .dashboard-header h2 { margin: 0; color: var(--primary); font-size: 24px; text-transform: uppercase; letter-spacing: 1px; font-weight: 800; display: flex; align-items: center; gap: 10px; }
  8. .dashboard-nav { display: flex; gap: 10px; }
  9. .nav-btn { padding: 10px 15px; border-radius: 6px; text-decoration: none; font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; transition: 0.2s; border: 1px solid transparent; }
  10. .nav-btn.active { background: rgba(249, 115, 22, 0.1); color: var(--primary); border-color: rgba(249, 115, 22, 0.2); }
  11. .nav-btn:not(.active) { background: rgba(255,255,255,0.05); color: var(--text-main); border-color: var(--border-color); }
  12. .nav-btn:hover { background: rgba(255,255,255,0.1); }
  13. .dashboard-grid { display: grid; grid-template-columns: 1.2fr 2fr; gap: 30px; }
  14. .panel-card { background: var(--panel-bg); padding: 30px; border-radius: var(--border-radius); box-shadow: var(--shadow-sm); border: 1px solid var(--border-color); }
  15. .panel-card h3 { margin-top: 0; border-bottom: 1px solid rgba(255,255,255,0.05); padding-bottom: 15px; color: var(--text-main); font-size: 18px; text-transform: uppercase; letter-spacing: 1px; }
  16. .form-group { margin-bottom: 20px; }
  17. .form-group label { display: block; margin-bottom: 8px; font-weight: 700; font-size: 13px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px;}
  18. .form-group input[type="text"], .form-group input[type="number"], .form-group select, .form-group textarea { width: 100%; padding: 12px; background: #0f172a; border: 1px solid var(--border-color); border-radius: 6px; box-sizing: border-box; font-family: 'Inter', sans-serif; color: var(--text-main); outline: none; transition: 0.2s; }
  19. .form-group input:focus, .form-group select:focus, .form-group textarea:focus { border-color: var(--primary); box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.2); }
  20. .form-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
  21. .btn-submit { padding: 14px 20px; background: var(--primary); color: #fff; border: none; border-radius: 6px; cursor: pointer; font-weight: 800; width: 100%; font-size: 15px; text-transform: uppercase; letter-spacing: 1px; transition: 0.2s; }
  22. .btn-submit:hover { background: var(--primary-hover); transform: translateY(-1px); box-shadow: 0 4px 15px rgba(249, 115, 22, 0.3); }
  23. .btn-cancel { background: rgba(255,255,255,0.05); border: 1px solid var(--border-color); color: var(--text-main); padding: 14px 20px; border-radius: 6px; display: block; text-align: center; font-weight: 700; margin-top: 10px; text-decoration: none; text-transform: uppercase; transition: 0.2s;}
  24. .btn-cancel:hover { background: rgba(255,255,255,0.1); }
  25. .alert { padding: 15px; border-radius: 6px; margin-bottom: 20px; font-weight: 600; font-size: 14px;}
  26. .success { background: rgba(16, 185, 129, 0.1); color: var(--accent-green); border: 1px solid rgba(16, 185, 129, 0.2); }
  27. .error { background: rgba(239, 68, 68, 0.1); color: #ef4444; border: 1px solid rgba(239, 68, 68, 0.2); }
  28. .tags-input-container { display: flex; flex-wrap: wrap; gap: 5px; padding: 8px 10px; border: 1px solid var(--border-color); border-radius: 6px; background: #0f172a; align-items: center; cursor: text; }
  29. .tags-input-container input { border: none; outline: none; flex-grow: 1; padding: 4px; min-width: 150px; font-family: 'Inter', sans-serif; font-size: 14px; background: transparent; color: var(--text-main); }
  30. .tag-chip { background: rgba(255,255,255,0.1); color: var(--text-main); padding: 4px 10px; border-radius: 16px; font-size: 12px; font-weight: 600; display: flex; align-items: center; gap: 6px; border: 1px solid rgba(255,255,255,0.05);}
  31. .tag-chip span { cursor: pointer; color: var(--text-muted); font-weight: bold; font-size: 15px; line-height: 1; transition: 0.2s;}
  32. .tag-chip span:hover { color: #ef4444; }
  33. /* LISTINGS */
  34. .prod-list { list-style: none; padding: 0; margin: 0; }
  35. .prod-item { display: flex; gap: 15px; align-items: center; padding: 20px 0; border-bottom: 1px solid rgba(255,255,255,0.05); }
  36. .prod-item:last-child { border-bottom: none; }
  37. .prod-info-wrap { display: flex; gap: 15px; flex-grow: 1; align-items: center; }
  38. .prod-thumb { width: 75px; height: 75px; object-fit: cover; border-radius: 6px; background: #0f172a; flex-shrink: 0; border: 1px solid var(--border-color); }
  39. .prod-info { flex-grow: 1; min-width: 0; }
  40. .prod-info h4 { margin: 0 0 4px 0; color: var(--text-main); font-size: 16px; font-weight: 700;}
  41. .prod-info p { margin: 0; font-size: 13px; color: var(--text-muted); }
  42. .prod-info p strong { color: var(--accent-green); font-size: 15px;}
  43. .prod-info .tags-display { font-size: 11px; margin-top: 8px; display: flex; flex-wrap: wrap; gap: 6px; }
  44. .prod-info .tags-display span { background: rgba(59, 130, 246, 0.1); color: var(--accent-blue); border-radius: 12px; padding: 3px 8px; border: 1px solid rgba(59, 130, 246, 0.2); font-weight: 600;}
  45. .badge { padding: 4px 10px; border-radius: 20px; font-size: 11px; font-weight: 800; color: #fff; text-transform: uppercase; display: inline-block; margin-top: 8px; letter-spacing: 0.5px;}
  46. .badge-available { background: var(--accent-green); }
  47. .badge-sold { background: #ef4444; }
  48. .badge-hidden { background: var(--text-muted); color: #0b1120; }
  49. .actions { display: flex; gap: 8px; flex-shrink: 0; }
  50. .act-btn { padding: 8px 15px; border-radius: 6px; font-size: 12px; text-decoration: none; font-weight: 700; cursor: pointer; border: none; text-align: center; text-transform: uppercase; transition: 0.2s;}
  51. .act-edit { background: rgba(249, 115, 22, 0.1); color: var(--primary); border: 1px solid rgba(249, 115, 22, 0.2); }
  52. .act-edit:hover { background: var(--primary); color: white; }
  53. .act-del { background: rgba(239, 68, 68, 0.1); color: #ef4444; border: 1px solid rgba(239, 68, 68, 0.2); }
  54. .act-del:hover { background: #ef4444; color: white; }
  55. @media (max-width: 992px) {
  56. .dashboard-grid { grid-template-columns: 1fr; }
  57. .dashboard-header { flex-direction: column; gap: 15px; text-align: center; }
  58. }
  59. </style>
  60. {% endblock %}
  61. {% block content %}
  62. <div class="dashboard-header">
  63. <h2>🏪 {{ t.seller_panel }}</h2>
  64. <div class="dashboard-nav">
  65. <a href="seller_dashboard.php" class="nav-btn active">{{ t.my_listings }}</a>
  66. <a href="seller_orders.php" class="nav-btn">{{ t.customer_orders }}</a>
  67. </div>
  68. </div>
  69. <div class="dashboard-grid">
  70. <!-- ΑΡΙΣΤΕΡΑ: Φόρμα Ανάρτησης -->
  71. <div class="panel-card">
  72. <h3>{% if edit_product %}{{ t.edit_listing }}{% else %}{{ t.add_listing }}{% endif %}</h3>
  73. {% if message %}
  74. {{ message|raw }}
  75. {% endif %}
  76. <form method="POST" enctype="multipart/form-data">
  77. <input type="hidden" name="action" value="{% if edit_product %}edit_product{% else %}add_product{% endif %}">
  78. {% if edit_product %}
  79. <input type="hidden" name="product_id" value="{{ edit_product.id }}">
  80. {% endif %}
  81. <div class="form-group">
  82. <label>{{ t.title }}</label>
  83. <input type="text" name="title" value="{{ edit_product.title|default('') }}" required>
  84. </div>
  85. <!-- ΝΕΟ ΠΕΔΙΟ: SKU / Αύξων Αριθμός -->
  86. <div class="form-group">
  87. <label>{{ t.sku|default('Κωδικός / Αύξων Αριθμός (SKU)') }}</label>
  88. <input type="text" name="sku" value="{{ edit_product.sku|default('') }}" placeholder="π.χ. 001, 002...">
  89. </div>
  90. <div class="form-group">
  91. <label>{{ t.category }}</label>
  92. <select name="category_id" id="category_select" required onchange="loadAttributes(this.value); generateSku(this.value);">
  93. <option value="">{{ t.select_cat }}</option>
  94. {% for cat in categories %}
  95. <option value="{{ cat.id }}" {% if edit_product and edit_product.category_id == cat.id %}selected{% endif %}>
  96. {{ cat.display_name }}
  97. </option>
  98. {% endfor %}
  99. </select>
  100. </div>
  101. <div class="form-cols">
  102. <div class="form-group">
  103. <label>{{ t.price }}</label>
  104. <input type="number" step="0.01" name="price" value="{{ edit_product.price|default('') }}" required>
  105. </div>
  106. <div class="form-group">
  107. <label>{{ t.stock }}</label>
  108. <input type="number" name="quantity" value="{{ edit_product.quantity|default(1) }}" min="1" required>
  109. </div>
  110. </div>
  111. <div class="form-group">
  112. <label>{{ t.tags }}</label>
  113. <div class="tags-input-container" id="tags_container">
  114. <input type="text" id="tag_input_field" placeholder="{{ t.tags_placeholder }}">
  115. </div>
  116. <input type="hidden" name="search_tags" id="hidden_search_tags" value="{{ edit_product.search_tags|default('') }}">
  117. </div>
  118. <div id="dynamic-attributes-container" style="background: rgba(0,0,0,0.1); padding: 5px 20px; border-radius: 8px; border: 1px solid var(--border-color); margin-bottom: 20px; display: none;"></div>
  119. <div class="form-group">
  120. <label>{{ t.description }}</label>
  121. <textarea name="description" rows="4">{{ edit_product.description|default('') }}</textarea>
  122. </div>
  123. <div class="form-group">
  124. <label>{{ t.listing_status }}</label>
  125. <select name="status">
  126. <option value="available" {% if edit_product and edit_product.status == 'available' %}selected{% endif %}>{{ t.status_opt_available }}</option>
  127. <option value="hidden" {% if edit_product and edit_product.status == 'hidden' %}selected{% endif %}>{{ t.status_opt_hidden }}</option>
  128. <option value="sold" {% if edit_product and edit_product.status == 'sold' %}selected{% endif %}>{{ t.status_opt_sold }}</option>
  129. </select>
  130. </div>
  131. <div class="form-group" style="background: rgba(0,0,0,0.15); padding: 15px; border-radius: 6px; border: 1px dashed var(--border-color);">
  132. <label style="color: var(--text-main); margin-bottom: 10px;">{{ t.photo }} <span style="color: var(--text-muted); font-size: 11px;">{% if edit_product %}{{ t.photo_opt }}{% else %}{{ t.photo_req }}{% endif %}</span></label>
  133. <input type="file" name="image" accept="image/jpeg, image/png, image/gif" style="background: transparent; border: none; padding: 0;">
  134. </div>
  135. <button type="submit" class="btn-submit">{% if edit_product %}{{ t.btn_save }}{% else %}{{ t.btn_upload }}{% endif %}</button>
  136. {% if edit_product %}
  137. <a href="seller_dashboard.php" class="btn-cancel">{{ t.btn_cancel }}</a>
  138. {% endif %}
  139. </form>
  140. </div>
  141. <!-- ΔΕΞΙΑ: Οι αγγελίες μου -->
  142. <div class="panel-card">
  143. <h3>{{ t.my_listings|default('Οι Αγγελίες Μου') }} ({{ my_products|length }})</h3>
  144. {% if my_products|length > 0 %}
  145. <ul class="prod-list">
  146. {% for prod in my_products %}
  147. {% set disp_cat = (current_lang == 'en' and prod.cat_name_en) ? prod.cat_name_en : prod.cat_name_el %}
  148. <li class="prod-item">
  149. <div class="prod-info-wrap">
  150. {% if prod.image_path %}
  151. <img src="{{ prod.image_path }}" class="prod-thumb">
  152. {% else %}
  153. <div class="prod-thumb" style="display:flex; align-items:center; justify-content:center; font-size:11px; color:var(--text-muted); font-weight: bold;">{{ t.no_img }}</div>
  154. {% endif %}
  155. <div class="prod-info">
  156. <h4>
  157. {{ prod.title }}
  158. </h4>
  159. {% if prod.sku %}
  160. <p style="margin-bottom: 4px; font-size: 12px; color: var(--text-muted);">
  161. {{ t.sku|default('SKU') }}: <strong style="color: var(--text-main); font-size: 12px;">{{ prod.sku }}</strong>
  162. </p>
  163. {% endif %}
  164. <p>{{ disp_cat }} | <strong>{{ prod.price }} €</strong> | {{ t.stock_short }} {{ prod.quantity }}</p>
  165. {% if prod.search_tags %}
  166. <div class="tags-display">
  167. {% for tag in prod.search_tags|split(',') %}
  168. <span>{{ tag|trim }}</span>
  169. {% endfor %}
  170. </div>
  171. {% endif %}
  172. {% set status_class = 'badge-available' %}
  173. {% set status_label = t.status_active %}
  174. {% if prod.status == 'sold' %}
  175. {% set status_class = 'badge-sold' %}
  176. {% set status_label = t.status_sold %}
  177. {% elseif prod.status == 'hidden' %}
  178. {% set status_class = 'badge-hidden' %}
  179. {% set status_label = t.status_hidden %}
  180. {% endif %}
  181. <span class="badge {{ status_class }}">
  182. {{ status_label }}
  183. </span>
  184. </div>
  185. </div>
  186. <div class="actions">
  187. <a href="seller_dashboard.php?edit={{ prod.id }}" class="act-btn act-edit">Edit</a>
  188. <form method="POST" onsubmit="return confirm('{{ t.confirm_delete }}');" style="margin:0;">
  189. <input type="hidden" name="action" value="delete_product">
  190. <input type="hidden" name="product_id" value="{{ prod.id }}">
  191. <button type="submit" class="act-btn act-del">Del</button>
  192. </form>
  193. </div>
  194. </li>
  195. {% endfor %}
  196. </ul>
  197. {% else %}
  198. <div style="text-align: center; padding: 60px 20px; color: var(--text-muted);">
  199. <span style="font-size: 50px; display: block; margin-bottom: 15px; opacity: 0.5;">📦</span>
  200. <strong style="font-size: 16px; color: var(--text-main);">{{ t.no_listings }}</strong><br><br>
  201. {{ t.use_form }}
  202. </div>
  203. {% endif %}
  204. </div>
  205. </div>
  206. {% endblock %}
  207. {% block extra_js %}
  208. <script>
  209. document.addEventListener('DOMContentLoaded', function() {
  210. const tagContainer = document.getElementById('tags_container');
  211. const tagInput = document.getElementById('tag_input_field');
  212. const hiddenTagsInput = document.getElementById('hidden_search_tags');
  213. let tags = [];
  214. if (hiddenTagsInput.value.trim() !== '') {
  215. tags = hiddenTagsInput.value.split(',').map(t => t.trim()).filter(t => t !== '');
  216. renderTags();
  217. }
  218. tagContainer.addEventListener('click', () => tagInput.focus());
  219. tagInput.addEventListener('keydown', function(e) {
  220. if (e.key === 'Enter' || e.key === ',') {
  221. e.preventDefault();
  222. addTag(tagInput.value);
  223. } else if (e.key === 'Backspace' && tagInput.value === '' && tags.length > 0) {
  224. removeTag(tags.length - 1);
  225. }
  226. });
  227. function addTag(tag) {
  228. tag = tag.replace(/,/g, '').trim();
  229. if (tag !== '' && !tags.includes(tag)) {
  230. tags.push(tag);
  231. updateHiddenAndRender();
  232. }
  233. tagInput.value = '';
  234. }
  235. window.removeTagEl = function(index) {
  236. tags.splice(index, 1);
  237. updateHiddenAndRender();
  238. }
  239. function removeTag(index) { window.removeTagEl(index); }
  240. function updateHiddenAndRender() {
  241. hiddenTagsInput.value = tags.join(', ');
  242. renderTags();
  243. }
  244. function renderTags() {
  245. tagContainer.querySelectorAll('.tag-chip').forEach(chip => chip.remove());
  246. tags.forEach((tag, index) => {
  247. const chip = document.createElement('div');
  248. chip.className = 'tag-chip';
  249. chip.innerHTML = `${tag} <span onclick="window.removeTagEl(${index})">&times;</span>`;
  250. tagContainer.insertBefore(chip, tagInput);
  251. });
  252. }
  253. });
  254. function loadAttributes(categoryId, productId = 0) {
  255. const container = document.getElementById('dynamic-attributes-container');
  256. if (!categoryId) {
  257. container.style.display = 'none';
  258. container.innerHTML = '';
  259. return;
  260. }
  261. let url = 'seller_dashboard.php?ajax_get_attributes=1&category_id=' + categoryId;
  262. if (productId > 0) { url += '&product_id=' + productId; }
  263. fetch(url)
  264. .then(response => response.text())
  265. .then(html => {
  266. html = html.replace("grid-template-columns: 1fr 1fr;", "");
  267. if (html.trim() !== '') {
  268. container.innerHTML = html;
  269. container.style.display = 'block';
  270. } else {
  271. container.style.display = 'none';
  272. container.innerHTML = '';
  273. }
  274. })
  275. .catch(error => console.error('Error:', error));
  276. }
  277. {% if edit_product %}
  278. window.addEventListener('DOMContentLoaded', () => {
  279. loadAttributes({{ edit_product.category_id }}, {{ edit_product.id }});
  280. });
  281. {% endif %}
  282. </script>
  283. {% endblock %}