seller_dashboard.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. <?php
  2. // Αρχείο: /var/www/html/hockey-cms/seller_dashboard.php
  3. session_start();
  4. if (!isset($_SESSION['user_id']) or empty($_SESSION['is_seller'])) {
  5. header("Location: index.php");
  6. exit;
  7. }
  8. require_once __DIR__ . '/vendor/autoload.php';
  9. require_once __DIR__ . '/config/database.php';
  10. // --- Μηχανισμός Αλλαγής Γλώσσας ---
  11. if (isset($_GET['lang']) and in_array($_GET['lang'], ['el', 'en'])) {
  12. $_SESSION['lang'] =$_GET['lang'];
  13. }
  14. $current_lang =$_SESSION['lang'] ?? 'el';
  15. // --- Λεξικό Μεταφράσεων ---
  16. $translations = [
  17. 'el' => [
  18. 'seller_panel' => 'Πίνακας Πωλητή',
  19. 'my_listings' => 'Οι Αγγελίες Μου',
  20. 'customer_orders' => 'Παραγγελίες Πελατών',
  21. 'edit_listing' => 'Επεξεργασία Αγγελίας',
  22. 'add_listing' => 'Ανέβασμα Αγγελίας',
  23. 'title' => 'Τίτλος Προϊόντος *',
  24. 'category' => 'Κατηγορία *',
  25. 'select_cat' => '-- Επίλεξε Κατηγορία --',
  26. 'sku' => 'Κωδικός (SKU)',
  27. 'price' => 'Τιμή (€) *',
  28. 'stock' => 'Απόθεμα',
  29. 'tags' => 'Ετικέτες Αναζήτησης (Tags)',
  30. 'tags_placeholder' => 'Γράψτε λέξη και πατήστε Enter...',
  31. 'description' => 'Περιγραφή',
  32. 'listing_status' => 'Κατάσταση Αγγελίας',
  33. 'status_opt_available' => 'Ενεργή (Εμφανίζεται)',
  34. 'status_opt_hidden' => 'Προσωρινή Απόκρυψη',
  35. 'status_opt_sold' => 'Πουλήθηκε',
  36. 'status_active' => 'Ενεργή',
  37. 'status_hidden' => 'Κρυφή',
  38. 'status_sold' => 'Πουλήθηκε',
  39. 'photo' => 'Φωτογραφία',
  40. 'photo_req' => '(Απαραίτητη)',
  41. 'photo_opt' => '(Αφήστε κενό αν δεν αλλάξει)',
  42. 'btn_save' => 'Αποθήκευση',
  43. 'btn_upload' => 'Ανέβασμα Προϊόντος',
  44. 'btn_cancel' => 'Ακύρωση',
  45. 'stock_short' => 'Αποθ:',
  46. 'no_img' => 'No IMG',
  47. 'confirm_delete' => 'Σίγουρα θέλετε να διαγράψετε την αγγελία;',
  48. 'no_listings' => 'Δεν έχεις ανεβάσει ακόμα καμία αγγελία.',
  49. 'use_form' => 'Χρησιμοποίησε τη φόρμα για να ξεκινήσεις!',
  50. 'features' => 'Χαρακτηριστικά',
  51. 'features_placeholder' => 'π.χ. τιμή...'
  52. ],
  53. 'en' => [
  54. 'seller_panel' => 'Seller Dashboard',
  55. 'my_listings' => 'My Listings',
  56. 'customer_orders' => 'Customer Orders',
  57. 'edit_listing' => 'Edit Listing',
  58. 'add_listing' => 'Upload Listing',
  59. 'title' => 'Product Title *',
  60. 'category' => 'Category *',
  61. 'select_cat' => '-- Select Category --',
  62. 'sku' => 'SKU',
  63. 'price' => 'Price (€) *',
  64. 'stock' => 'Stock',
  65. 'tags' => 'Search Tags',
  66. 'tags_placeholder' => 'Type a word and press Enter...',
  67. 'description' => 'Description',
  68. 'listing_status' => 'Listing Status',
  69. 'status_opt_available' => 'Active (Visible)',
  70. 'status_opt_hidden' => 'Temporarily Hidden',
  71. 'status_opt_sold' => 'Sold',
  72. 'status_active' => 'Active',
  73. 'status_hidden' => 'Hidden',
  74. 'status_sold' => 'Sold',
  75. 'photo' => 'Photo',
  76. 'photo_req' => '(Required)',
  77. 'photo_opt' => '(Leave blank if unchanged)',
  78. 'btn_save' => 'Save Changes',
  79. 'btn_upload' => 'Upload Product',
  80. 'btn_cancel' => 'Cancel',
  81. 'stock_short' => 'Qty:',
  82. 'no_img' => 'No IMG',
  83. 'confirm_delete' => 'Are you sure you want to delete this listing?',
  84. 'no_listings' => 'You haven\'t uploaded any listings yet.',
  85. 'use_form' => 'Use the form to get started!',
  86. 'features' => 'Features',
  87. 'features_placeholder' => 'e.g. value...'
  88. ]
  89. ];
  90. $t =$translations[$current_lang];$seller_id = $_SESSION['user_id'];$message = '';
  91. // AJAX GET ATTRIBUTES
  92. if (isset($_GET['ajax_get_attributes']) and isset($_GET['category_id'])) {$cat_id = (int)$_GET['category_id'];$product_id = isset($_GET['product_id']) ? (int)$_GET['product_id'] : 0;
  93. $stmt =$pdo->prepare("SELECT attribute_group_id FROM categories WHERE id = ?");
  94. $stmt->execute([$cat_id]);
  95. $cat =$stmt->fetch();
  96. if ($cat &&$cat['attribute_group_id']) {
  97. $attrStmt =$pdo->prepare("SELECT id, name, unit FROM attributes WHERE group_id = ? ORDER BY name");
  98. $attrStmt->execute([$cat['attribute_group_id']]);$attributes = $attrStmt->fetchAll();$existing_values = [];
  99. if ($product_id > 0) {
  100. $valStmt =$pdo->prepare("SELECT attribute_id, value FROM product_attribute_values WHERE product_id = ?");
  101. $valStmt->execute([$product_id]);
  102. while ($vRow = $valStmt->fetch(PDO::FETCH_ASSOC)) {$existing_values[$vRow['attribute_id']] =$vRow['value'];
  103. }
  104. }
  105. if ($attributes) {
  106. echo "<h4 style='margin-bottom: 12px; margin-top: 20px; color: var(--primary); text-transform: uppercase; letter-spacing: 1px; font-size: 14px;'>".$t['features']."</h4>";
  107. echo "<div class='form-cols' style='display: grid; grid-template-columns: 1fr 1fr; gap: 15px;'>";
  108. foreach ($attributes as $attr) {$val = isset($existing_values[$attr['id']]) ? htmlspecialchars($existing_values[$attr['id']]) : '';
  109. echo "<div class='form-group'>";
  110. echo "<label>" . htmlspecialchars($attr['name']) . ($attr['unit'] ? " ({$attr['unit']})" : "") . "</label>";
  111. echo "<input type='text' name='attributes[" . $attr['id'] . "]' value='" . $val . "' placeholder='".$t['features_placeholder']."'>";
  112. echo "</div>";
  113. }
  114. echo "</div>";
  115. }
  116. }
  117. exit;
  118. }
  119. // --- 2. AJAX API: Δημιουργία προτεινόμενου SKU (Πρόθεμα 3 Γράμματα + Αύξων) ---
  120. if (isset($_GET['ajax_generate_sku']) && isset($_GET['category_id'])) {
  121. $cat_id = (int)$_GET['category_id'];
  122. $stmt =$pdo->prepare("SELECT name_en FROM categories WHERE id = ?");
  123. $stmt->execute([$cat_id]);
  124. $name_en =$stmt->fetchColumn();
  125. if ($name_en) {
  126. // Κρατάμε μόνο τα γράμματα από τα Αγγλικά
  127. $clean_name = preg_replace('/[^a-zA-Z]/', '',$name_en);
  128. $prefix = strtoupper(substr($clean_name, 0, 3));
  129. if (empty($prefix)) {$prefix = 'CAT'; }
  130. // Βρίσκουμε το μεγαλύτερο νούμερο με αυτό το πρόθεμα που ανήκει στον seller (ή γενικά)
  131. // Θα το κάνουμε γενικά (όπως στο Admin) για να είναι το SKU απολύτως μοναδικό σε όλο το σύστημα
  132. $stmtSku =$pdo->prepare("SELECT sku FROM products WHERE sku LIKE ?");
  133. $stmtSku->execute([$prefix . '-%']);
  134. $skus =$stmtSku->fetchAll(PDO::FETCH_COLUMN);
  135. $max = 0;
  136. foreach ($skus as$s) {
  137. $parts = explode('-',$s);
  138. if (count($parts) > 1) {
  139. $num = (int)end($parts);
  140. if ($num >$max) { $max =$num; }
  141. }
  142. }
  143. $next_num = str_pad($max + 1, 3, '0', STR_PAD_LEFT);
  144. echo $prefix . '-' .$next_num;
  145. }
  146. exit;
  147. }
  148. // Βοηθητική Resize
  149. function resizeAndSaveImage($source_file, $target_file,$max_resolution = 800) {
  150. $info = getimagesize($source_file);
  151. if (!$info) return false;
  152. $mime =$info['mime'];
  153. switch ($mime) {
  154. case 'image/jpeg': $image = imagecreatefromjpeg($source_file); break;
  155. case 'image/png': $image = imagecreatefrompng($source_file); break;
  156. case 'image/gif': $image = imagecreatefromgif($source_file); break;
  157. default: return false;
  158. }
  159. $original_width = imagesx($image);
  160. $original_height = imagesy($image);
  161. $ratio =$max_resolution / max($original_width,$original_height);
  162. $new_width =$original_width > $max_resolution ? round($original_width * $ratio) :$original_width;
  163. $new_height =$original_height > $max_resolution ? round($original_height * $ratio) :$original_height;
  164. $new_image = imagecreatetruecolor($new_width,$new_height);
  165. if ($mime == 'image/png') {
  166. imagealphablending($new_image, false);
  167. imagesavealpha($new_image, true);
  168. }
  169. imagecopyresampled($new_image,$image, 0, 0, 0, 0, $new_width,$new_height, $original_width,$original_height);
  170. imagejpeg($new_image,$target_file, 85);
  171. imagedestroy($image);
  172. imagedestroy($new_image);
  173. return true;
  174. }
  175. // POST Actions
  176. if ($_SERVER['REQUEST_METHOD'] === 'POST' and isset($_POST['action'])) {
  177. $action =$_POST['action'];
  178. if ($action == 'delete_product') {
  179. $product_id = (int)$_POST['product_id'];
  180. try {
  181. $delStmt =$pdo->prepare("DELETE FROM products WHERE id = ? AND seller_id = ?");
  182. $delStmt->execute([$product_id,$seller_id]);
  183. if ($delStmt->rowCount() > 0) {$message = "<div class='alert success'>Η αγγελία διαγράφηκε επιτυχώς!</div>";
  184. } else {
  185. $message = "<div class='alert error'>Αδυναμία διαγραφής.</div>";
  186. }
  187. } catch (Exception $e) {$message = "<div class='alert error'>Σφάλμα: " . $e->getMessage() . "</div>";
  188. }
  189. }
  190. else {
  191. $title = trim($_POST['title']);
  192. $sku = trim($_POST['sku']);
  193. $category_id = (int)$_POST['category_id'];
  194. $price = (float)$_POST['price'];
  195. $quantity = (int)$_POST['quantity'];
  196. $status =$_POST['status'] ?? 'available';
  197. $description = trim($_POST['description']);
  198. $search_tags = trim($_POST['search_tags']);
  199. if (!empty($title) and !empty($category_id) and !empty($price)) {
  200. try {
  201. $pdo->beginTransaction();
  202. $stmtGrp =$pdo->prepare("SELECT attribute_group_id FROM categories WHERE id = ?");
  203. $stmtGrp->execute([$category_id]);
  204. $group_id =$stmtGrp->fetchColumn();
  205. if ($action == 'add_product') {
  206. $stmt =$pdo->prepare("INSERT INTO products (seller_id, category_id, attribute_group_id, title, sku, description, search_tags, price, quantity, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
  207. $stmt->execute([$seller_id, $category_id,$group_id, $title,$sku, $description,$search_tags, $price,$quantity, $status]);$product_id = $pdo->lastInsertId();$message = "<div class='alert success'>Η αγγελία ανέβηκε επιτυχώς!</div>";
  208. } elseif ($action == 'edit_product') {
  209. $product_id = (int)$_POST['product_id'];
  210. $stmt =$pdo->prepare("UPDATE products SET category_id = ?, attribute_group_id = ?, title = ?, sku = ?, description = ?, search_tags = ?, price = ?, quantity = ?, status = ? WHERE id = ? AND seller_id = ?");
  211. $stmt->execute([$category_id, $group_id,$title, $sku,$description, $search_tags,$price, $quantity,$status, $product_id,$seller_id]);
  212. $delAttr =$pdo->prepare("DELETE FROM product_attribute_values WHERE product_id = ?");
  213. $delAttr->execute([$product_id]);$message = "<div class='alert success'>Η αγγελία ενημερώθηκε επιτυχώς!</div>";
  214. }
  215. if (isset($_POST['attributes']) and is_array($_POST['attributes'])) {
  216. $insAttr =$pdo->prepare("INSERT INTO product_attribute_values (product_id, attribute_id, value) VALUES (?, ?, ?)");
  217. foreach ($_POST['attributes'] as $attr_id =>$val) {
  218. $val = trim($val);
  219. if ($val !== '') {
  220. $insAttr->execute([$product_id, $attr_id,$val]);
  221. }
  222. }
  223. }
  224. if (isset($_FILES['image']) and $_FILES['image']['error'] == 0) {$upload_dir = __DIR__ . '/uploads/equipment/';
  225. if (!is_dir($upload_dir)) { mkdir($upload_dir, 0755, true); }
  226. $filename = 'prod_' .$product_id . '_' . time() . '.jpg';
  227. $target_path = $upload_dir .$filename;
  228. if (resizeAndSaveImage($_FILES['image']['tmp_name'],$target_path)) {
  229. $checkImg =$pdo->prepare("SELECT id FROM product_images WHERE product_id = ? AND is_primary = 1");
  230. $checkImg->execute([$product_id]);
  231. if ($checkImg->fetch()) {
  232. $updImg =$pdo->prepare("UPDATE product_images SET image_path = ? WHERE product_id = ? AND is_primary = 1");
  233. $updImg->execute(['uploads/equipment/' . $filename,$product_id]);
  234. } else {
  235. $insImg =$pdo->prepare("INSERT INTO product_images (product_id, image_path, is_primary) VALUES (?, ?, 1)");
  236. $insImg->execute([$product_id, 'uploads/equipment/' .$filename]);
  237. }
  238. }
  239. }
  240. $pdo->commit();
  241. } catch (Exception $e) {
  242. $pdo->rollBack();$message = "<div class='alert error'>Σφάλμα: " . $e->getMessage() . "</div>";
  243. }
  244. } else {
  245. $message = "<div class='alert error'>Συμπληρώστε τα υποχρεωτικά πεδία!</div>";
  246. }
  247. }
  248. }
  249. // Ανάκτηση δεδομένων για το View
  250. $edit_product = null;
  251. if (isset($_GET['edit'])) {
  252. $edit_id = (int)$_GET['edit'];
  253. $editStmt =$pdo->prepare("SELECT * FROM products WHERE id = ? AND seller_id = ?");
  254. $editStmt->execute([$edit_id,$seller_id]);
  255. $edit_product =$editStmt->fetch(PDO::FETCH_ASSOC);
  256. }
  257. $categories =$pdo->query("SELECT id, parent_id, IFNULL(NULLIF(name_en, ''), name_el) as display_name FROM categories ORDER BY sort_order ASC, name_el ASC")->fetchAll();
  258. if ($current_lang === 'el') {
  259. $categories =$pdo->query("SELECT id, parent_id, name_el as display_name FROM categories ORDER BY sort_order ASC, name_el ASC")->fetchAll();
  260. }
  261. $stmtProds =$pdo->prepare("
  262. SELECT p.id, p.sku, p.title, p.search_tags, p.price, p.quantity, p.status,
  263. IFNULL(NULLIF(c.name_en, ''), c.name_el) as cat_name_en, c.name_el as cat_name_el,
  264. pi.image_path
  265. FROM products p
  266. LEFT JOIN categories c ON p.category_id = c.id
  267. LEFT JOIN product_images pi ON (p.id = pi.product_id AND pi.is_primary = 1)
  268. WHERE p.seller_id = ?
  269. ORDER BY p.id DESC
  270. ");
  271. $stmtProds->execute([$seller_id]);
  272. $my_products =$stmtProds->fetchAll(PDO::FETCH_ASSOC);
  273. // --- ΦΟΡΤΩΣΗ TWIG ---
  274. $active_theme = 'default';
  275. $site_logo = '';$site_title = 'Hockey CMS';
  276. try {
  277. $stmtSettings =$pdo->query("SELECT `key`, value FROM settings WHERE `key` IN ('active_theme', 'site_logo', 'site_title')");
  278. while ($row =$stmtSettings->fetch(PDO::FETCH_ASSOC)) {
  279. if ($row['key'] === 'active_theme' && !empty($row['value'])) {
  280. $active_theme =$row['value'];
  281. } elseif ($row['key'] === 'site_logo') {
  282. $site_logo =$row['value'];
  283. } elseif ($row['key'] === 'site_title' && !empty($row['value'])) {
  284. $site_title =$row['value'];
  285. }
  286. }
  287. } catch (Exception $e) {}
  288. $theme_dir = __DIR__ . '/themes/' .$active_theme;
  289. if (!is_dir($theme_dir)) {
  290. $active_theme = 'default';$theme_dir = __DIR__ . '/themes/default';
  291. }
  292. $loader = new \Twig\Loader\FilesystemLoader($theme_dir);
  293. $twig = new \Twig\Environment($loader, ['cache' => false]);
  294. $twig->addGlobal('session',$_SESSION);
  295. $twig->addGlobal('current_lang',$current_lang);
  296. $twig->addGlobal('site_logo',$site_logo);
  297. echo $twig->render('seller_dashboard.html.twig', [
  298. 'site_title' => $site_title,
  299. 't' => $t,
  300. 'message' => $message,
  301. 'edit_product' => $edit_product,
  302. 'categories' => $categories,
  303. 'my_products' => $my_products
  304. ]);