diff --git a/admin/attribute_keys.php b/admin/attribute_keys.php new file mode 100644 index 0000000..860865d --- /dev/null +++ b/admin/attribute_keys.php @@ -0,0 +1,143 @@ +prepare("INSERT INTO attribute_keys (name) VALUES (?)"); + $stmt->execute([$name]); + $_SESSION['success_message'] = 'Klucz atrybutu został pomyślnie dodany.'; + } catch (PDOException $e) { + if ($e->errorInfo[1] == 1062) { // Duplicate entry + $error = 'Klucz atrybutu o tej nazwie już istnieje.'; + } else { + $error = 'Wystąpił błąd podczas dodawania klucza atrybutu: ' . $e->getMessage(); + } + } + } + + if (!empty($error)) { + $_SESSION['error_message'] = $error; + } + header("Location: attribute_keys.php"); + exit(); +} + +// Handle deletion +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_key'])) { + $key_id = $_POST['id']; + if ($key_id) { + try { + // It is better to check for usage first to provide a user-friendly error message. + $stmt_check = db()->prepare("SELECT COUNT(*) FROM product_attributes WHERE attribute_key_id = ?"); + $stmt_check->execute([$key_id]); + + if ($stmt_check->fetchColumn() > 0) { + $_SESSION['error_message'] = 'Nie można usunąć klucza, ponieważ jest on używany przez produkty. Zmień atrybuty produktów przed usunięciem klucza.'; + } else { + // No product attributes use this key, safe to delete. + $stmt = db()->prepare("DELETE FROM attribute_keys WHERE id = ?"); + $stmt->execute([$key_id]); + $_SESSION['success_message'] = 'Klucz atrybutu został pomyślnie usunięty.'; + } + } catch (PDOException $e) { + $_SESSION['error_message'] = 'Wystąpił błąd podczas usuwania klucza atrybutu: ' . $e->getMessage(); + } + } else { + $_SESSION['error_message'] = 'Nieprawidłowe żądanie usunięcia.'; + } + header("Location: attribute_keys.php"); + exit(); +} + + +// Fetch all attribute keys +$keys_stmt = db()->query("SELECT * FROM attribute_keys ORDER BY name"); +$attribute_keys = $keys_stmt->fetchAll(); + +?> + + + + + + Admin - Klucze atrybutów + + + + + + +
+ +

' . htmlspecialchars($_SESSION['error_message']) . '

'; + unset($_SESSION['error_message']); + } + if (isset($_SESSION['success_message'])) { + echo '

' . htmlspecialchars($_SESSION['success_message']) . '

'; + unset($_SESSION['success_message']); + } + ?> + +
+
+

Parametry techniczne – klucze

+
+
+
+
+

Istniejące klucze

+ + + + + + + + + + + + + + + + + +
IDNazwaAkcje
+
+ + +
+
+
+
+

Dodaj nowy klucz

+
+
+ + +
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/admin/client_prices.php b/admin/client_prices.php new file mode 100644 index 0000000..f23dd88 --- /dev/null +++ b/admin/client_prices.php @@ -0,0 +1,144 @@ +prepare("SELECT COUNT(*) FROM client_prices WHERE client_id = :client_id AND product_id = :product_id"); + $stmt->execute(['client_id' => $clientId, 'product_id' => $productId]); + $exists = $stmt->fetchColumn() > 0; + + if ($exists) { + $stmt = $pdo->prepare("UPDATE client_prices SET price = :price WHERE client_id = :client_id AND product_id = :product_id"); + $stmt->execute(['price' => $price, 'client_id' => $clientId, 'product_id' => $productId]); + $message = '
Cena została zaktualizowana.
'; + } else { + $stmt = $pdo->prepare("INSERT INTO client_prices (client_id, product_id, price) VALUES (:client_id, :product_id, :price)"); + $stmt->execute(['client_id' => $clientId, 'product_id' => $productId, 'price' => $price]); + $message = '
Nowa cena została dodana.
'; + } + } else { + $message = '
Wszystkie pola są wymagane.
'; + } +} + +// Fetch data for display +$clients = $pdo->query("SELECT id, name FROM clients ORDER BY name")->fetchAll(PDO::FETCH_ASSOC); +$products = $pdo->query("SELECT id, name FROM products ORDER BY name")->fetchAll(PDO::FETCH_ASSOC); + +// Fetch existing prices +$pricesStmt = $pdo->query(" + SELECT + cp.client_id, + cp.product_id, + cp.price, + c.name as client_name, + p.name as product_name + FROM client_prices cp + JOIN clients c ON cp.client_id = c.id + JOIN products p ON cp.product_id = p.id + ORDER BY c.name, p.name +"); +$existingPrices = $pricesStmt->fetchAll(PDO::FETCH_ASSOC); + +?> + + + + + + Cennik indywidualny + + + + + + +
+
+
+

Cennik indywidualny

+
+
+ + +
+
+

Dodaj/Edytuj cenę

+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+ +

Istniejące ceny indywidualne

+
+ + + + + + + + + + + + + + + + + + + + + + + +
KlientProduktCena (PLN)
Brak zdefiniowanych cen indywidualnych.
+
+
+
+
+ + \ No newline at end of file diff --git a/admin/clients.php b/admin/clients.php new file mode 100644 index 0000000..1ca8c09 --- /dev/null +++ b/admin/clients.php @@ -0,0 +1,106 @@ +query('SELECT * FROM clients ORDER BY name ASC'); + $clients = $stmt_clients->fetchAll(); +} catch (PDOException $e) { + error_log('DB Error in admin/clients.php: ' . $e->getMessage()); + $error_message = 'Błąd podczas pobierania klientów.'; +} + +$pageTitle = 'Klienci'; +?> + + + + + + <?php echo $pageTitle; ?> - Panel Administracyjny + + + + + +
+
+
+ +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDNazwaNIPMiastoPrzypisani użytkownicyAkcje
Nie znaleziono klientów.
+ prepare("SELECT email FROM users WHERE client_id = :client_id"); + $stmtUsers->execute(['client_id' => $client['id']]); + $assignedUsers = $stmtUsers->fetchAll(PDO::FETCH_COLUMN); + if ($assignedUsers) { + $assignedUsersLabel = implode(', ', array_map('htmlspecialchars', $assignedUsers)); + } + } catch (PDOException $e) { + error_log('DB Error in admin/clients.php for client ID ' . $client['id'] . ': ' . $e->getMessage()); + $assignedUsersLabel = 'Błąd ładowania'; + } + echo $assignedUsersLabel; + ?> + + Edytuj + Cennik +
+
+ +
+
+
+ + + + diff --git a/admin/edit_client.php b/admin/edit_client.php new file mode 100644 index 0000000..6d39124 --- /dev/null +++ b/admin/edit_client.php @@ -0,0 +1,152 @@ + '', + 'nip' => '', + 'street' => '', + 'city' => '', + 'postal_code' => '', + 'credit_limit' => 0, +]; +$isNewClient = !$clientId; +$pageTitle = $isNewClient ? 'Dodaj nowego klienta' : 'Edytuj klienta'; +$errorMessage = ''; +$successMessage = ''; + +if ($clientId) { + $stmt = $db->prepare("SELECT * FROM clients WHERE id = :id"); + $stmt->execute(['id' => $clientId]); + $client = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$client) { + die("Klient nie został znaleziony."); + } + + // Calculate used credit + $used_credit = $client['credit_limit'] - $client['credit_balance']; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $name = $_POST['name'] ?? ''; + $tax_id = $_POST['tax_id'] ?? ''; + $address = $_POST['address'] ?? ''; + $city = $_POST['city'] ?? ''; + $zip_code = $_POST['zip_code'] ?? ''; + $credit_limit = $_POST['credit_limit'] ?? 0; + + if (empty($name)) { + $errorMessage = 'Nazwa klienta jest wymagana.'; + } else { + try { + if ($isNewClient) { + $stmt = $db->prepare("INSERT INTO clients (name, nip, street, city, postal_code, credit_limit) VALUES (:name, :tax_id, :address, :city, :zip_code, :credit_limit)"); + } else { + $stmt = $db->prepare("UPDATE clients SET name = :name, nip = :tax_id, street = :address, city = :city, postal_code = :zip_code, credit_limit = :credit_limit WHERE id = :id"); + } + + $params = [ + 'name' => $name, + 'tax_id' => $tax_id, + 'address' => $address, + 'city' => $city, + 'zip_code' => $zip_code, + 'credit_limit' => $credit_limit + ]; + + if (!$isNewClient) { + $params['id'] = $clientId; + } + + $stmt->execute($params); + + if ($isNewClient) { + $clientId = $db->lastInsertId(); + header('Location: clients.php?status=created'); + exit; + } + $successMessage = 'Dane klienta zostały zaktualizowane.'; + // Re-fetch data to display updated values + $stmt = $db->prepare("SELECT * FROM clients WHERE id = :id"); + $stmt->execute(['id' => $clientId]); + $client = $stmt->fetch(PDO::FETCH_ASSOC); + + } catch (PDOException $e) { + $errorMessage = 'Wystąpił błąd podczas zapisywania danych klienta.'; + // error_log($e->getMessage()); // Uncomment for debugging + } + } +} +?> + + + + + + <?php echo htmlspecialchars($pageTitle); ?> + + + +
+
+
+

+
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +

PLN

+
+
+ +

+ PLN +

+
+ + + Anuluj +
+
+
+
+ + + diff --git a/admin/edit_product.php b/admin/edit_product.php new file mode 100644 index 0000000..b2c1e37 --- /dev/null +++ b/admin/edit_product.php @@ -0,0 +1,462 @@ +prepare("SELECT file_path FROM product_images WHERE id = ? AND product_id = ?"); + $img_stmt->execute([$image_id_to_delete, $product_id_for_redirect]); + $image_to_delete = $img_stmt->fetch(PDO::FETCH_ASSOC); + + if ($image_to_delete) { + $file_path = __DIR__ . '/../uploads/products/' . $image_to_delete['file_path']; + if (file_exists($file_path)) { + unlink($file_path); + } + + $delete_stmt = $pdo->prepare("DELETE FROM product_images WHERE id = ?"); + $delete_stmt->execute([$image_id_to_delete]); + } + + header('Location: edit_product.php?id=' . $product_id_for_redirect); + exit; +} + +// Handle document deletion +if (isset($_GET['delete_document']) && isset($_GET['id'])) { + $doc_id_to_delete = $_GET['delete_document']; + $product_id_for_redirect = $_GET['id']; + + $doc_stmt = $pdo->prepare("SELECT file_path FROM product_documents WHERE id = ? AND product_id = ?"); + $doc_stmt->execute([$doc_id_to_delete, $product_id_for_redirect]); + $doc_to_delete = $doc_stmt->fetch(PDO::FETCH_ASSOC); + + if ($doc_to_delete) { + $file_path = __DIR__ . '/../uploads/documents/' . $doc_to_delete['file_path']; + if (file_exists($file_path)) { + unlink($file_path); + } + + $delete_stmt = $pdo->prepare("DELETE FROM product_documents WHERE id = ?"); + $delete_stmt->execute([$doc_id_to_delete]); + } + + header('Location: edit_product.php?id=' . $product_id_for_redirect); + exit; +} + + +$product = [ + 'id' => null, + 'name' => '', + 'description' => '', + 'price_net' => '', + 'price_gross' => '', + 'supplier_id' => null, + 'is_active' => 1, + 'product_role' => 'membrana', + 'unit' => 'szt', + 'units_per_pallet' => null +]; +$errors = []; + +// Fetch suppliers +$stmt = $pdo->prepare("SELECT id, email FROM users WHERE role = 'supplier' AND is_active = 1 ORDER BY email"); +$stmt->execute(); +$suppliers = $stmt->fetchAll(PDO::FETCH_ASSOC); + +// Fetch all attribute keys +$keys_stmt = $pdo->query("SELECT * FROM attribute_keys ORDER BY name"); +$attribute_keys = $keys_stmt->fetchAll(PDO::FETCH_ASSOC); + +// Fetch product's current attributes and images +$product_attributes = []; +$product_images = []; +if (isset($_GET['id'])) { + $product_id = $_GET['id']; + $attr_stmt = $pdo->prepare("SELECT attribute_key_id, value FROM product_attributes WHERE product_id = ?"); + $attr_stmt->execute([$product_id]); + $product_attributes_raw = $attr_stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($product_attributes_raw as $attr) { + $product_attributes[$attr['attribute_key_id']] = $attr['value']; + } + + $img_stmt = $pdo->prepare("SELECT * FROM product_images WHERE product_id = ? ORDER BY is_primary DESC, id ASC"); + $img_stmt->execute([$product_id]); + $product_images = $img_stmt->fetchAll(PDO::FETCH_ASSOC); + + // Fetch all products for related products selection + $all_products_stmt = $pdo->query("SELECT id, name FROM products ORDER BY name"); + $all_products = $all_products_stmt->fetchAll(PDO::FETCH_ASSOC); + + // Fetch current related products + $related_products_stmt = $pdo->prepare("SELECT related_product_id FROM product_relations WHERE product_id = ?"); + $related_products_stmt->execute([$product_id]); + $related_product_ids = $related_products_stmt->fetchAll(PDO::FETCH_COLUMN); + + // Fetch product documents + $docs_stmt = $pdo->prepare("SELECT * FROM product_documents WHERE product_id = ?"); + $docs_stmt->execute([$product_id]); + $product_documents = $docs_stmt->fetchAll(PDO::FETCH_ASSOC); + + +} + +if (isset($_GET['id'])) { + $stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?"); + $stmt->execute([$_GET['id']]); + $product = $stmt->fetch(); + if (!$product) { + die('Nie znaleziono produktu'); + } +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $name = $_POST['name'] ?? ''; + $description = $_POST['description'] ?? ''; + $price_net = !empty($_POST['price_net']) ? (float)$_POST['price_net'] : null; + $price_gross = !empty($_POST['price_gross']) ? (float)$_POST['price_gross'] : null; + $unit = $_POST['unit'] ?? 'szt'; + $units_per_pallet = !empty($_POST['units_per_pallet']) ? $_POST['units_per_pallet'] : null; + $supplier_id = !empty($_POST['supplier_id']) ? $_POST['supplier_id'] : null; + $attributes = $_POST['attributes'] ?? []; + $is_active = isset($_POST['is_active']) ? 1 : 0; + $product_role = $_POST['product_role'] ?? 'membrana'; + $id = $_POST['id'] ?? null; + + // Auto-calculate prices + if ($price_net !== null && $price_gross === null) { + $price_gross = round($price_net * 1.23, 2); + } elseif ($price_gross !== null && $price_net === null) { + $price_net = round($price_gross / 1.23, 2); + } + + if ($supplier_id) { + $stmt = $pdo->prepare("SELECT COUNT(*) FROM users WHERE id = ? AND role = 'supplier' AND is_active = 1"); + $stmt->execute([$supplier_id]); + if ($stmt->fetchColumn() == 0) { + $errors[] = 'Wybrany dostawca jest nieprawidłowy.'; + } + } + + if (empty($errors)) { + try { + $pdo->beginTransaction(); + + if ($id) { // Update + $stmt = $pdo->prepare("UPDATE products SET name=?, description=?, price_net=?, price_gross=?, unit=?, units_per_pallet=?, supplier_id=?, is_active=?, product_role=? WHERE id=?"); + $stmt->execute([$name, $description, $price_net, $price_gross, $unit, $units_per_pallet, $supplier_id, $is_active, $product_role, $id]); + $product_id = $id; + } else { // Insert + $stmt = $pdo->prepare("INSERT INTO products (name, description, price_net, price_gross, unit, units_per_pallet, supplier_id, is_active, product_role) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$name, $description, $price_net, $price_gross, $unit, $units_per_pallet, $supplier_id, $is_active, $product_role]); + $product_id = $pdo->lastInsertId(); + } + + // Handle image uploads + if (isset($_FILES['images']) && !empty($_FILES['images']['name'][0])) { + $image_errors = []; + $allowed_types = ['image/jpeg', 'image/png']; + $upload_dir = __DIR__ . '/../uploads/products/'; + + if (!is_dir($upload_dir)) { + mkdir($upload_dir, 0777, true); + } + + foreach ($_FILES['images']['tmp_name'] as $key => $tmp_name) { + if ($_FILES['images']['error'][$key] === UPLOAD_ERR_OK) { + $file_type = mime_content_type($tmp_name); + if (in_array($file_type, $allowed_types)) { + $product_upload_dir = $upload_dir . $product_id . '/'; + if (!is_dir($product_upload_dir)) { + mkdir($product_upload_dir, 0777, true); + } + $file_ext = pathinfo($_FILES['images']['name'][$key], PATHINFO_EXTENSION); + $file_name = uniqid('prod_' . $product_id . '_', true) . '.' . $file_ext; + $destination = $product_upload_dir . $file_name; + + // GEMINI DEBUG + file_put_contents('/tmp/gemini_debug.log', "Destination: {$destination}\nFile Path for DB: {$product_id}/{$file_name}\n", FILE_APPEND); + + if (move_uploaded_file($tmp_name, $destination)) { + $img_stmt = $pdo->prepare("INSERT INTO product_images (product_id, file_path) VALUES (?, ?)"); + $img_stmt->execute([$product_id, $product_id . '/' . $file_name]); + } else { + $image_errors[] = "Nie udało się przenieść pliku: " . htmlspecialchars($_FILES['images']['name'][$key]); + } + } else { + $image_errors[] = "Niedozwolony typ pliku: " . htmlspecialchars($_FILES['images']['name'][$key]); + } + } elseif ($_FILES['images']['error'][$key] !== UPLOAD_ERR_NO_FILE) { + $image_errors[] = "Błąd podczas przesyłania pliku: " . htmlspecialchars($_FILES['images']['name'][$key]) . ": " . upload_error_message($_FILES['images']['error'][$key]); + } + } + // Store image errors in session to display after redirect if needed, or handle differently + if(!empty($image_errors)) { + // For simplicity, we add them to the main errors array. + $errors = array_merge($errors, $image_errors); + if ($pdo->inTransaction()) $pdo->rollBack(); + // Stop further execution if image upload fails + goto end_of_post_handling; + } + } + + // Handle document uploads + if (isset($_FILES['documents']) && !empty($_FILES['documents']['name'][0])) { + $doc_errors = []; + $allowed_doc_types = ['application/pdf']; + $doc_upload_dir = __DIR__ . '/../uploads/documents/' . $product_id . '/'; + + if (!is_dir($doc_upload_dir)) { + mkdir($doc_upload_dir, 0777, true); + } + + foreach ($_FILES['documents']['tmp_name'] as $key => $tmp_name) { + if ($_FILES['documents']['error'][$key] === UPLOAD_ERR_OK) { + $file_type = mime_content_type($tmp_name); + if (in_array($file_type, $allowed_doc_types)) { + $original_file_name = basename($_FILES['documents']['name'][$key]); + $sanitized_file_name = sanitize_filename($original_file_name); + $destination = $doc_upload_dir . $sanitized_file_name; + + if (move_uploaded_file($tmp_name, $destination)) { + $doc_stmt = $pdo->prepare("INSERT INTO product_documents (product_id, file_name, file_path) VALUES (?, ?, ?)"); + $doc_stmt->execute([$product_id, $original_file_name, $product_id . '/' . $sanitized_file_name]); + } else { + $doc_errors[] = "Nie udało się przenieść pliku: " . htmlspecialchars($original_file_name); + } + } else { + $doc_errors[] = "Niedozwolony typ pliku: " . htmlspecialchars($original_file_name); + } + } elseif ($_FILES['documents']['error'][$key] !== UPLOAD_ERR_NO_FILE) { + $doc_errors[] = "Błąd podczas przesyłania pliku: " . htmlspecialchars($_FILES['documents']['name'][$key]) . ": " . upload_error_message($_FILES['documents']['error'][$key]); + } + } + if(!empty($doc_errors)) { + $errors = array_merge($errors, $doc_errors); + if ($pdo->inTransaction()) $pdo->rollBack(); + goto end_of_post_handling; + } + } + + + + $clear_stmt = $pdo->prepare("DELETE FROM product_attributes WHERE product_id = ?"); + $clear_stmt->execute([$product_id]); + + $attr_sql = "INSERT INTO product_attributes (product_id, attribute_key_id, value) VALUES (?, ?, ?)"; + $attr_stmt = $pdo->prepare($attr_sql); + foreach ($attributes as $key_id => $value) { + if (!empty($value)) { + $attr_stmt->execute([$product_id, $key_id, $value]); + } + } + + // Handle related products + $related_products = $_POST['related_products'] ?? []; + $clear_related_stmt = $pdo->prepare("DELETE FROM product_relations WHERE product_id = ?"); + $clear_related_stmt->execute([$product_id]); + + if (!empty($related_products)) { + $rel_sql = "INSERT INTO product_relations (product_id, related_product_id) VALUES (?, ?)"; + $rel_stmt = $pdo->prepare($rel_sql); + foreach ($related_products as $related_id) { + $rel_stmt->execute([$product_id, $related_id]); + } + } + + + $pdo->commit(); + header("Location: products.php"); + exit; + + } catch (Exception $e) { + if ($pdo->inTransaction()) $pdo->rollBack(); + $errors[] = 'Błąd podczas zapisywania produktu: ' . $e->getMessage(); + } + } + end_of_post_handling: +} + +$page_title = $product['id'] ? 'Edytuj produkt' : 'Dodaj produkt'; +?> + + + + + + <?php echo htmlspecialchars($page_title); ?> - Panel Administracyjny + + + + +
+

+ + +
+

+
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ > + +
+
+ +
+
Zdjęcia
+
+ + +
+ '; + foreach($product_images as $image) { + echo '
'; + echo ''; + echo 'Usuń'; + echo '
'; + } + echo '
'; + endif; ?> + + +
+
Atrybuty
+
+ + +
+
+
Specyfikacja techniczna
+ +
+ + +
+ +
+ +
+
Produkty powiązane
+
+ + +
+
+ +
+
Dokumenty produktu (PDF)
+
+ + +
+ '; + foreach($product_documents as $doc) { + echo '
  • '; + echo htmlspecialchars($doc['file_name']); + echo 'Usuń'; + echo '
  • '; + } + echo ''; + endif; ?> +
    + +
    + + Anuluj +
    +
    +
    + + + + \ No newline at end of file diff --git a/admin/edit_user.php b/admin/edit_user.php new file mode 100644 index 0000000..197e78a --- /dev/null +++ b/admin/edit_user.php @@ -0,0 +1,185 @@ + '', + 'email' => '', + 'role' => 'client', + 'is_active' => 1, + 'client_id' => null +]; +$is_new_user = true; +$pageTitle = 'Dodaj użytkownika'; + +if (isset($_GET['id'])) { + $is_new_user = false; + $pageTitle = 'Edytuj użytkownika'; + try { + $stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?'); + $stmt->execute([$_GET['id']]); + $user = $stmt->fetch(); + if (!$user) { + die('Użytkownik nie znaleziony.'); + } + } catch (PDOException $e) { + die("Błąd bazy danych: " . $e->getMessage()); + } +} + +$errors = []; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $email = $_POST['email'] ?? ''; + $password = $_POST['password'] ?? ''; + $role = $_POST['role'] ?? 'client'; + $is_active = isset($_POST['is_active']) ? 1 : 0; + $client_id = ($role === 'client') ? ($_POST['client_id'] ?? null) : null; + + if (empty($email)) { + $errors[] = 'Email jest wymagany.'; + } + if ($is_new_user && empty($password)) { + $errors[] = 'Hasło jest wymagane dla nowych użytkowników.'; + } + + try { + $stmt = $pdo->prepare('SELECT id FROM users WHERE email = ? AND id != ?'); + $stmt->execute([$email, $user['id']]); + if ($stmt->fetch()) { + $errors[] = 'Adres email jest już w użyciu.'; + } + + if (!$is_new_user && $user['role'] === 'admin' && (!$is_active || $role !== 'admin')) { + $stmt = $pdo->query('SELECT COUNT(*) FROM users WHERE role = \'admin\' AND is_active = 1'); + $admin_count = $stmt->fetchColumn(); + $self_deactivation = $user['id'] === $_GET['id']; + if ($admin_count <= 1 && $self_deactivation) { + $errors[] = 'Nie można deaktywować lub zmienić roli ostatniego administratora.'; + } + } + + if ($role === 'client' && empty($client_id)) { + $errors[] = 'Firma klienta jest wymagana dla użytkownika typu klient.'; + } + + if (empty($errors)) { + $pdo->beginTransaction(); + if ($is_new_user) { + $password_hash = password_hash($password, PASSWORD_DEFAULT); + $stmt = $pdo->prepare('INSERT INTO users (email, password_hash, role, is_active, client_id) VALUES (?, ?, ?, ?, ?)'); + $stmt->execute([$email, $password_hash, $role, $is_active, $client_id]); + } else { + if (!empty($password)) { + $password_hash = password_hash($password, PASSWORD_DEFAULT); + $stmt = $pdo->prepare('UPDATE users SET email = ?, password_hash = ?, role = ?, is_active = ?, client_id = ? WHERE id = ?'); + $stmt->execute([$email, $password_hash, $role, $is_active, $client_id, $_GET['id']]); + } else { + $stmt = $pdo->prepare('UPDATE users SET email = ?, role = ?, is_active = ?, client_id = ? WHERE id = ?'); + $stmt->execute([$email, $role, $is_active, $client_id, $_GET['id']]); + } + } + $pdo->commit(); + header('Location: users.php'); + exit; + } + } catch (PDOException $e) { + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } + $errors[] = "Błąd bazy danych: " . $e->getMessage(); + } +} + +try { + $clients_stmt = $pdo->query('SELECT id, name FROM clients ORDER BY name'); + $clients = $clients_stmt->fetchAll(); +} catch (PDOException $e) { + die("Błąd bazy danych: " . $e->getMessage()); +} + +?> + + + + + + <?php echo $pageTitle; ?> + + + + + +
    +
    +
    +

    +
    +
    + +
    + +

    + +
    + + +
    +
    + + +
    +
    + + > + +
    Zostaw puste, aby zachować obecne hasło.
    + +
    +
    + + +
    +
    + + +
    +
    + + > + +
    + + Anuluj +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/admin/menu.php b/admin/menu.php new file mode 100644 index 0000000..8475243 --- /dev/null +++ b/admin/menu.php @@ -0,0 +1,36 @@ + + diff --git a/admin/order_details.php b/admin/order_details.php new file mode 100644 index 0000000..55d5ad4 --- /dev/null +++ b/admin/order_details.php @@ -0,0 +1,157 @@ + 'Oczekuje na płatność', + 'paid' => 'Opłacone', + 'in_progress' => 'W realizacji', + 'shipped' => 'Wysłane', + 'completed' => 'Zakończone', + 'cancelled' => 'Anulowane', + 'transfer' => 'Przelew bankowy', + 'credit' => 'Kredyt kupiecki', +]; + +function get_status_translation_local($status, $translations) { + return $translations[$status] ?? ucfirst(str_replace('_', ' ', $status)); +} + +function get_payment_method_translation_local($method, $translations) { + return $translations[$method] ?? ucfirst($method); +} + +if (!$order_id) { + die('Nie podano ID zamówienia'); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['status'])) { + $new_status = $_POST['status']; + $update_stmt = $pdo->prepare("UPDATE orders SET status = ? WHERE id = ?"); + $update_stmt->execute([$new_status, $order_id]); + + header("Location: order_details.php?id=$order_id"); + exit; +} + +$stmt = $pdo->prepare(" + SELECT o.*, c.name as client_company_name + FROM orders o + LEFT JOIN clients c ON o.client_id = c.id + WHERE o.id = ? +"); +$stmt->execute([$order_id]); +$order = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$order) { + die('Nie znaleziono zamówienia'); +} + +$items_stmt = $pdo->prepare(" + SELECT oi.*, p.name as product_name + FROM order_items oi + JOIN products p ON oi.product_id = p.id + WHERE oi.order_id = ? +"); +$items_stmt->execute([$order_id]); +$order_items = $items_stmt->fetchAll(PDO::FETCH_ASSOC); + +$statuses = ['pending_payment', 'paid', 'in_progress', 'shipped', 'completed', 'cancelled']; +$pageTitle = 'Szczegóły zamówienia #' . htmlspecialchars($order['id']); + +?> + + + + + + <?php echo $pageTitle; ?> - Panel Administracyjny + + + + + + +
    + +
    +

    + + Powrót do listy + +
    + +
    +
    +
    +
    Pozycje zamówienia
    +
    +
    + + + + + + + + + + + + + + + + + + + +
    ProduktIlośćCena jednostkowaSuma
    +
    +
    +
    +
    +
    +
    +
    Podsumowanie
    +
    +

    Klient:

    +

    Data:

    +

    Metoda płatności:

    +

    Suma:

    +
    +
    +
    +
    Status zamówienia
    +
    +
    +
    + + +
    +
    +
    + Aktualny status: + +
    +
    +
    +
    +
    +
    + + + diff --git a/admin/orders.php b/admin/orders.php new file mode 100644 index 0000000..4659330 --- /dev/null +++ b/admin/orders.php @@ -0,0 +1,160 @@ + 0, + 'new_week' => 0, + 'awaiting_payment' => 0, + 'in_progress' => 0, +]; + +try { + $pdo = db(); + + // Fetch all orders with customer information + $stmt = $pdo->query(" + SELECT + o.id, + c.name as client_company_name, + o.created_at, + o.status, + o.total_amount, + o.delivery_source + FROM orders o + LEFT JOIN clients c ON o.client_id = c.id + ORDER BY o.created_at DESC + "); + $orders = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // Fetch stats + $today_start = date('Y-m-d 00:00:00'); + $week_start = date('Y-m-d 00:00:00', strtotime('-7 days')); + + $new_today_stmt = $pdo->prepare("SELECT COUNT(*) FROM orders WHERE created_at >= ?"); + $new_today_stmt->execute([$today_start]); + $stats['new_today'] = $new_today_stmt->fetchColumn(); + + $new_week_stmt = $pdo->prepare("SELECT COUNT(*) FROM orders WHERE created_at >= ?"); + $new_week_stmt->execute([$week_start]); + $stats['new_week'] = $new_week_stmt->fetchColumn(); + + $awaiting_payment_stmt = $pdo->query("SELECT COUNT(*) FROM orders WHERE status = 'pending_payment'"); + $stats['awaiting_payment'] = $awaiting_payment_stmt->fetchColumn(); + + $in_progress_stmt = $pdo->query("SELECT COUNT(*) FROM orders WHERE status = 'in_progress'"); + $stats['in_progress'] = $in_progress_stmt->fetchColumn(); + +} catch (PDOException $e) { + $error = "Błąd bazy danych: " . $e->getMessage(); +} + +$pageTitle = "Zarządzanie zamówieniami"; + +?> + + + + + + <?= $pageTitle ?> + + + + + +
    +

    + + +
    + + +
    +
    +
    +
    +
    Nowe (dziś)
    +

    +
    +
    +
    +
    +
    +
    +
    Nowe (tydzień)
    +

    +
    +
    +
    +
    +
    +
    +
    Do zapłaty
    +

    +
    +
    +
    +
    +
    +
    +
    W realizacji
    +

    +
    +
    +
    +
    + + +
    +
    + Wszystkie zamówienia +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IDKlientDataStatusŹródłoSumaAkcje
    Brak zamówień do wyświetlenia.
    # + Szczegóły +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/admin/products.php b/admin/products.php new file mode 100644 index 0000000..bdf3578 --- /dev/null +++ b/admin/products.php @@ -0,0 +1,91 @@ +query("SELECT * FROM products ORDER BY CASE product_role WHEN 'membrana' THEN 1 WHEN 'akcesoria' THEN 2 ELSE 3 END, created_at DESC"); +$products = $stmt->fetchAll(); + +$page_title = 'Zarządzanie produktami'; +?> + + + + + + <?php echo htmlspecialchars($page_title); ?> + + + + + + + + +
    +
    +

    + + Dodaj produkt + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IDNazwaTypCena nettoCena bruttoAktywnyAkcje
    Nie znaleziono produktów.
    + + Tak + + Nie + + + + Edytuj + + + + +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/admin/toggle_active.php b/admin/toggle_active.php new file mode 100644 index 0000000..fa0f412 --- /dev/null +++ b/admin/toggle_active.php @@ -0,0 +1,31 @@ +prepare("SELECT is_active FROM products WHERE id = ?"); +$stmt->execute([$id]); +$current_status = $stmt->fetchColumn(); + +if ($current_status === false) { + die("Produkt nie znaleziony."); +} + +// Flip the status +$new_status = $current_status ? 0 : 1; + +$update_stmt = $pdo->prepare("UPDATE products SET is_active = ? WHERE id = ?"); +$update_stmt->execute([$new_status, $id]); + +header("Location: products.php"); +exit; diff --git a/admin/users.php b/admin/users.php new file mode 100644 index 0000000..66d1671 --- /dev/null +++ b/admin/users.php @@ -0,0 +1,84 @@ +query('SELECT * FROM users ORDER BY created_at DESC'); +$users = $stmt->fetchAll(); + +$page_title = 'Użytkownicy'; +?> + + + + + + <?php echo htmlspecialchars($page_title); ?> - Panel Administracyjny + + + + + + + +
    +
    +

    + + Dodaj użytkownika + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IDEmailRolaAktywnyData utworzeniaAkcje
    Nie znaleziono użytkowników.
    + + Tak + + Nie + + + + Edytuj + +
    +
    +
    +
    + + + + diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..63e084a --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,35 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + background-color: #F8F9FA; +} + +.product-card { + transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; + border-radius: 0.5rem; + border: 1px solid #dee2e6; +} + +.product-card:hover { + transform: translateY(-5px); + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); +} + +.card-img-top { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; + aspect-ratio: 1 / 1; + object-fit: cover; + padding: 1rem; +} + +.btn-primary { + background-color: #1E4A7B; + border-color: #1E4A7B; +} + +.btn-primary:hover, .btn-primary:focus, .btn-primary:active { + background-color: #15355a; + border-color: #15355a; +} diff --git a/assets/pasted-20251209-065617-6bf1b4e6.png b/assets/pasted-20251209-065617-6bf1b4e6.png new file mode 100644 index 0000000..4afb5d0 Binary files /dev/null and b/assets/pasted-20251209-065617-6bf1b4e6.png differ diff --git a/assets/pasted-20251212-131131-f6f18157.jpg b/assets/pasted-20251212-131131-f6f18157.jpg new file mode 100644 index 0000000..7a83af4 Binary files /dev/null and b/assets/pasted-20251212-131131-f6f18157.jpg differ diff --git a/assets/pasted-20251212-131440-62c0087c.jpg b/assets/pasted-20251212-131440-62c0087c.jpg new file mode 100644 index 0000000..e570828 Binary files /dev/null and b/assets/pasted-20251212-131440-62c0087c.jpg differ diff --git a/cart.php b/cart.php new file mode 100644 index 0000000..6f55f12 --- /dev/null +++ b/cart.php @@ -0,0 +1,184 @@ +prepare($sql); + $stmt->execute($params); + $products = $stmt->fetchAll(PDO::FETCH_ASSOC); + + foreach ($products as $product) { + $quantity = $cart[$product['id']]; + $line_total = $product['final_price'] * $quantity; + $cart_products[] = [ + 'id' => $product['id'], + 'name' => $product['name'], + 'price' => $product['final_price'], + 'quantity' => $quantity, + 'line_total' => $line_total, + ]; + $total_price += $line_total; + } + } catch (PDOException $e) { + die("Błąd połączenia z bazą danych: " . $e->getMessage()); + } +} + +$page_title = 'Koszyk'; +$user_role = get_user_role(); + +?> + + + + + + <?php echo htmlspecialchars($page_title); ?> - B2B Commerce + + + + + + + +
    +

    Koszyk

    + + + + Wróć do sklepu + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ProduktCenaIlośćRazem
    +
    + + + + + + +
    +
    +
    + + + + +
    +
    Razem:
    + +
    + Wróć do sklepu + Przejdź do zamówienia +
    + +
    + + + + + \ No newline at end of file diff --git a/cart_actions.php b/cart_actions.php new file mode 100644 index 0000000..3a5ad37 --- /dev/null +++ b/cart_actions.php @@ -0,0 +1,63 @@ + 0 && $quantity > 0) { + // If product is already in cart, update quantity + if (isset($_SESSION['cart'][$product_id])) { + $_SESSION['cart'][$product_id] += $quantity; + } else { + $_SESSION['cart'][$product_id] = $quantity; + } + } + break; + + case 'update': + $product_id = isset($_POST['product_id']) ? (int)$_POST['product_id'] : 0; + $quantity = isset($_POST['quantity']) ? (int)$_POST['quantity'] : 0; + + if ($product_id > 0) { + if ($quantity > 0) { + $_SESSION['cart'][$product_id] = $quantity; + } else { + // Remove item if quantity is 0 or less + unset($_SESSION['cart'][$product_id]); + } + } + break; + + case 'remove': + $product_id = isset($_POST['product_id']) ? (int)$_POST['product_id'] : 0; + if ($product_id > 0) { + unset($_SESSION['cart'][$product_id]); + } + break; + } + + if ($action === 'add' && isset($product_id)) { + header('Location: related_suggestions.php?product_id=' . $product_id . '&qty=' . $quantity); + } else { + // Redirect back to the appropriate page + $redirect_url = $_POST['redirect_to'] ?? 'index.php'; + header('Location: ' . $redirect_url); + } + exit; +} diff --git a/checkout.php b/checkout.php new file mode 100644 index 0000000..98a8ac1 --- /dev/null +++ b/checkout.php @@ -0,0 +1,204 @@ +prepare('SELECT credit_limit, credit_balance, credit_enabled FROM clients WHERE id = ?'); + $stmt->execute([$client_id]); + $credit_info = $stmt->fetch(PDO::FETCH_ASSOC); + } + + $sql = "SELECT p.id, p.name, p.units_per_pallet, COALESCE(cp.price, p.price_gross) as price FROM products p LEFT JOIN client_prices cp ON p.id = cp.product_id AND cp.client_id = ? WHERE p.id IN ($placeholders)"; + $stmt = $pdo->prepare($sql); + $params = array_merge([$client_id], $product_ids); + $stmt->execute($params); + $products = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $is_supplier_delivery = false; + foreach ($products as $product) { + $quantity = $cart[$product['id']]; + $line_total = $product['price'] * $quantity; + $cart_products[] = [ + 'id' => $product['id'], + 'name' => $product['name'], + 'price' => $product['price'], + 'quantity' => $quantity, + 'line_total' => $line_total, + ]; + $total_price += $line_total; + + // Check for full pallets only if units_per_pallet is set and positive + if (isset($product['units_per_pallet']) && $product['units_per_pallet'] > 0) { + if ($quantity >= $product['units_per_pallet']) { + $is_supplier_delivery = true; + } + } + } + + $delivery_source = $is_supplier_delivery ? 'supplier' : 'cs'; + + } catch (PDOException $e) { + die('Błąd połączenia z bazą danych: ' . $e->getMessage()); + } +} + +$page_title = 'Podsumowanie zamówienia'; +$user_role = get_user_role(); + +?> + + + + + + <?php echo htmlspecialchars($page_title); ?> - ExtraB2B + + + + + + + +
    +

    Podsumowanie zamówienia

    + +
    +
    +
    +
    Twoje zamówienie
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    ProduktIlośćCena jedn.Suma
    Suma:
    +
    +
    +
    +
    +
    +
    Opcje dostawy i płatności
    +
    +
    + Źródło dostawy: +
    + +
    + Dostępny kredyt kupiecki: +
    + +
    +
    + + +
    +
    + + +
    + + +
    +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/data/products.php b/data/products.php new file mode 100644 index 0000000..4257415 --- /dev/null +++ b/data/products.php @@ -0,0 +1,39 @@ + 1, + "name" => "Widget przemysłowy", + "description" => "Wysokiej jakości widget przeznaczony do zastosowań przemysłowych o dużej wytrzymałości.", + "image" => "https://picsum.photos/seed/p1/500/500" + ], + [ + "id" => 2, + "name" => "Zestaw precyzyjnych kół zębatych", + "description" => "Zestaw precyzyjnie wykonanych kół zębatych do robotyki i maszyn zautomatyzowanych.", + "image" => "https://picsum.photos/seed/p2/500/500" + ], + [ + "id" => 3, + "name" => "Wzmacniany panel z włókna węglowego", + "description" => "Lekki i niezwykle wytrzymały panel z włókna węglowego, odpowiedni dla przemysłu lotniczego i motoryzacyjnego.", + "image" => "https://picsum.photos/seed/p3/500/500" + ], + [ + "id" => 4, + "name" => "Zautomatyzowany przenośnik taśmowy", + "description" => "Zautomatyzowany system przenośników taśmowych usprawniający logistykę magazynową.", + "image" => "https://picsum.photos/seed/p4/500/500" + ], + [ + "id" => 5, + "name" => "Agregat hydrauliczny", + "description" => "Kompaktowy i wydajny agregat hydrauliczny do różnych potrzeb przemysłowych.", + "image" => "https://picsum.photos/seed/p5/500/500" + ], + [ + "id" => 6, + "name" => "Zestaw czujników bezpieczeństwa", + "description" => "Zestaw wielu czujników zapewniający bezpieczeństwo operacyjne w pobliżu ciężkich maszyn.", + "image" => "https://picsum.photos/seed/p6/500/500" + ] +]; diff --git a/db/migrate.php b/db/migrate.php new file mode 100644 index 0000000..87e38a9 --- /dev/null +++ b/db/migrate.php @@ -0,0 +1,62 @@ +exec(" + CREATE TABLE IF NOT EXISTS schema_migrations ( + version VARCHAR(255) NOT NULL PRIMARY KEY, + applied_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + "); + + // 2. Get all migrations that have already been run + $applied_migrations = $pdo->query("SELECT version FROM schema_migrations")->fetchAll(PDO::FETCH_COLUMN); + +} catch (PDOException $e) { + die("A database error occurred during setup: " . $e->getMessage()); +} + +// 3. Get all migration files on disk +$migrationsDir = __DIR__ . '/migrations'; +$all_files = glob($migrationsDir . '/*.sql'); +sort($all_files); + +// 4. Determine and run new migrations +foreach ($all_files as $file) { + $filename = basename($file); + + if (!in_array($filename, $applied_migrations)) { + echo "Applying migration: $filename\n"; + + try { + $pdo->beginTransaction(); + + $sql = file_get_contents($file); + $pdo->exec($sql); + + // Record the migration + $stmt = $pdo->prepare("INSERT INTO schema_migrations (version) VALUES (?)"); + $stmt->execute([$filename]); + + $pdo->commit(); + echo " Success.\n"; + + } catch (PDOException $e) { + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } + echo " Error applying migration $filename: " . $e->getMessage() . "\n"; + // Stop on first error + break; + } + } else { + echo "Skipping already applied migration: $filename\n"; + } +} + +echo "Migrations completed.\n"; + diff --git a/db/migrations/001_create_products_table.sql b/db/migrations/001_create_products_table.sql new file mode 100644 index 0000000..6288551 --- /dev/null +++ b/db/migrations/001_create_products_table.sql @@ -0,0 +1,13 @@ + +CREATE TABLE IF NOT EXISTS `products` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `description` TEXT, + `price` DECIMAL(10, 2) NOT NULL, + `image_url` VARCHAR(255), + `is_active` BOOLEAN NOT NULL DEFAULT TRUE, + `supplier_ref` VARCHAR(100), + `tech_params` JSON, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); diff --git a/db/migrations/002_create_users_table.sql b/db/migrations/002_create_users_table.sql new file mode 100644 index 0000000..ff9a87a --- /dev/null +++ b/db/migrations/002_create_users_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS `users` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `email` VARCHAR(255) NOT NULL UNIQUE, + `password_hash` VARCHAR(255) NOT NULL, + `role` ENUM('admin', 'finance', 'support', 'client', 'supplier') NOT NULL, + `is_active` BOOLEAN NOT NULL DEFAULT TRUE, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); diff --git a/db/migrations/003_seed_admin_user.sql b/db/migrations/003_seed_admin_user.sql new file mode 100644 index 0000000..ec3ef02 --- /dev/null +++ b/db/migrations/003_seed_admin_user.sql @@ -0,0 +1,7 @@ +INSERT INTO `users` (`email`, `password_hash`, `role`, `is_active`) VALUES ( + 'admin@example.com', + -- This is a hash of the string 'password' using PASSWORD_BCRYPT + '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', + 'admin', + 1 +); diff --git a/db/migrations/004_create_orders_table.sql b/db/migrations/004_create_orders_table.sql new file mode 100644 index 0000000..8edd03e --- /dev/null +++ b/db/migrations/004_create_orders_table.sql @@ -0,0 +1,12 @@ +-- Create orders table +CREATE TABLE IF NOT EXISTS orders ( + id INT AUTO_INCREMENT PRIMARY KEY, + client_id INT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + status VARCHAR(50) NOT NULL DEFAULT 'pending_payment', + total_amount DECIMAL(10, 2) NOT NULL, + payment_method VARCHAR(50) NOT NULL, + delivery_source VARCHAR(50) NOT NULL, + notes TEXT, + FOREIGN KEY (client_id) REFERENCES users(id) +); diff --git a/db/migrations/005_create_order_items_table.sql b/db/migrations/005_create_order_items_table.sql new file mode 100644 index 0000000..8528007 --- /dev/null +++ b/db/migrations/005_create_order_items_table.sql @@ -0,0 +1,13 @@ +-- Create order_items table +CREATE TABLE IF NOT EXISTS order_items ( + id INT AUTO_INCREMENT PRIMARY KEY, + order_id INT NOT NULL, + product_id INT NOT NULL, + quantity INT NOT NULL, + unit_price DECIMAL(10, 2) NOT NULL, + line_total DECIMAL(10, 2) NOT NULL, + supplier_id INT, + delivery_source VARCHAR(50), + FOREIGN KEY (order_id) REFERENCES orders(id), + FOREIGN KEY (product_id) REFERENCES products(id) +); diff --git a/db/migrations/006_add_updated_at_to_users.sql b/db/migrations/006_add_updated_at_to_users.sql new file mode 100644 index 0000000..83469cf --- /dev/null +++ b/db/migrations/006_add_updated_at_to_users.sql @@ -0,0 +1 @@ +ALTER TABLE `users` ADD COLUMN `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; \ No newline at end of file diff --git a/db/migrations/007_add_supplier_id_to_products.sql b/db/migrations/007_add_supplier_id_to_products.sql new file mode 100644 index 0000000..188b97b --- /dev/null +++ b/db/migrations/007_add_supplier_id_to_products.sql @@ -0,0 +1 @@ +ALTER TABLE products ADD COLUMN supplier_id INT NULL; \ No newline at end of file diff --git a/db/migrations/008_add_item_status_to_order_items.sql b/db/migrations/008_add_item_status_to_order_items.sql new file mode 100644 index 0000000..c44cf97 --- /dev/null +++ b/db/migrations/008_add_item_status_to_order_items.sql @@ -0,0 +1 @@ +ALTER TABLE order_items ADD COLUMN item_status VARCHAR(50) NOT NULL DEFAULT 'pending'; \ No newline at end of file diff --git a/db/migrations/009_create_clients_table.sql b/db/migrations/009_create_clients_table.sql new file mode 100644 index 0000000..4f1c578 --- /dev/null +++ b/db/migrations/009_create_clients_table.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS clients ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + nip VARCHAR(255) NULL, + regon VARCHAR(255) NULL, + krs VARCHAR(255) NULL, + street VARCHAR(255) NULL, + city VARCHAR(255) NULL, + postal_code VARCHAR(255) NULL, + country VARCHAR(255) NULL, + phone VARCHAR(255) NULL, + email VARCHAR(255) NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); diff --git a/db/migrations/010_add_client_id_to_users.sql b/db/migrations/010_add_client_id_to_users.sql new file mode 100644 index 0000000..32bda61 --- /dev/null +++ b/db/migrations/010_add_client_id_to_users.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN client_id INT NULL, ADD FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE SET NULL; \ No newline at end of file diff --git a/db/migrations/011_create_client_prices_table.sql b/db/migrations/011_create_client_prices_table.sql new file mode 100644 index 0000000..b1bc6fa --- /dev/null +++ b/db/migrations/011_create_client_prices_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS client_prices ( + client_id INT NOT NULL, + product_id INT NOT NULL, + price DECIMAL(10, 2) NOT NULL, + PRIMARY KEY (client_id, product_id), + FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE +); diff --git a/db/migrations/012_add_trade_credit_to_clients.sql b/db/migrations/012_add_trade_credit_to_clients.sql new file mode 100644 index 0000000..1aad391 --- /dev/null +++ b/db/migrations/012_add_trade_credit_to_clients.sql @@ -0,0 +1,4 @@ +ALTER TABLE clients +ADD COLUMN credit_limit DECIMAL(10,2) NOT NULL DEFAULT 0, +ADD COLUMN credit_balance DECIMAL(10,2) NOT NULL DEFAULT 0, +ADD COLUMN credit_enabled TINYINT(1) NOT NULL DEFAULT 0; diff --git a/db/migrations/013_add_units_per_pallet_to_products.sql b/db/migrations/013_add_units_per_pallet_to_products.sql new file mode 100644 index 0000000..a270c0d --- /dev/null +++ b/db/migrations/013_add_units_per_pallet_to_products.sql @@ -0,0 +1 @@ +ALTER TABLE products ADD COLUMN units_per_pallet INT NOT NULL DEFAULT 1; \ No newline at end of file diff --git a/db/migrations/015_add_delivery_source_to_orders.sql b/db/migrations/015_add_delivery_source_to_orders.sql new file mode 100644 index 0000000..d8611a1 --- /dev/null +++ b/db/migrations/015_add_delivery_source_to_orders.sql @@ -0,0 +1 @@ +ALTER TABLE orders ADD COLUMN delivery_source VARCHAR(20) NOT NULL DEFAULT 'cs'; \ No newline at end of file diff --git a/db/migrations/016_seed_demo_data.sql b/db/migrations/016_seed_demo_data.sql new file mode 100644 index 0000000..62ffa0a --- /dev/null +++ b/db/migrations/016_seed_demo_data.sql @@ -0,0 +1,10 @@ +INSERT INTO `users` (`email`, `password_hash`, `role`, `is_active`, `client_id`) VALUES +('supplier@example.com', '$2y$10$Vty/qSlkVoNiqhDRZlSLg.KeIWDBAvShA9d/.0CpOfzyvx8oF0vKG', 'supplier', 1, NULL); + +-- Get the ID of the supplier we just created +SET @supplier_id = LAST_INSERT_ID(); + +INSERT INTO `products` (`name`, `description`, `price`, `image_url`, `is_active`, `supplier_id`, `units_per_pallet`) VALUES +('Produkt A', 'Opis produktu A', 100.00, 'https://via.placeholder.com/300', 1, @supplier_id, 10), +('Produkt B', 'Opis produktu B', 250.50, 'https://via.placeholder.com/300', 1, @supplier_id, 20), +('Produkt C', 'Opis produktu C', 50.00, 'https://via.placeholder.com/300', 1, @supplier_id, 50); diff --git a/db/migrations/017_create_attribute_keys_table.sql b/db/migrations/017_create_attribute_keys_table.sql new file mode 100644 index 0000000..d818d8d --- /dev/null +++ b/db/migrations/017_create_attribute_keys_table.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS attribute_keys ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL UNIQUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); diff --git a/db/migrations/018_create_product_attributes_table.sql b/db/migrations/018_create_product_attributes_table.sql new file mode 100644 index 0000000..8e72267 --- /dev/null +++ b/db/migrations/018_create_product_attributes_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS product_attributes ( + id INT AUTO_INCREMENT PRIMARY KEY, + product_id INT NOT NULL, + attribute_key_id INT NOT NULL, + value TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, + FOREIGN KEY (attribute_key_id) REFERENCES attribute_keys(id) ON DELETE CASCADE, + UNIQUE KEY `product_attribute` (product_id, attribute_key_id) +); diff --git a/db/migrations/019_create_product_images_table.sql b/db/migrations/019_create_product_images_table.sql new file mode 100644 index 0000000..7c3cc87 --- /dev/null +++ b/db/migrations/019_create_product_images_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS product_images ( + id INT AUTO_INCREMENT PRIMARY KEY, + product_id INT NOT NULL, + file_path VARCHAR(255) NOT NULL, + is_primary TINYINT(1) DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE +); diff --git a/db/migrations/020_remove_image_url_from_products.sql b/db/migrations/020_remove_image_url_from_products.sql new file mode 100644 index 0000000..589c91c --- /dev/null +++ b/db/migrations/020_remove_image_url_from_products.sql @@ -0,0 +1 @@ +ALTER TABLE products DROP COLUMN image_url; diff --git a/db/migrations/021_create_product_relations_table.sql b/db/migrations/021_create_product_relations_table.sql new file mode 100644 index 0000000..266f2d8 --- /dev/null +++ b/db/migrations/021_create_product_relations_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS product_relations ( + product_id INT NOT NULL, + related_product_id INT NOT NULL, + PRIMARY KEY (product_id, related_product_id), + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, + FOREIGN KEY (related_product_id) REFERENCES products(id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/db/migrations/022_create_product_documents_table.sql b/db/migrations/022_create_product_documents_table.sql new file mode 100644 index 0000000..aa1ed21 --- /dev/null +++ b/db/migrations/022_create_product_documents_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS product_documents ( + id INT AUTO_INCREMENT PRIMARY KEY, + product_id INT NOT NULL, + file_name VARCHAR(255) NOT NULL, + file_path VARCHAR(255) NOT NULL, + uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/db/migrations/023_add_product_role_to_products.sql b/db/migrations/023_add_product_role_to_products.sql new file mode 100644 index 0000000..ac0a1a7 --- /dev/null +++ b/db/migrations/023_add_product_role_to_products.sql @@ -0,0 +1 @@ +ALTER TABLE products ADD COLUMN product_role ENUM('membrana', 'akcesoria') NOT NULL DEFAULT 'membrana'; \ No newline at end of file diff --git a/db/migrations/024_add_unit_to_products.sql b/db/migrations/024_add_unit_to_products.sql new file mode 100644 index 0000000..8874c70 --- /dev/null +++ b/db/migrations/024_add_unit_to_products.sql @@ -0,0 +1 @@ +ALTER TABLE products ADD COLUMN unit VARCHAR(10) NOT NULL DEFAULT 'szt'; diff --git a/db/migrations/025_add_net_gross_prices_to_products.sql b/db/migrations/025_add_net_gross_prices_to_products.sql new file mode 100644 index 0000000..f1707be --- /dev/null +++ b/db/migrations/025_add_net_gross_prices_to_products.sql @@ -0,0 +1,3 @@ +ALTER TABLE products +ADD COLUMN price_net DECIMAL(12,2) NULL, +ADD COLUMN price_gross DECIMAL(12,2) NULL; \ No newline at end of file diff --git a/includes/auth.php b/includes/auth.php new file mode 100644 index 0000000..1859ac7 --- /dev/null +++ b/includes/auth.php @@ -0,0 +1,59 @@ +prepare('SELECT * FROM users WHERE email = ? AND is_active = 1'); + $stmt->execute([$email]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($user && password_verify($password, $user['password_hash'])) { + $_SESSION['user_id'] = $user['id']; + $_SESSION['user_role'] = $user['role']; + if (isset($user['client_id'])) { + $_SESSION['client_id'] = $user['client_id']; + } + return $user['role']; + } + return false; +} + +function logout() { + session_unset(); + session_destroy(); +} + +function is_logged_in() { + return isset($_SESSION['user_id']); +} + +function get_user_role() { + return $_SESSION['user_role'] ?? null; +} + +function require_login() { + if (!is_logged_in()) { + header('Location: /login.php'); + exit(); + } +} + +function require_role($role) { + require_login(); + $user_role = get_user_role(); + if (is_array($role)) { + if (!in_array($user_role, $role)) { + http_response_code(403); + die('Forbidden'); + } + } else { + if ($user_role !== $role) { + http_response_code(403); + die('Forbidden'); + } + } +} diff --git a/includes/footer.php b/includes/footer.php new file mode 100644 index 0000000..e396a8e --- /dev/null +++ b/includes/footer.php @@ -0,0 +1,5 @@ + diff --git a/includes/header.php b/includes/header.php new file mode 100644 index 0000000..1afb512 --- /dev/null +++ b/includes/header.php @@ -0,0 +1,287 @@ + [ + 'menu_catalog' => 'Katalog', + 'menu_cart' => 'Koszyk', + 'menu_orders' => 'Zamówienia', + 'menu_profile' => 'Profil', + 'menu_logout' => 'Wyloguj', + 'btn_add_to_cart' => 'Dodaj do koszyka', + 'btn_go_to_cart' => 'Przejdź do koszyka', + 'btn_checkout' => 'Przejdź do zamówienia', + 'btn_back_to_shop' => 'Wróć do sklepu', + 'label_quantity' => 'Ilość', + 'label_price' => 'Cena', + 'label_total' => 'Razem', + 'label_product' => 'Produkt', + 'title_cart' => 'Koszyk', + 'title_orders' => 'Twoje zamówienia', + 'title_order_details' => 'Szczegóły zamówienia', + 'title_checkout' => 'Podsumowanie zamówienia', + 'title_profile' => 'Profil użytkownika', + 'footer_powered_by' => 'powered by LEA24', + 'cart_empty' => 'Twój koszyk jest pusty.', + 'product' => 'Produkt', + 'remove' => 'Usuń', + 'subtotal' => 'Suma częściowa', + 'continue_shopping' => 'Kontynuuj zakupy', + 'order_summary' => 'Podsumowanie', + 'order_date' => 'Data zamówienia', + 'order_status' => 'Status', + 'order_total' => 'Suma', + 'order_action' => 'Akcja', + 'order_view' => 'Zobacz', + 'order_id' => 'ID Zamówienia', + 'order_number' => 'Numer zamówienia', + 'order_confirmation' => 'Potwierdzenie zamówienia', + 'order_thank_you' => 'Dziękujemy za złożenie zamówienia.', + 'order_number_is' => 'Numer Twojego zamówienia to', + 'first_name' => 'Imię', + 'last_name' => 'Nazwisko', + 'email' => 'Email', + 'current_password' => 'Aktualne hasło', + 'new_password' => 'Nowe hasło', + 'confirm_new_password' => 'Potwierdź nowe hasło', + 'update_profile' => 'Zaktualizuj profil', + 'password_note' => 'Pozostaw puste, jeśli nie chcesz zmieniać hasła.', + 'profile_updated' => 'Profil zaktualizowany pomyślnie.', + 'password_updated' => 'Hasło zaktualizowane pomyślnie.', + 'password_mismatch' => 'Nowe hasła nie są zgodne.', + 'incorrect_password' => 'Nieprawidłowe aktualne hasło.', + 'app_title' => 'B2B Commerce', + 'btn_update' => 'Zaktualizuj', + 'btn_remove' => 'Usuń', + 'label_unit_price' => 'Cena jednostkowa', + 'label_subtotal' => 'Suma częściowa', + 'confirm_order' => 'Potwierdź zamówienie', + 'delivery_payment_options' => 'Opcje dostawy i płatności', + 'delivery_source' => 'Źródło dostawy', + 'central_warehouse' => 'Magazyn Centralny', + 'external_supplier' => 'Dostawca zewnętrzny', + 'available_trade_credit' => 'Dostępny limit kredytu kupieckiego', + 'order_notes' => 'Uwagi do zamówienia', + 'order_history' => 'Historia zamówień', + 'error_client_id_not_found' => 'Nie znaleziono identyfikatora klienta. Zaloguj się ponownie.', + 'error_fetching_orders' => 'Wystąpił błąd podczas pobierania zamówień. Prosimy spróbować ponownie później.', + 'no_orders_yet' => 'Nie masz jeszcze żadnych zamówień.', + 'btn_view_details' => 'Szczegóły', + 'error_no_permission' => 'Brak uprawnień do wyświetlenia tego zamówienia.', + 'error_order_not_found' => 'Nie znaleziono zamówienia lub nie masz do niego dostępu.', + 'error_database' => 'Błąd bazy danych. Prosimy spróbować ponownie później.', + 'label_payment_method' => 'Metoda płatności', + 'label_notes' => 'Uwagi', + 'label_image' => 'Zdjęcie', + 'label_no_image' => 'Brak zdjęcia', + 'btn_back_to_orders' => 'Wróć do listy zamówień', + 'order_details_for' => 'Szczegóły zamówienia', + 'error_loading_profile' => 'Wystąpił błąd podczas ładowania danych profilu. Prosimy spróbować ponownie później.', + 'profile_meta_description' => 'Zarządzaj swoim profilem w platformie B2B Commerce.', + 'toggle_navigation' => 'Przełącz nawigację', + 'label_email' => 'Adres e-mail', + 'label_client' => 'Klient', + 'password_management' => 'Zarządzanie hasłem', + 'feature_in_preparation' => 'Funkcja w przygotowaniu.', + 'track_status_in' => 'Możesz śledzić jego status w panelu', + 'my_orders' => 'Moje zamówienia', + 'header_account' => 'Konto', + // Standardized Statuses + 'status_pending' => 'Oczekujące', + 'status_pending_payment' => 'Oczekuje na płatność', + 'status_paid' => 'Zapłacone', + 'status_in_progress' => 'W realizacji', + 'status_shipped' => 'Wysłane', + 'status_partially_shipped' => 'Częściowo wysłane', + 'status_completed' => 'Zrealizowane', + 'status_cancelled' => 'Anulowane', + // Standardized Payment Methods + 'payment_method' => 'Metoda płatności', + 'payment_bank_transfer' => 'Przelew tradycyjny', + 'payment_online' => 'Płatność online (Przelewy24)', + 'payment_credit' => 'Kredyt kupiecki', + 'header_welcome' => 'Witaj', + 'footer_text' => 'powered by LEA24', + ], + 'en' => [ + 'menu_catalog' => 'Catalog', + 'menu_cart' => 'Cart', + 'menu_orders' => 'Orders', + 'menu_profile' => 'Profile', + 'menu_logout' => 'Logout', + 'btn_add_to_cart' => 'Add to cart', + 'btn_go_to_cart' => 'Go to cart', + 'btn_checkout' => 'Proceed to checkout', + 'btn_back_to_shop' => 'Back to shop', + 'label_quantity' => 'Quantity', + 'label_price' => 'Price', + 'label_total' => 'Total', + 'label_product' => 'Product', + 'title_cart' => 'Shopping Cart', + 'title_orders' => 'Your Orders', + 'title_order_details' => 'Order Details', + 'title_checkout' => 'Checkout', + 'title_profile' => 'User Profile', + 'footer_powered_by' => 'powered by LEA24', + 'cart_empty' => 'Your cart is empty.', + 'product' => 'Product', + 'remove' => 'Remove', + 'subtotal' => 'Subtotal', + 'continue_shopping' => 'Continue shopping', + 'order_summary' => 'Order Summary', + 'order_date' => 'Order Date', + 'order_status' => 'Status', + 'order_total' => 'Total', + 'order_action' => 'Action', + 'order_view' => 'View', + 'order_id' => 'Order ID', + 'order_number' => 'Order Number', + 'order_confirmation' => 'Order Confirmation', + 'order_thank_you' => 'Thank you for your order.', + 'order_number_is' => 'Your order number is', + 'first_name' => 'First Name', + 'last_name' => 'Last Name', + 'email' => 'Email', + 'current_password' => 'Current Password', + 'new_password' => 'New Password', + 'confirm_new_password' => 'Confirm New Password', + 'update_profile' => 'Update Profile', + 'password_note' => 'Leave blank if you don\'t want to change the password.', + 'profile_updated' => 'Profile updated successfully.', + 'password_updated' => 'Password updated successfully.', + 'password_mismatch' => 'New passwords do not match.', + 'incorrect_password' => 'Incorrect current password.', + 'app_title' => 'B2B Commerce', + 'btn_update' => 'Update', + 'btn_remove' => 'Remove', + 'label_unit_price' => 'Unit price', + 'label_subtotal' => 'Subtotal', + 'confirm_order' => 'Confirm order', + 'delivery_payment_options' => 'Delivery and payment options', + 'delivery_source' => 'Delivery source', + 'central_warehouse' => 'Central Warehouse', + 'external_supplier' => 'External Supplier', + 'available_trade_credit' => 'Available trade credit', + 'order_notes' => 'Order notes', + 'order_history' => 'Order History', + 'error_client_id_not_found' => 'Client ID not found. Please log in again.', + 'error_fetching_orders' => 'An error occurred while fetching orders. Please try again later.', + 'no_orders_yet' => 'You have no orders yet.', + 'btn_view_details' => 'Details', + 'error_no_permission' => 'You do not have permission to view this order.', + 'error_order_not_found' => 'Order not found or you do not have access to it.', + 'error_database' => 'Database error. Please try again later.', + 'label_payment_method' => 'Payment Method', + 'label_notes' => 'Notes', + 'label_image' => 'Image', + 'label_no_image' => 'No image', + 'btn_back_to_orders' => 'Back to orders', + 'order_details_for' => 'Order Details', + 'error_loading_profile' => 'An error occurred while loading profile data. Please try again later.', + 'profile_meta_description' => 'Manage your profile on the B2B Commerce platform.', + 'toggle_navigation' => 'Toggle navigation', + 'label_email' => 'Email address', + 'label_client' => 'Client', + 'password_management' => 'Password Management', + 'feature_in_preparation' => 'Feature in preparation.', + 'track_status_in' => 'You can track its status in the', + 'my_orders' => 'My Orders', + 'header_account' => 'Account', + // Standardized Statuses + 'status_pending' => 'Pending', + 'status_pending_payment' => 'Pending payment', + 'status_paid' => 'Paid', + 'status_in_progress' => 'In progress', + 'status_shipped' => 'Shipped', + 'status_partially_shipped' => 'Partially shipped', + 'status_completed' => 'Completed', + 'status_cancelled' => 'Cancelled', + // Standardized Payment Methods + 'payment_method' => 'Payment method', + 'payment_bank_transfer' => 'Bank transfer', + 'payment_online' => 'Online payment (Przelewy24)', + 'payment_credit' => 'Trade credit', + 'header_welcome' => 'Welcome', + 'footer_text' => 'powered by LEA24', + ] +]; + +if (!function_exists('t')) { + function t($key) { + global $translations, $lang; + return $translations[$lang][$key] ?? $key; + } +} + +function getCurrentLanguage() { + global $lang; + return $lang; +} + +require_once __DIR__ . '/auth.php'; + +$user_role = get_user_role(); +$current_lang = getCurrentLanguage(); +?> + \ No newline at end of file diff --git a/includes/helpers.php b/includes/helpers.php new file mode 100644 index 0000000..991ce5a --- /dev/null +++ b/includes/helpers.php @@ -0,0 +1,77 @@ +|.]+/', '_', $filename_without_ext); + + // Remove any leading/trailing underscores + $sanitized_filename = trim($sanitized_filename, '_'); + + // Ensure the filename is not empty + if (empty($sanitized_filename)) { + $sanitized_filename = 'unnamed_file'; + } + + // Re-append the extension if it exists + if (!empty($extension)) { + return $sanitized_filename . '.' . $extension; + } + + return $sanitized_filename; +} + +function upload_error_message($error_code) { + switch ($error_code) { + case UPLOAD_ERR_INI_SIZE: + return 'The uploaded file exceeds the upload_max_filesize directive in php.ini'; + case UPLOAD_ERR_FORM_SIZE: + return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'; + case UPLOAD_ERR_PARTIAL: + return 'The uploaded file was only partially uploaded'; + case UPLOAD_ERR_NO_FILE: + return 'No file was uploaded'; + case UPLOAD_ERR_NO_TMP_DIR: + return 'Missing a temporary folder'; + case UPLOAD_ERR_CANT_WRITE: + return 'Failed to write file to disk.'; + case UPLOAD_ERR_EXTENSION: + return 'A PHP extension stopped the file upload.'; + default: + return 'Unknown upload error'; + } +} diff --git a/includes/lang.php b/includes/lang.php new file mode 100644 index 0000000..bd86260 --- /dev/null +++ b/includes/lang.php @@ -0,0 +1,232 @@ + [ + 'menu_catalog' => 'Katalog', + 'menu_cart' => 'Koszyk', + 'menu_orders' => 'Zamówienia', + 'menu_profile' => 'Profil', + 'menu_logout' => 'Wyloguj', + 'btn_add_to_cart' => 'Dodaj do koszyka', + 'btn_go_to_cart' => 'Przejdź do koszyka', + 'btn_checkout' => 'Przejdź do zamówienia', + 'btn_back_to_shop' => 'Wróć do sklepu', + 'label_quantity' => 'Ilość', + 'label_price' => 'Cena', + 'label_total' => 'Razem', + 'label_product' => 'Produkt', + 'title_cart' => 'Koszyk', + 'title_orders' => 'Twoje zamówienia', + 'title_order_details' => 'Szczegóły zamówienia', + 'title_checkout' => 'Podsumowanie zamówienia', + 'title_profile' => 'Profil użytkownika', + 'footer_powered_by' => 'powered by LEA24', + 'cart_empty' => 'Twój koszyk jest pusty.', + 'product' => 'Produkt', + 'remove' => 'Usuń', + 'subtotal' => 'Suma częściowa', + 'continue_shopping' => 'Kontynuuj zakupy', + 'order_summary' => 'Podsumowanie', + 'order_date' => 'Data zamówienia', + 'order_status' => 'Status', + 'order_total' => 'Suma', + 'order_action' => 'Akcja', + 'order_view' => 'Zobacz', + 'order_id' => 'ID Zamówienia', + 'order_number' => 'Numer zamówienia', + 'order_confirmation' => 'Potwierdzenie zamówienia', + 'order_thank_you' => 'Dziękujemy za złożenie zamówienia.', + 'order_number_is' => 'Numer Twojego zamówienia to', + 'first_name' => 'Imię', + 'last_name' => 'Nazwisko', + 'email' => 'Email', + 'current_password' => 'Aktualne hasło', + 'new_password' => 'Nowe hasło', + 'confirm_new_password' => 'Potwierdź nowe hasło', + 'update_profile' => 'Zaktualizuj profil', + 'password_note' => 'Pozostaw puste, jeśli nie chcesz zmieniać hasła.', + 'profile_updated' => 'Profil zaktualizowany pomyślnie.', + 'password_updated' => 'Hasło zaktualizowane pomyślnie.', + 'password_mismatch' => 'Nowe hasła nie są zgodne.', + 'incorrect_password' => 'Nieprawidłowe aktualne hasło.', + 'app_title' => 'B2B Commerce', + 'btn_update' => 'Zaktualizuj', + 'btn_remove' => 'Usuń', + 'label_unit_price' => 'Cena jednostkowa', + 'label_subtotal' => 'Suma częściowa', + 'confirm_order' => 'Potwierdź zamówienie', + 'delivery_payment_options' => 'Opcje dostawy i płatności', + 'delivery_source' => 'Źródło dostawy', + 'central_warehouse' => 'Magazyn Centralny', + 'external_supplier' => 'Dostawca zewnętrzny', + 'available_trade_credit' => 'Dostępny limit kredytu kupieckiego', + 'order_notes' => 'Uwagi do zamówienia', + 'order_history' => 'Historia zamówień', + 'error_client_id_not_found' => 'Nie znaleziono identyfikatora klienta. Zaloguj się ponownie.', + 'error_fetching_orders' => 'Wystąpił błąd podczas pobierania zamówień. Prosimy spróbować ponownie później.', + 'no_orders_yet' => 'Nie masz jeszcze żadnych zamówień.', + 'btn_view_details' => 'Szczegóły', + 'error_no_permission' => 'Brak uprawnień do wyświetlenia tego zamówienia.', + 'error_order_not_found' => 'Nie znaleziono zamówienia lub nie masz do niego dostępu.', + 'error_database' => 'Błąd bazy danych. Prosimy spróbować ponownie później.', + 'label_payment_method' => 'Metoda płatności', + 'label_notes' => 'Uwagi', + 'label_image' => 'Zdjęcie', + 'label_no_image' => 'Brak zdjęcia', + 'btn_back_to_orders' => 'Wróć do listy zamówień', + 'order_details_for' => 'Szczegóły zamówienia', + 'error_loading_profile' => 'Wystąpił błąd podczas ładowania danych profilu. Prosimy spróbować ponownie później.', + 'profile_meta_description' => 'Zarządzaj swoim profilem w platformie B2B Commerce.', + 'toggle_navigation' => 'Przełącz nawigację', + 'label_email' => 'Adres e-mail', + 'label_client' => 'Klient', + 'password_management' => 'Zarządzanie hasłem', + 'feature_in_preparation' => 'Funkcja w przygotowaniu.', + 'track_status_in' => 'Możesz śledzić jego status w panelu', + 'my_orders' => 'Moje zamówienia', + 'header_account' => 'Konto', + // Standardized Statuses + 'status_pending' => 'Oczekujące', + 'status_pending_payment' => 'Oczekuje na płatność', + 'status_paid' => 'Zapłacone', + 'status_in_progress' => 'W realizacji', + 'status_shipped' => 'Wysłane', + 'status_partially_shipped' => 'Częściowo wysłane', + 'status_completed' => 'Zrealizowane', + 'status_cancelled' => 'Anulowane', + // Standardized Payment Methods + 'payment_method' => 'Metoda płatności', + 'payment_bank_transfer' => 'Przelew tradycyjny', + 'payment_online' => 'Płatność online (Przelewy24)', + 'payment_credit' => 'Kredyt kupiecki', + 'header_welcome' => 'Witaj', + 'footer_text' => 'powered by LEA24', + ], + 'en' => [ + 'menu_catalog' => 'Catalog', + 'menu_cart' => 'Cart', + 'menu_orders' => 'Orders', + 'menu_profile' => 'Profile', + 'menu_logout' => 'Logout', + 'btn_add_to_cart' => 'Add to cart', + 'btn_go_to_cart' => 'Go to cart', + 'btn_checkout' => 'Proceed to checkout', + 'btn_back_to_shop' => 'Back to shop', + 'label_quantity' => 'Quantity', + 'label_price' => 'Price', + 'label_total' => 'Total', + 'label_product' => 'Product', + 'title_cart' => 'Shopping Cart', + 'title_orders' => 'Your Orders', + 'title_order_details' => 'Order Details', + 'title_checkout' => 'Checkout', + 'title_profile' => 'User Profile', + 'footer_powered_by' => 'powered by LEA24', + 'cart_empty' => 'Your cart is empty.', + 'product' => 'Product', + 'remove' => 'Remove', + 'subtotal' => 'Subtotal', + 'continue_shopping' => 'Continue shopping', + 'order_summary' => 'Order Summary', + 'order_date' => 'Order Date', + 'order_status' => 'Status', + 'order_total' => 'Total', + 'order_action' => 'Action', + 'order_view' => 'View', + 'order_id' => 'Order ID', + 'order_number' => 'Order Number', + 'order_confirmation' => 'Order Confirmation', + 'order_thank_you' => 'Thank you for your order.', + 'order_number_is' => 'Your order number is', + 'first_name' => 'First Name', + 'last_name' => 'Last Name', + 'email' => 'Email', + 'current_password' => 'Current Password', + 'new_password' => 'New Password', + 'confirm_new_password' => 'Confirm New Password', + 'update_profile' => 'Update Profile', + 'password_note' => 'Leave blank if you don\'t want to change the password.', + 'profile_updated' => 'Profile updated successfully.', + 'password_updated' => 'Password updated successfully.', + 'password_mismatch' => 'New passwords do not match.', + 'incorrect_password' => 'Incorrect current password.', + 'app_title' => 'B2B Commerce', + 'btn_update' => 'Update', + 'btn_remove' => 'Remove', + 'label_unit_price' => 'Unit price', + 'label_subtotal' => 'Subtotal', + 'confirm_order' => 'Confirm order', + 'delivery_payment_options' => 'Delivery and payment options', + 'delivery_source' => 'Delivery source', + 'central_warehouse' => 'Central Warehouse', + 'external_supplier' => 'External Supplier', + 'available_trade_credit' => 'Available trade credit', + 'order_notes' => 'Order notes', + 'order_history' => 'Order History', + 'error_client_id_not_found' => 'Client ID not found. Please log in again.', + 'error_fetching_orders' => 'An error occurred while fetching orders. Please try again later.', + 'no_orders_yet' => 'You have no orders yet.', + 'btn_view_details' => 'Details', + 'error_no_permission' => 'You do not have permission to view this order.', + 'error_order_not_found' => 'Order not found or you do not have access to it.', + 'error_database' => 'Database error. Please try again later.', + 'label_payment_method' => 'Payment Method', + 'label_notes' => 'Notes', + 'label_image' => 'Image', + 'label_no_image' => 'No image', + 'btn_back_to_orders' => 'Back to orders', + 'order_details_for' => 'Order Details', + 'error_loading_profile' => 'An error occurred while loading profile data. Please try again later.', + 'profile_meta_description' => 'Manage your profile on the B2B Commerce platform.', + 'toggle_navigation' => 'Toggle navigation', + 'label_email' => 'Email address', + 'label_client' => 'Client', + 'password_management' => 'Password Management', + 'feature_in_preparation' => 'Feature in preparation.', + 'track_status_in' => 'You can track its status in the', + 'my_orders' => 'My Orders', + 'header_account' => 'Account', + // Standardized Statuses + 'status_pending' => 'Pending', + 'status_pending_payment' => 'Pending payment', + 'status_paid' => 'Paid', + 'status_in_progress' => 'In progress', + 'status_shipped' => 'Shipped', + 'status_partially_shipped' => 'Partially shipped', + 'status_completed' => 'Completed', + 'status_cancelled' => 'Cancelled', + // Standardized Payment Methods + 'payment_method' => 'Payment method', + 'payment_bank_transfer' => 'Bank transfer', + 'payment_online' => 'Online payment (Przelewy24)', + 'payment_credit' => 'Trade credit', + 'header_welcome' => 'Welcome', + 'footer_text' => 'powered by LEA24', + ] +]; + +if (!function_exists('t')) { + function t($key) { + global $translations, $lang; + return $translations[$lang][$key] ?? $key; + } +} + +function getCurrentLanguage() { + global $lang; + return $lang; +} +?> \ No newline at end of file diff --git a/includes/status_updater.php b/includes/status_updater.php new file mode 100644 index 0000000..c54ed2f --- /dev/null +++ b/includes/status_updater.php @@ -0,0 +1,79 @@ +beginTransaction(); + + // 1. Get the current order status. If it's a terminal state, don't override it. + $stmt = $pdo->prepare("SELECT status FROM orders WHERE id = :order_id FOR UPDATE"); + $stmt->execute(['order_id' => $order_id]); + $current_status = $stmt->fetchColumn(); + + if ($current_status === 'completed' || $current_status === 'cancelled') { + $pdo->commit(); + return true; // No action needed + } + + // TODO: Later, check payment status here. If pending_payment, we might not want to move to 'in_progress' yet. + + // 2. Get counts of item statuses for the order + $stmt = $pdo->prepare(" + SELECT + COUNT(*) AS total_items, + SUM(CASE WHEN item_status = 'shipped' THEN 1 ELSE 0 END) AS shipped_items, + SUM(CASE WHEN item_status = 'pending' THEN 1 ELSE 0 END) AS pending_items, + SUM(CASE WHEN item_status = 'in_progress' THEN 1 ELSE 0 END) AS in_progress_items + FROM order_items + WHERE order_id = :order_id + "); + $stmt->execute(['order_id' => $order_id]); + $status_counts = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$status_counts || $status_counts['total_items'] == 0) { + $pdo->rollBack(); + return false; // No items found + } + + $new_status = null; + + // 3. Apply status aggregation rules + if ($status_counts['shipped_items'] == $status_counts['total_items']) { + $new_status = 'shipped'; + } elseif ($status_counts['pending_items'] > 0 || $status_counts['in_progress_items'] > 0) { + // As long as payment is made, any non-shipped item means work is in progress. + $new_status = 'in_progress'; + } + + // 4. Update the order status if it has changed + if ($new_status && $new_status !== $current_status) { + $update_stmt = $pdo->prepare("UPDATE orders SET status = :status WHERE id = :order_id"); + $update_stmt->execute(['status' => $new_status, 'order_id' => $order_id]); + // TODO: send email when order.status changes to 'shipped' + } + + // Commit the transaction + $pdo->commit(); + + return true; + + } catch (Exception $e) { + // Roll back on error + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } + // Log error, e.g., error_log('Order status update failed: ' . $e->getMessage()); + return false; + } +} diff --git a/index.php b/index.php index 7205f3d..66bb697 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,227 @@ prepare($sql); + $stmt->execute(['user_id' => $_SESSION['user_id']]); + $products = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // Separate products into main and accessories + $main_products = []; + $accessories = []; + foreach ($products as $product) { + if ($product['product_role'] === 'akcesoria') { + $accessories[] = $product; + } else { + $main_products[] = $product; + } + } + +} catch (Exception $e) { + $error = "Błąd bazy danych: " . $e->getMessage(); + $products = []; + $main_products = []; + $accessories = []; +} + +$user_role = get_user_role(); +$page_title = 'Katalog'; ?> - - + + - - - New Style - - - - - - - - - - - - - - - - - - - + + + <?= htmlspecialchars($page_title) ?> - B2B Commerce + + + + + + + + + + + + + + + + + + -
    -
    -

    Analyzing your requirements and generating your website…

    -
    - Loading… -
    -

    AI is collecting your requirements and applying the first changes.

    -

    This page will update automatically as the plan is implemented.

    -

    Runtime: PHP — UTC

    + +
    - + + +
    +

    Katalog

    + + +
    + + +
    + +
    +
    + + <?= htmlspecialchars($product['name']) ?> + +
    +
    + + + +
    +

    100 ? substr($desc, 0, 100) . '...' : $desc); + ?>

    +

    PLN /

    +
    + +
    +
    + +
    + + +
    +

    Akcesoria i produkty uzupełniające

    +
    + +
    +
    + + <?= htmlspecialchars($product['name']) ?> + +
    +
    + + + +
    +

    PLN /

    +
    + +
    +
    + +
    + + +
    + + + + + - + \ No newline at end of file diff --git a/login.php b/login.php new file mode 100644 index 0000000..4fa3aff --- /dev/null +++ b/login.php @@ -0,0 +1,99 @@ + + + + + + + <?php echo htmlspecialchars($page_title); ?> - B2B Commerce + + + + + +
    +
    +
    +
    +
    +
    + Logo +
    +

    + +
    + +
    +
    + + +
    +
    + + +
    + +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..c3d3268 --- /dev/null +++ b/logout.php @@ -0,0 +1,9 @@ + + + + + + + <?php echo htmlspecialchars($page_title); ?> - B2B Commerce + + + + + + + +
    + + Kontynuuj zakupy +
    + + + + + \ No newline at end of file diff --git a/order_details.php b/order_details.php new file mode 100644 index 0000000..644c6fe --- /dev/null +++ b/order_details.php @@ -0,0 +1,212 @@ + 'Oczekujące', + 'status_pending_payment' => 'Oczekuje na płatność', + 'status_paid' => 'Zapłacone', + 'status_in_progress' => 'W realizacji', + 'status_shipped' => 'Wysłane', + 'status_partially_shipped' => 'Częściowo wysłane', + 'status_completed' => 'Zrealizowane', + 'status_cancelled' => 'Anulowane', + 'payment_bank_transfer' => 'Przelew tradycyjny', + 'payment_online' => 'Płatność online (Przelewy24)', + 'payment_credit' => 'Kredyt kupiecki', + ]; + + $payment_methods = ['bank_transfer', 'online', 'credit']; + if (in_array($key, $payment_methods)) { + $translation_key = 'payment_' . $key; + } else { + $translation_key = 'status_' . $key; + } + + return $translations[$translation_key] ?? ucfirst(str_replace('_', ' ', $key)); +} + +$order_id = isset($_GET['id']) ? (int)$_GET['id'] : 0; +if ($order_id === 0) { + header('Location: orders.php'); + exit; +} + +$client_id = $_SESSION['client_id'] ?? 0; +$error_message = null; +$order = null; +$order_items = []; +$product_images = []; + +if ($client_id === 0) { + $error_message = 'Brak uprawnień do wyświetlenia tego zamówienia.'; +} else { + try { + $pdo = db(); + + $stmt = $pdo->prepare('SELECT * FROM orders WHERE id = :order_id AND client_id = :client_id'); + $stmt->execute([':order_id' => $order_id, ':client_id' => $client_id]); + $order = $stmt->fetch(); + + if (!$order) { + $error_message = 'Nie znaleziono zamówienia lub nie masz do niego dostępu.'; + } else { + $stmt = $pdo->prepare( + 'SELECT oi.*, p.name as product_name FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = ?' + ); + $stmt->execute([$order_id]); + $order_items = $stmt->fetchAll(); + + if (!empty($order_items)) { + $product_ids = array_map(fn($item) => $item['product_id'], $order_items); + $placeholders = implode(',', array_fill(0, count($product_ids), '?')); + + $image_stmt = $pdo->prepare( + "SELECT product_id, file_path, is_primary, id FROM product_images WHERE product_id IN ($placeholders) ORDER BY product_id, is_primary DESC, id ASC" + ); + $image_stmt->execute($product_ids); + $images_data = $image_stmt->fetchAll(); + + $product_images_temp = []; + foreach ($images_data as $image) { + if (!isset($product_images_temp[$image['product_id']])) { + $product_images_temp[$image['product_id']] = 'uploads/products/' . $image['product_id'] . '/' . basename($image['file_path']); + } + } + $product_images = $product_images_temp; + } + } + + } catch (PDOException $e) { + $error_message = 'Błąd bazy danych. Prosimy spróbować ponownie później.'; + error_log($e->getMessage()); + } +} + +$page_title = $order ? 'Szczegóły zamówienia #' . $order['id'] : 'Szczegóły zamówienia'; +$user_role = get_user_role(); +$lang = 'pl'; + +?> + + + + + + <?php echo htmlspecialchars($page_title); ?> - B2B Commerce + + + + + + + +
    + +
    + +
    + Wróć do listy zamówień + +

    + +
    +
    Podsumowanie
    +
    +

    Data zamówienia:

    +

    Status:

    +

    Metoda płatności:

    +

    Suma:

    +

    Uwagi:

    +
    +
    + +
    +
    Szczegóły zamówienia
    +
    + + + + + + + + + + + + + + + + + + + + + +
    ZdjęcieProduktCena jednostkowaIlośćSuma częściowa
    <?php echo htmlspecialchars($item['product_name']); ?>
    +
    +
    + +
    + Wróć do listy zamówień +
    + +
    + + + + + \ No newline at end of file diff --git a/order_process.php b/order_process.php new file mode 100644 index 0000000..18f55e3 --- /dev/null +++ b/order_process.php @@ -0,0 +1,128 @@ +beginTransaction(); + + // 1. Get product details from the database + $product_ids = array_keys($cart); + $placeholders = implode(',', array_fill(0, count($product_ids), '?')); + $stmt = $pdo->prepare("SELECT id, price, units_per_pallet FROM products WHERE id IN ($placeholders)"); + $stmt->execute($product_ids); + $products_by_id = $stmt->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_UNIQUE|PDO::FETCH_ASSOC); + + + // 2. Calculate total amount & total pallets + $total_amount = 0; + $is_supplier_delivery = false; + $client_id = $_SESSION['client_id'] ?? null; + + $product_prices = []; + if ($client_id) { + $price_placeholders = implode(',', array_fill(0, count($product_ids), '?')); + $sql = "SELECT p.id, COALESCE(cp.price, p.price) as price FROM products p LEFT JOIN client_prices cp ON p.id = cp.product_id AND cp.client_id = ? WHERE p.id IN ($price_placeholders)"; + $stmt = $pdo->prepare($sql); + $params = array_merge([$client_id], $product_ids); + $stmt->execute($params); + $product_prices = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); + } + + $is_supplier_delivery = false; + foreach ($cart as $product_id => $quantity) { + if (isset($products_by_id[$product_id])) { + $product = $products_by_id[$product_id]; + $price = $product_prices[$product_id] ?? $product['price']; + $total_amount += $price * $quantity; + + $units_per_pallet = $product['units_per_pallet']; + if (isset($units_per_pallet) && $units_per_pallet > 0) { + if ($quantity >= $units_per_pallet) { + $is_supplier_delivery = true; + } + } + } + } + + $delivery_source = $is_supplier_delivery ? 'supplier' : 'cs'; + + if ($_POST['payment_method'] === 'credit') { + $stmt = $pdo->prepare('SELECT credit_balance, credit_enabled FROM clients WHERE id = ? FOR UPDATE'); + $stmt->execute([$client_id]); + $credit_info = $stmt->fetch(); + + if (!$credit_info || !$credit_info['credit_enabled'] || $credit_info['credit_balance'] < $total_amount) { + throw new Exception('Invalid payment method or insufficient credit.'); + } + + $new_balance = $credit_info['credit_balance'] - $total_amount; + $stmt = $pdo->prepare('UPDATE clients SET credit_balance = ? WHERE id = ?'); + $stmt->execute([$new_balance, $client_id]); + } + + // 3. Create the order + $stmt = $pdo->prepare( + 'INSERT INTO orders (client_id, total_amount, payment_method, delivery_source, notes, status) VALUES (?, ?, ?, ?, ?, ?)' + ); + $stmt->execute([ + $client_id, + $total_amount, + $_POST['payment_method'], + $delivery_source, + $_POST['notes'], + $_POST['payment_method'] === 'credit' ? 'in_progress' : 'pending_payment' + ]); + $order_id = $pdo->lastInsertId(); + + // 4. Insert order items + $stmt = $pdo->prepare( + 'INSERT INTO order_items (order_id, product_id, quantity, unit_price, line_total) VALUES (?, ?, ?, ?, ?)' + ); + foreach ($cart as $product_id => $quantity) { + if (isset($products_by_id[$product_id])) { + $product = $products_by_id[$product_id]; + $price = $product_prices[$product_id] ?? $product['price']; + $stmt->execute([ + $order_id, + $product_id, + $quantity, + $price, + $price * $quantity + ]); + } + } + + // 5. Commit the transaction + $pdo->commit(); + + // 6. Clear the cart and store order ID in session for the confirmation page + unset($_SESSION['cart']); + $_SESSION['latest_order_id'] = $order_id; + + // 7. Redirect to confirmation page + header('Location: order_confirmation.php'); + exit; + +} catch (PDOException $e) { + $pdo->rollBack(); + // In a real application, log this error + die("Błąd podczas przetwarzania zamówienia: " . $e->getMessage()); +} diff --git a/orders.php b/orders.php new file mode 100644 index 0000000..5a3e5e7 --- /dev/null +++ b/orders.php @@ -0,0 +1,159 @@ + 'Oczekujące', + 'status_pending_payment' => 'Oczekuje na płatność', + 'status_paid' => 'Zapłacone', + 'status_in_progress' => 'W realizacji', + 'status_shipped' => 'Wysłane', + 'status_partially_shipped' => 'Częściowo wysłane', + 'status_completed' => 'Zrealizowane', + 'status_cancelled' => 'Anulowane', + 'payment_bank_transfer' => 'Przelew tradycyjny', + 'payment_online' => 'Płatność online (Przelewy24)', + 'payment_credit' => 'Kredyt kupiecki', + ]; + + $payment_methods = ['bank_transfer', 'online', 'credit']; + if (in_array($key, $payment_methods)) { + $translation_key = 'payment_' . $key; + } else { + $translation_key = 'status_' . $key; + } + + return $translations[$translation_key] ?? ucfirst(str_replace('_', ' ', $key)); +} + + +$orders = []; +$error_message = ''; + +if (!isset($_SESSION['client_id'])) { + $error_message = 'Nie znaleziono identyfikatora klienta. Zaloguj się ponownie.'; +} else { + $client_id = $_SESSION['client_id']; + try { + $pdo = db(); + $stmt = $pdo->prepare('SELECT * FROM orders WHERE client_id = ? ORDER BY created_at DESC'); + $stmt->execute([$client_id]); + $orders = $stmt->fetchAll(PDO::FETCH_ASSOC); + } catch (PDOException $e) { + error_log("Database error in orders.php: " . $e->getMessage()); + $error_message = 'Wystąpił błąd podczas pobierania zamówień. Prosimy spróbować ponownie później.'; + } +} + +$page_title = 'Twoje zamówienia'; +$user_role = get_user_role(); +$lang = 'pl'; + +?> + + + + + + <?php echo htmlspecialchars($page_title); ?> - B2B Commerce + + + + + + + +
    +

    Historia zamówień

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Numer zamówieniaData zamówieniaStatusSuma
    # + + Szczegóły + +
    + +
    + + + + + diff --git a/product.php b/product.php new file mode 100644 index 0000000..c118734 --- /dev/null +++ b/product.php @@ -0,0 +1,284 @@ +prepare("SELECT p.*, + COALESCE(cp.price, p.price_gross) as final_price, + p.price_net as final_price_net + FROM products p + LEFT JOIN users u ON u.id = :user_id + LEFT JOIN client_prices cp ON cp.product_id = p.id AND cp.client_id = u.client_id + WHERE p.id = :product_id"); + $stmt->execute(['user_id' => $_SESSION['user_id'], 'product_id' => $product_id]); + $product = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$product) { + header('Location: index.php'); + exit; + } + + // If client-specific price is used, re-calculate net price from it + if (!empty($product['final_price']) && empty($product['final_price_net'])) { + $product['final_price_net'] = round($product['final_price'] / 1.23, 2); + } + + + // Fetch product images + $img_stmt = $pdo->prepare("SELECT * FROM product_images WHERE product_id = ? ORDER BY is_primary DESC, id ASC"); + $img_stmt->execute([$product_id]); + $product_images = $img_stmt->fetchAll(PDO::FETCH_ASSOC); + $primary_image = $product_images[0] ?? null; + +} catch (PDOException $e) { + die('Błąd połączenia z bazą danych: ' . $e->getMessage()); +} + +$page_title = htmlspecialchars($product['name']); + +?> + + + + + + <?php echo $page_title; ?> - ExtraB2B + + + + + + + + +
    + ← Wróć do listy produktów +
    + +
    +
    + + <?= htmlspecialchars($product['name']) ?> +
    + + 1): ?> +
    + +
    + + Miniatura produktu + +
    + +
    + +
    + + +
    +

    + +
    +

    PLN /

    + Cena brutto +

    PLN netto

    +
    + +
    + + +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +

    +
    +
    + prepare("SELECT ak.name, pa.value FROM product_attributes pa JOIN attribute_keys ak ON pa.attribute_key_id = ak.id WHERE pa.product_id = ? AND pa.value IS NOT NULL AND pa.value != '' ORDER BY ak.name"); + $attrs_stmt->execute([$product_id]); + $product_attributes = $attrs_stmt->fetchAll(PDO::FETCH_ASSOC); + + if ($product_attributes) { + echo ''; + echo ''; + foreach ($product_attributes as $attr) { + echo ''; + echo ''; + echo ''; + echo ''; + } + echo ''; + echo '
    ' . htmlspecialchars($attr['name']) . '' . htmlspecialchars($attr['value']) . '
    '; + } else { + echo '

    Brak dodatkowych danych technicznych.

    '; + } + ?> +
    +
    + prepare("SELECT * FROM product_documents WHERE product_id = ?"); + $docs_stmt->execute([$product_id]); + $product_documents = $docs_stmt->fetchAll(PDO::FETCH_ASSOC); + + if ($product_documents) { + echo ''; + } else { + echo '

    Brak dokumentów do pobrania.

    '; + } + ?> +
    + +
    +
    +
    + + + + + + + diff --git a/profile.php b/profile.php new file mode 100644 index 0000000..b32175c --- /dev/null +++ b/profile.php @@ -0,0 +1,134 @@ +prepare("SELECT email FROM users WHERE id = ?"); + $stmt->execute([$user_id]); + $user_email = $stmt->fetchColumn(); + + // Fetch client name if client_id exists + if ($client_id) { + $stmt = $pdo->prepare("SELECT name FROM clients WHERE id = ?"); + $stmt->execute([$client_id]); + $client_name = $stmt->fetchColumn(); + } + +} catch (PDOException $e) { + error_log("Profile page error: " . $e->getMessage()); + $error_message = 'Wystąpił błąd podczas ładowania danych profilu. Prosimy spróbować ponownie później.'; +} + +$lang = 'pl'; +?> + + + + + + <?php echo htmlspecialchars($page_title); ?> - B2B Commerce + + + + + + + + + + + +
    +

    + + +
    + +
    +
    +
    Witaj,
    +
      +
    • + Adres e-mail: +
    • + +
    • + Klient: +
    • + +
    +
    +
    + +
    +
    +
    Zarządzanie hasłem
    +

    Funkcja w przygotowaniu.

    +
    +
    + +
    + + + + + \ No newline at end of file diff --git a/related_suggestions.php b/related_suggestions.php new file mode 100644 index 0000000..a295242 --- /dev/null +++ b/related_suggestions.php @@ -0,0 +1,239 @@ +prepare(" + SELECT + p.id, + p.name, + p.unit, + p.price_net, + COALESCE(cp.price, p.price_gross) as final_price, + pi.file_path AS primary_image + FROM products p + LEFT JOIN users u ON u.id = :user_id + LEFT JOIN client_prices cp ON cp.product_id = p.id AND cp.client_id = u.client_id + LEFT JOIN product_images pi ON pi.product_id = p.id AND pi.is_primary = 1 + WHERE p.id = :product_id +"); +$stmt->execute(['user_id' => $user_id, 'product_id' => $product_id]); +$added_product = $stmt->fetch(PDO::FETCH_ASSOC); + +// If product somehow doesn't exist, redirect away +if (!$added_product) { + header('Location: cart.php'); + exit; +} + +// If image is not found, use a placeholder +if (empty($added_product['primary_image'])) { + $added_product['primary_image'] = 'assets/pasted-20251212-131440-62c0087c.jpg'; // A default placeholder +} + + +// Fetch related products (accessories) +$related_products_stmt = $db->prepare(" + SELECT + p.id, + p.name, + p.unit, + p.price_net, + COALESCE(cp.price, p.price_gross) as final_price, + pi.file_path as primary_image + FROM products p + JOIN product_relations pr ON p.id = pr.related_product_id + LEFT JOIN users u ON u.id = :user_id + LEFT JOIN client_prices cp ON cp.product_id = p.id AND cp.client_id = u.client_id + LEFT JOIN product_images pi ON p.id = pi.product_id AND pi.is_primary = 1 + WHERE pr.product_id = :product_id AND p.product_role = 'akcesoria' +"); +$related_products_stmt->execute(['user_id' => $user_id, 'product_id' => $product_id]); +$related_products = $related_products_stmt->fetchAll(PDO::FETCH_ASSOC); + +$user_role = get_user_role(); +$page_title = 'Dodano do koszyka'; +?> + + + + + + <?= htmlspecialchars($page_title) ?> - B2B Commerce + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + Produkt został pomyślnie dodany do koszyka! +
    + + +
    +
    +

    Dodałeś do koszyka:

    +
    +
    +
    +
    + <?= htmlspecialchars($added_product['name']); ?> +
    +
    +
    + 0): ?> + Ilość: + +
    +
    +
    +
    +

    brutto

    +

    netto

    +
    +
    +
    +
    +
    +
    + + + +

    Polecamy także produkty powiązane:

    +
    + +
    +
    +
    + + <?= htmlspecialchars($product['name']); ?> + +
    +
    + +
    +
    +

    + + Jednostka: + +

    +
    +
    +
    +

    brutto

    +

    netto

    +
    +
    +
    +
    + + + + +
    + + +
    +
    +
    +
    +
    + +
    + + +
    + Kontynuuj zakupy + Przejdź do koszyka +
    + +
    + + + + + + + diff --git a/supplier/order_items.php b/supplier/order_items.php new file mode 100644 index 0000000..d027c91 --- /dev/null +++ b/supplier/order_items.php @@ -0,0 +1,113 @@ +prepare('SELECT order_id FROM order_items WHERE id = ?'); + $stmt_get_order->execute([$order_item_id]); + $order_id = $stmt_get_order->fetchColumn(); + + if ($order_id) { + $stmt = db()->prepare( + 'UPDATE order_items oi + JOIN products p ON oi.product_id = p.id + SET oi.item_status = ? + WHERE oi.id = ? AND p.supplier_id = ?' + ); + $stmt->execute([$item_status, $order_item_id, $supplier_id]); + + update_order_status($order_id); + } + + header('Location: /supplier/order_items.php'); + exit; +} + +$stmt = db()->prepare( + 'SELECT + oi.id AS order_item_id, + o.id AS order_id, + p.name AS product_name, + oi.quantity, + oi.item_status, + oi.updated_at + FROM order_items oi + JOIN orders o ON oi.order_id = o.id + JOIN products p ON oi.product_id = p.id + WHERE p.supplier_id = ? + ORDER BY o.created_at DESC' +); +$stmt->execute([$supplier_id]); +$order_items = $stmt->fetchAll(); + +$item_statuses = ['pending', 'in_progress', 'shipped']; + +$pageTitle = t('my_order_items'); +include '../includes/header.php'; +?> + +
    +
    +
    +

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/uploads/documents/1/unnamed_file.pdf b/uploads/documents/1/unnamed_file.pdf new file mode 100644 index 0000000..3d0ff74 Binary files /dev/null and b/uploads/documents/1/unnamed_file.pdf differ diff --git a/uploads/documents/2/unnamed_file.pdf b/uploads/documents/2/unnamed_file.pdf new file mode 100644 index 0000000..bae4e89 Binary files /dev/null and b/uploads/documents/2/unnamed_file.pdf differ diff --git a/uploads/documents/7/unnamed_file.pdf b/uploads/documents/7/unnamed_file.pdf new file mode 100644 index 0000000..8787075 Binary files /dev/null and b/uploads/documents/7/unnamed_file.pdf differ diff --git a/uploads/products/1/prod_1_693bb4b8cafc84.71050013.jpg b/uploads/products/1/prod_1_693bb4b8cafc84.71050013.jpg new file mode 100644 index 0000000..5662947 Binary files /dev/null and b/uploads/products/1/prod_1_693bb4b8cafc84.71050013.jpg differ diff --git a/uploads/products/1/prod_1_693bb5570d0001.42808802.jpg b/uploads/products/1/prod_1_693bb5570d0001.42808802.jpg new file mode 100644 index 0000000..5662947 Binary files /dev/null and b/uploads/products/1/prod_1_693bb5570d0001.42808802.jpg differ diff --git a/uploads/products/1/prod_1_693bbe3b50f255.26236493.jpg b/uploads/products/1/prod_1_693bbe3b50f255.26236493.jpg new file mode 100644 index 0000000..bc824e9 Binary files /dev/null and b/uploads/products/1/prod_1_693bbe3b50f255.26236493.jpg differ diff --git a/uploads/products/2/prod_2_693bbe4f269239.01053848.jpg b/uploads/products/2/prod_2_693bbe4f269239.01053848.jpg new file mode 100644 index 0000000..bc824e9 Binary files /dev/null and b/uploads/products/2/prod_2_693bbe4f269239.01053848.jpg differ diff --git a/uploads/products/3/prod_3_693bbccab68f28.84224252.jpg b/uploads/products/3/prod_3_693bbccab68f28.84224252.jpg new file mode 100644 index 0000000..aa8b84e Binary files /dev/null and b/uploads/products/3/prod_3_693bbccab68f28.84224252.jpg differ diff --git a/uploads/products/4/prod_4_693bbcd7f0cf89.88681384.jpg b/uploads/products/4/prod_4_693bbcd7f0cf89.88681384.jpg new file mode 100644 index 0000000..5dcc5d2 Binary files /dev/null and b/uploads/products/4/prod_4_693bbcd7f0cf89.88681384.jpg differ diff --git a/uploads/products/5/prod_5_693bbc6c51ada0.64391905.jpg b/uploads/products/5/prod_5_693bbc6c51ada0.64391905.jpg new file mode 100644 index 0000000..05e0796 Binary files /dev/null and b/uploads/products/5/prod_5_693bbc6c51ada0.64391905.jpg differ diff --git a/uploads/products/6/prod_6_693bbcb53d3ad4.54723030.jpg b/uploads/products/6/prod_6_693bbcb53d3ad4.54723030.jpg new file mode 100644 index 0000000..bf96c3f Binary files /dev/null and b/uploads/products/6/prod_6_693bbcb53d3ad4.54723030.jpg differ diff --git a/uploads/products/7/prod_7_693bbc95f3ad62.80108781.JPG b/uploads/products/7/prod_7_693bbc95f3ad62.80108781.JPG new file mode 100644 index 0000000..79d3cb3 Binary files /dev/null and b/uploads/products/7/prod_7_693bbc95f3ad62.80108781.JPG differ diff --git a/uploads/products/7/prod_7_693bbc95f42182.47320741.png b/uploads/products/7/prod_7_693bbc95f42182.47320741.png new file mode 100644 index 0000000..9919303 Binary files /dev/null and b/uploads/products/7/prod_7_693bbc95f42182.47320741.png differ diff --git a/uploads/products/prod_1_693afe1d3edc78.69259636.png b/uploads/products/prod_1_693afe1d3edc78.69259636.png new file mode 100644 index 0000000..4afb5d0 Binary files /dev/null and b/uploads/products/prod_1_693afe1d3edc78.69259636.png differ diff --git a/uploads/products/prod_1_693b00fe28f657.18624169.png b/uploads/products/prod_1_693b00fe28f657.18624169.png new file mode 100644 index 0000000..4afb5d0 Binary files /dev/null and b/uploads/products/prod_1_693b00fe28f657.18624169.png differ diff --git a/uploads/products/prod_1_693b03ee837bd6.83842630.png b/uploads/products/prod_1_693b03ee837bd6.83842630.png new file mode 100644 index 0000000..b59bdfb Binary files /dev/null and b/uploads/products/prod_1_693b03ee837bd6.83842630.png differ diff --git a/uploads/products/prod_1_693b03ee8562b4.70893384.png b/uploads/products/prod_1_693b03ee8562b4.70893384.png new file mode 100644 index 0000000..21e57f4 Binary files /dev/null and b/uploads/products/prod_1_693b03ee8562b4.70893384.png differ diff --git a/uploads/products/prod_1_693b03ee85d3f3.04162035.jpg b/uploads/products/prod_1_693b03ee85d3f3.04162035.jpg new file mode 100644 index 0000000..d400c78 Binary files /dev/null and b/uploads/products/prod_1_693b03ee85d3f3.04162035.jpg differ