MVP, do pokazania klientowi. Bez wersji ENG

This commit is contained in:
Flatlogic Bot 2025-12-12 14:13:03 +00:00
parent 9244e10328
commit 09395ccf2c
80 changed files with 4909 additions and 142 deletions

143
admin/attribute_keys.php Normal file
View File

@ -0,0 +1,143 @@
<?php
session_start();
require_once __DIR__ . '/../includes/lang.php';
require_once __DIR__ . '/../includes/auth.php';
require_role('admin');
require_once __DIR__ . '/../includes/helpers.php';
// Handle form submission for adding a new key
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_key'])) {
$name = trim($_POST['name']);
$error = '';
if (empty($name)) {
$error = 'Nazwa klucza nie może być pusta.';
}
if (empty($error)) {
try {
$stmt = db()->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();
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin - Klucze atrybutów</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css">
</head>
<body>
<?php include 'menu.php'; ?>
<div class="container mt-4">
<?php
if (isset($_SESSION['error_message'])) {
echo '<div class="alert alert-danger"><p class="mb-0">' . htmlspecialchars($_SESSION['error_message']) . '</p></div>';
unset($_SESSION['error_message']);
}
if (isset($_SESSION['success_message'])) {
echo '<div class="alert alert-success"><p class="mb-0">' . htmlspecialchars($_SESSION['success_message']) . '</p></div>';
unset($_SESSION['success_message']);
}
?>
<div class="card">
<div class="card-header">
<h3>Parametry techniczne klucze</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-8">
<h4>Istniejące klucze</h4>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Nazwa</th>
<th style="width: 100px;">Akcje</th>
</tr>
</thead>
<tbody>
<?php foreach ($attribute_keys as $key): ?>
<tr>
<td><?php echo htmlspecialchars($key['id']); ?></td>
<td><?php echo htmlspecialchars($key['name']); ?></td>
<td>
<form method="POST" action="" onsubmit="return confirm('Czy na pewno chcesz usunąć ten klucz?');">
<input type="hidden" name="id" value="<?php echo $key['id']; ?>">
<button type="submit" name="delete_key" class="btn btn-sm btn-danger">Usuń</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="col-md-4">
<h4>Dodaj nowy klucz</h4>
<form method="POST" action="">
<div class="mb-3">
<label for="name" class="form-label">Nazwa klucza</label>
<input type="text" name="name" id="name" class="form-control" required>
</div>
<button type="submit" name="add_key" class="btn btn-primary">Dodaj klucz</button>
</form>
</div>
</div>
</div>
</div>
</div>
<?php include __DIR__ . '/../includes/footer.php'; ?>

144
admin/client_prices.php Normal file
View File

@ -0,0 +1,144 @@
<?php
session_start();
require_once __DIR__ . '/../includes/lang.php';
require_once __DIR__ . '/../includes/auth.php';
require_role('admin');
require_once __DIR__ . '/../includes/helpers.php';
$pdo = db();
$message = '';
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['client_id'], $_POST['product_id'], $_POST['price'])) {
$clientId = $_POST['client_id'];
$productId = $_POST['product_id'];
$price = $_POST['price'];
if (!empty($clientId) && !empty($productId) && is_numeric($price)) {
// Upsert logic
$stmt = $pdo->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 = '<div class="alert alert-success">Cena została zaktualizowana.</div>';
} 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 = '<div class="alert alert-success">Nowa cena została dodana.</div>';
}
} else {
$message = '<div class="alert alert-danger">Wszystkie pola są wymagane.</div>';
}
}
// 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);
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cennik indywidualny</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css">
</head>
<body>
<?php include 'menu.php'; ?>
<div class="container mt-4">
<div class="card">
<div class="card-header">
<h2>Cennik indywidualny</h2>
</div>
<div class="card-body">
<?php if ($message) echo $message; ?>
<div class="card mb-4">
<div class="card-header">
<h3>Dodaj/Edytuj cenę</h3>
</div>
<div class="card-body">
<form method="POST" action="client_prices.php">
<div class="row">
<div class="col-md-4 mb-3">
<label for="client_id" class="form-label">Klient:</label>
<select name="client_id" id="client_id" class="form-select" required>
<option value="">Wybierz klienta</option>
<?php foreach ($clients as $client): ?>
<option value="<?php echo $client['id']; ?>"><?php echo htmlspecialchars($client['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4 mb-3">
<label for="product_id" class="form-label">Produkt:</label>
<select name="product_id" id="product_id" class="form-select" required>
<option value="">Wybierz produkt</option>
<?php foreach ($products as $product): ?>
<option value="<?php echo $product['id']; ?>"><?php echo htmlspecialchars($product['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2 mb-3">
<label for="price" class="form-label">Cena:</label>
<input type="number" step="0.01" name="price" id="price" class="form-control" required>
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">Zapisz</button>
</div>
</div>
</form>
</div>
</div>
<h3>Istniejące ceny indywidualne</h3>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Klient</th>
<th>Produkt</th>
<th>Cena (PLN)</th>
</tr>
</thead>
<tbody>
<?php if (empty($existingPrices)): ?>
<tr>
<td colspan="3" class="text-center">Brak zdefiniowanych cen indywidualnych.</td>
</tr>
<?php else: ?>
<?php foreach ($existingPrices as $price): ?>
<tr>
<td><?php echo htmlspecialchars($price['client_name']); ?></td>
<td><?php echo htmlspecialchars($price['product_name']); ?></td>
<td><?php echo number_format($price['price'], 2, ',', ' '); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php include __DIR__ . '/../includes/footer.php'; ?>

106
admin/clients.php Normal file
View File

@ -0,0 +1,106 @@
<?php
session_start();
require_once __DIR__ . '/../includes/auth.php';
require_role('admin');
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/helpers.php';
$clients = [];
$error_message = '';
$pdo = null;
try {
$pdo = db();
$stmt_clients = $pdo->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';
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $pageTitle; ?> - Panel Administracyjny</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<?php require_once __DIR__ . '/menu.php'; ?>
<main class="container mt-4">
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h1><?php echo $pageTitle; ?></h1>
<a href="edit_client.php" class="btn btn-success">+ Dodaj klienta</a>
</div>
</div>
<div class="card-body">
<?php if ($error_message): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($error_message); ?>
</div>
<?php elseif ($pdo): ?>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Nazwa</th>
<th>NIP</th>
<th>Miasto</th>
<th>Przypisani użytkownicy</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
<?php if (empty($clients)): ?>
<tr>
<td colspan="6" class="text-center">Nie znaleziono klientów.</td>
</tr>
<?php else: ?>
<?php foreach ($clients as $client): ?>
<tr>
<td><?php echo htmlspecialchars($client['id']); ?></td>
<td><?php echo htmlspecialchars($client['name']); ?></td>
<td><?php echo htmlspecialchars($client['nip']); ?></td>
<td><?php echo htmlspecialchars($client['city']); ?></td>
<td>
<?php
$assignedUsersLabel = '—';
try {
$stmtUsers = $pdo->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 = '<span class="text-danger font-italic">Błąd ładowania</span>';
}
echo $assignedUsersLabel;
?>
</td>
<td>
<a href="edit_client.php?id=<?php echo $client['id']; ?>" class="btn btn-sm btn-primary">Edytuj</a>
<a href="client_prices.php?client_id=<?php echo $client['id']; ?>" class="btn btn-sm btn-info">Cennik</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

152
admin/edit_client.php Normal file
View File

@ -0,0 +1,152 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
require_role('admin');
require_once __DIR__ . '/../includes/helpers.php';
require_once __DIR__ . '/menu.php';
$db = db();
$clientId = $_GET['id'] ?? null;
$client = [
'name' => '',
'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
}
}
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($pageTitle); ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<div class="card">
<div class="card-header">
<h1><?php echo htmlspecialchars($pageTitle); ?></h1>
</div>
<div class="card-body">
<?php if ($errorMessage): ?>
<div class="alert alert-danger"><?php echo $errorMessage; ?></div>
<?php endif; ?>
<?php if ($successMessage): ?>
<div class="alert alert-success"><?php echo $successMessage; ?></div>
<?php endif; ?>
<form method="post">
<div class="mb-3">
<label for="name" class="form-label">Nazwa firmy</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($client['name'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="tax_id" class="form-label">NIP</label>
<input type="text" class="form-control" id="tax_id" name="tax_id" value="<?php echo htmlspecialchars($client['nip'] ?? ''); ?>">
</div>
<div class="mb-3">
<label for="address" class="form-label">Adres</label>
<input type="text" class="form-control" id="address" name="address" value="<?php echo htmlspecialchars($client['street'] ?? ''); ?>">
</div>
<div class="mb-3">
<label for="zip_code" class="form-label">Kod pocztowy</label>
<input type="text" class="form-control" id="zip_code" name="zip_code" value="<?php echo htmlspecialchars($client['postal_code'] ?? ''); ?>">
</div>
<div class="mb-3">
<label for="city" class="form-label">Miasto</label>
<input type="text" class="form-control" id="city" name="city" value="<?php echo htmlspecialchars($client['city'] ?? ''); ?>">
</div>
<div class="mb-3">
<label for="credit_limit" class="form-label">Limit kredytu kupieckiego</label>
<input type="number" step="0.01" class="form-control" id="credit_limit" name="credit_limit" value="<?php echo htmlspecialchars($client['credit_limit'] ?? '0'); ?>">
</div>
<?php if ($clientId): ?>
<div class="mb-3">
<label class="form-label">Wykorzystany kredyt</label>
<p class="form-control-plaintext"><?php echo number_format($used_credit, 2, ',', ' '); ?> PLN</p>
</div>
<div class="mb-3">
<label class="form-label">Dostępny kredyt</label>
<p class="form-control-plaintext fw-bold <?php echo (($client['credit_limit'] - $used_credit) < 0) ? 'text-danger' : 'text-success'; ?>">
<?php echo number_format($client['credit_limit'] - $used_credit, 2, ',', ' '); ?> PLN
</p>
</div>
<?php endif; ?>
<button type="submit" class="btn btn-primary">Zapisz</button>
<a href="clients.php" class="btn btn-secondary">Anuluj</a>
</form>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

462
admin/edit_product.php Normal file
View File

@ -0,0 +1,462 @@
<?php
session_start();
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/helpers.php';
require_role('admin');
$pdo = db();
// Handle image deletion
if (isset($_GET['delete_image']) && isset($_GET['id'])) {
$image_id_to_delete = $_GET['delete_image'];
$product_id_for_redirect = $_GET['id'];
$img_stmt = $pdo->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';
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title); ?> - Panel Administracyjny</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<?php include __DIR__ . '/menu.php'; ?>
<main class="container my-5">
<h1><?php echo $page_title; ?></h1>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<?php foreach ($errors as $error): ?><p><?php echo htmlspecialchars($error); ?></p><?php endforeach; ?>
</div>
<?php endif; ?>
<form action="edit_product.php<?php echo $product['id'] ? '?id='.$product['id'] : '' ?>" method="POST" enctype="multipart/form-data">
<input type="hidden" name="id" value="<?php echo htmlspecialchars($product['id'] ?? ''); ?>">
<div class="card card-body mb-4">
<div class="mb-3">
<label for="name" class="form-label">Nazwa produktu</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($product['name'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Opis</label>
<textarea class="form-control" id="description" name="description" rows="3"><?php echo htmlspecialchars($product['description'] ?? ''); ?></textarea>
</div>
<div class="mb-3">
<label for="price_net" class="form-label">Cena netto</label>
<input type="number" step="0.01" class="form-control" id="price_net" name="price_net" value="<?= htmlspecialchars($product['price_net'] ?? '') ?>">
</div>
<div class="mb-3">
<label for="price_gross" class="form-label">Cena brutto (z VAT 23%)</label>
<input type="number" step="0.01" class="form-control" id="price_gross" name="price_gross" value="<?= htmlspecialchars($product['price_gross'] ?? '') ?>">
</div>
<div class="mb-3">
<label for="unit" class="form-label">Jednostka miary</label>
<select name="unit" id="unit" class="form-select">
<option value="szt" <?= ($product['unit'] ?? 'szt') === 'szt' ? 'selected' : '' ?>>sztuka</option>
<option value="mb" <?= ($product['unit'] ?? 'szt') === 'mb' ? 'selected' : '' ?>>mb</option>
<option value="m2" <?= ($product['unit'] ?? 'szt') === 'm2' ? 'selected' : '' ?>>m2</option>
</select>
</div>
<div class="mb-3">
<label for="units_per_pallet" class="form-label">Ilość sztuk na palecie</label>
<input type="number" min="1" class="form-control" id="units_per_pallet" name="units_per_pallet" value="<?= htmlspecialchars($product['units_per_pallet'] ?? '') ?>">
</div>
<div class="mb-3">
<label for="product_role" class="form-label">Typ produktu</label>
<select name="product_role" id="product_role" class="form-select">
<option value="membrana" <?php echo ($product['product_role'] ?? 'membrana') === 'membrana' ? 'selected' : ''; ?>>membrana</option>
<option value="akcesoria" <?php echo ($product['product_role'] ?? 'membrana') === 'akcesoria' ? 'selected' : ''; ?>>akcesoria</option>
</select>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="is_active" name="is_active" value="1" <?php echo !empty($product['is_active']) ? 'checked' : ''; ?>>
<label class="form-check-label" for="is_active">Aktywny</label>
</div>
</div>
<div class="card card-body mb-4">
<h5>Zdjęcia</h5>
<div class="mb-3">
<label for="images" class="form-label">Dodaj nowe zdjęcia</label>
<input type="file" class="form-control" id="images" name="images[]" multiple accept="image/png, image/jpeg">
</div>
<?php if (!empty($product_images)):
echo '<div class="row g-3">';
foreach($product_images as $image) {
echo '<div class="col-md-3 text-center">';
echo '<img src="../uploads/products/' . htmlspecialchars($image['file_path']) . '" class="img-thumbnail mb-2" style="max-height: 150px;">';
echo '<a href="edit_product.php?id=' . htmlspecialchars($product['id']) . '&delete_image=' . htmlspecialchars($image['id']) . '" class="btn btn-danger btn-sm" onclick="return confirm(\'Czy na pewno chcesz usunąć to zdjęcie?\');">Usuń</a>';
echo '</div>';
}
echo '</div>';
endif; ?>
</div>
<div class="card card-body mb-4">
<h5>Atrybuty</h5>
<div class="mb-3">
<label for="supplier_id" class="form-label">Dostawca</label>
<select name="supplier_id" id="supplier_id" class="form-select">
<option value="">-- Wybierz dostawcę --</option>
<?php foreach ($suppliers as $supplier): ?>
<option value="<?php echo htmlspecialchars($supplier['id']); ?>" <?php echo (isset($product['supplier_id']) && $product['supplier_id'] == $supplier['id']) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($supplier['email']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<hr>
<h5>Specyfikacja techniczna</h5>
<?php foreach ($attribute_keys as $key): ?>
<div class="mb-3">
<label for="attribute_<?php echo htmlspecialchars($key['id']); ?>" class="form-label"><?php echo htmlspecialchars($key['name']); ?></label>
<input type="text" class="form-control" id="attribute_<?php echo htmlspecialchars($key['id']); ?>" name="attributes[<?php echo htmlspecialchars($key['id']); ?>]" value="<?php echo htmlspecialchars($product_attributes[$key['id']] ?? ''); ?>">
</div>
<?php endforeach; ?>
</div>
<div class="card card-body mb-4">
<h5>Produkty powiązane</h5>
<div class="mb-3">
<label for="related_products" class="form-label">Wybierz produkty powiązane</label>
<select multiple class="form-control" id="related_products" name="related_products[]" size="10">
<?php foreach ($all_products as $p): ?>
<?php if ($p['id'] != $product['id']): // Exclude self ?>
<option value="<?php echo htmlspecialchars($p['id']); ?>" <?php echo in_array($p['id'], $related_product_ids) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($p['name']); ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="card card-body mb-4">
<h5>Dokumenty produktu (PDF)</h5>
<div class="mb-3">
<label for="documents" class="form-label">Dodaj nowe dokumenty</label>
<input type="file" class="form-control" id="documents" name="documents[]" multiple accept="application/pdf">
</div>
<?php if (!empty($product_documents)):
echo '<ul class="list-group">';
foreach($product_documents as $doc) {
echo '<li class="list-group-item d-flex justify-content-between align-items-center">';
echo htmlspecialchars($doc['file_name']);
echo '<a href="edit_product.php?id=' . htmlspecialchars($product['id']) . '&delete_document=' . htmlspecialchars($doc['id']) . '" class="btn btn-danger btn-sm" onclick="return confirm(\'Czy na pewno chcesz usunąć ten dokument?\');">Usuń</a>';
echo '</li>';
}
echo '</ul>';
endif; ?>
</div>
<div class="mt-4">
<button type="submit" class="btn btn-primary">Zapisz</button>
<a href="products.php" class="btn btn-secondary">Anuluj</a>
</div>
</form>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const priceNetInput = document.getElementById('price_net');
const priceGrossInput = document.getElementById('price_gross');
const vatRate = 1.23;
priceNetInput.addEventListener('input', function() {
const netValue = parseFloat(this.value);
if (!isNaN(netValue)) {
priceGrossInput.value = (netValue * vatRate).toFixed(2);
} else {
priceGrossInput.value = '';
}
});
priceGrossInput.addEventListener('input', function() {
const grossValue = parseFloat(this.value);
if (!isNaN(grossValue)) {
priceNetInput.value = (grossValue / vatRate).toFixed(2);
} else {
priceNetInput.value = '';
}
});
});
</script>
</body>
</html>

185
admin/edit_user.php Normal file
View File

@ -0,0 +1,185 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
require_role('admin');
require_once __DIR__ . '/../db/config.php';
$pdo = db();
$user = [
'id' => '',
'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());
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $pageTitle; ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<?php include __DIR__ . '/menu.php'; ?>
<main class="container mt-4">
<div class="card">
<div class="card-header">
<h1><?php echo $pageTitle; ?></h1>
</div>
<div class="card-body">
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<?php foreach ($errors as $error): ?>
<p><?php echo htmlspecialchars($error); ?></p>
<?php endforeach; ?>
</div>
<?php endif; ?>
<form action="" method="post">
<div class="mb-3">
<label for="email" class="form-label">E-mail</label>
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($user['email']); ?>" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Hasło</label>
<input type="password" class="form-control" id="password" name="password" <?php if($is_new_user) echo 'required' ?> >
<?php if (!$is_new_user): ?>
<div class="form-text">Zostaw puste, aby zachować obecne hasło.</div>
<?php endif; ?>
</div>
<div class="mb-3">
<label for="role" class="form-label">Rola</label>
<select class="form-select" id="role" name="role" onchange="toggleClientDropdown()">
<option value="admin" <?php if($user['role'] === 'admin') echo 'selected'; ?>>Administrator</option>
<option value="client" <?php if($user['role'] === 'client') echo 'selected'; ?>>Klient</option>
<option value="supplier" <?php if($user['role'] === 'supplier') echo 'selected'; ?>>Dostawca</option>
<option value="finance" <?php if($user['role'] === 'finance') echo 'selected'; ?>>Finanse</option>
<option value="support" <?php if($user['role'] === 'support') echo 'selected'; ?>>Wsparcie</choice>
</select>
</div>
<div id="client-dropdown" class="mb-3" style="display: <?php echo ($user['role'] === 'client') ? 'block' : 'none'; ?>;">
<label for="client_id" class="form-label">Powiązana firma</label>
<select class="form-select" id="client_id" name="client_id">
<option value="">Wybierz firmę</option>
<?php foreach ($clients as $client): ?>
<option value="<?php echo $client['id']; ?>" <?php if(isset($user['client_id']) && $user['client_id'] == $client['id']) echo 'selected'; ?>><?php echo htmlspecialchars($client['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-check mb-3">
<input type="hidden" name="is_active" value="0">
<input class="form-check-input" type="checkbox" id="is_active" name="is_active" value="1" <?php if($user['is_active']) echo 'checked'; ?>>
<label class="form-check-label" for="is_active">Aktywny</label>
</div>
<button type="submit" class="btn btn-primary">Zapisz</button>
<a href="users.php" class="btn btn-secondary">Anuluj</a>
</form>
</div>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
function toggleClientDropdown() {
var role = document.getElementById('role').value;
var dropdown = document.getElementById('client-dropdown');
if (role === 'client') {
dropdown.style.display = 'block';
} else {
dropdown.style.display = 'none';
}
}
</script>
</body>
</html>

36
admin/menu.php Normal file
View File

@ -0,0 +1,36 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/admin/orders.php">Admin Panel</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="orders.php">Zamówienia</a>
</li>
<li class="nav-item">
<a class="nav-link" href="products.php">Produkty</a>
</li>
<li class="nav-item">
<a class="nav-link" href="clients.php">Klienci</a>
</li>
<li class="nav-item">
<a class="nav-link" href="users.php">Użytkownicy</a>
</li>
<li class="nav-item">
<a class="nav-link" href="client_prices.php">Ceny klientów</a>
</li>
<li class="nav-item">
<a class="nav-link" href="attribute_keys.php">Atrybuty</a>
</li>
</ul>
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="../logout.php">Wyloguj</a>
</li>
</ul>
</div>
</div>
</nav>

157
admin/order_details.php Normal file
View File

@ -0,0 +1,157 @@
<?php
session_start();
require_once __DIR__ . '/../includes/auth.php';
require_role('admin');
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/helpers.php';
$pdo = db();
$order_id = $_GET['id'] ?? null;
// Simple local translations
$i18n = [
'pending_payment' => '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']);
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $pageTitle; ?> - Panel Administracyjny</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body>
<?php require_once __DIR__ . '/menu.php'; ?>
<main class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1><?php echo $pageTitle; ?></h1>
<a href="orders.php" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Powrót do listy
</a>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card mb-4">
<div class="card-header">Pozycje zamówienia</div>
<div class="card-body">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Produkt</th>
<th>Ilość</th>
<th>Cena jednostkowa</th>
<th>Suma</th>
</tr>
</thead>
<tbody>
<?php foreach ($order_items as $item): ?>
<tr>
<td><?php echo htmlspecialchars($item['product_name']); ?></td>
<td><?php echo htmlspecialchars($item['quantity']); ?></td>
<td><?php echo number_format($item['unit_price'], 2, ',', ' '); ?> zł</td>
<td><?php echo number_format($item['line_total'], 2, ',', ' '); ?> zł</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card mb-4">
<div class="card-header">Podsumowanie</div>
<div class="card-body">
<p><strong>Klient:</strong> <?php echo htmlspecialchars($order['client_company_name'] ?? 'Brak'); ?></p>
<p><strong>Data:</strong> <?php echo date('d.m.Y H:i', strtotime($order['created_at'])); ?></p>
<p><strong>Metoda płatności:</strong> <span class="badge bg-secondary"><?php echo htmlspecialchars(get_payment_method_translation_local($order['payment_method'], $i18n)); ?></span></p>
<p><strong>Suma:</strong> <strong class="fs-5"><?php echo number_format($order['total_amount'], 2, ',', ' '); ?> zł</strong></p>
</div>
</div>
<div class="card">
<div class="card-header">Status zamówienia</div>
<div class="card-body">
<form action="order_details.php?id=<?php echo $order_id; ?>" method="POST">
<div class="input-group">
<select name="status" class="form-select">
<?php foreach ($statuses as $status): ?>
<option value="<?php echo $status; ?>" <?php echo ($order['status'] === $status) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars(get_status_translation_local($status, $i18n)); ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit" class="btn btn-primary">Zapisz</button>
</div>
</form>
<div class="mt-2">
<strong>Aktualny status:</strong>
<span class="badge bg-info fs-6"><?php echo htmlspecialchars(get_status_translation_local($order['status'], $i18n)); ?></span>
</div>
</div>
</div>
</div>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

160
admin/orders.php Normal file
View File

@ -0,0 +1,160 @@
<?php
session_start();
require_once __DIR__ . '/../includes/auth.php';
require_role('admin');
require_once __DIR__ . '/../includes/helpers.php';
$pdotry = null;
$error = null;
$orders = [];
$stats = [
'new_today' => 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";
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $pageTitle ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css">
</head>
<body>
<?php include 'menu.php'; ?>
<div class="container">
<h1 class="mb-4"><?= $pageTitle ?></h1>
<?php if ($error): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<div class="row mb-4">
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">Nowe (dziś)</h5>
<p class="card-text fs-4"><?= $stats['new_today'] ?></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">Nowe (tydzień)</h5>
<p class="card-text fs-4"><?= $stats['new_week'] ?></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center text-bg-warning">
<div class="card-body">
<h5 class="card-title">Do zapłaty</h5>
<p class="card-text fs-4"><?= $stats['awaiting_payment'] ?></p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center text-bg-info">
<div class="card-body">
<h5 class="card-title">W realizacji</h5>
<p class="card-text fs-4"><?= $stats['in_progress'] ?></p>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
Wszystkie zamówienia
</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Klient</th>
<th>Data</th>
<th>Status</th>
<th>Źródło</th>
<th>Suma</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
<?php if (empty($orders)): ?>
<tr>
<td colspan="7" class="text-center">Brak zamówień do wyświetlenia.</td>
</tr>
<?php else: ?>
<?php foreach ($orders as $order): ?>
<tr>
<td>#<?= htmlspecialchars($order['id']) ?></td>
<td><?= htmlspecialchars($order['client_company_name'] ?? 'Klient indywidualny') ?></td>
<td><?= date('d.m.Y H:i', strtotime($order['created_at'])) ?></td>
<td><span class="badge bg-info"><?= htmlspecialchars($order['status']) ?></span></td>
<td><?= htmlspecialchars($order['delivery_source'] ?? 'N/A') ?></td>
<td><?= htmlspecialchars(number_format($order['total_amount'], 2, ',', ' ')) ?> zł</td>
<td>
<a href="order_details.php?id=<?= $order['id'] ?>" class="btn btn-sm btn-primary">Szczegóły</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

91
admin/products.php Normal file
View File

@ -0,0 +1,91 @@
<?php
session_start();
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/helpers.php';
require_role('admin');
$pdo = db();
$stmt = $pdo->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';
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title); ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="../assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<?php include 'menu.php'; ?>
<main class="container my-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2"><?php echo $page_title; ?></h1>
<a href="edit_product.php" class="btn btn-primary">
<i class="bi bi-plus-lg"></i> Dodaj produkt
</a>
</div>
<div class="card">
<div class="card-body">
<table class="table table-striped table-hover">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Nazwa</th>
<th>Typ</th>
<th>Cena netto</th>
<th>Cena brutto</th>
<th>Aktywny</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
<?php if (empty($products)):
?>
<tr>
<td colspan="7" class="text-center">Nie znaleziono produktów.</td>
</tr>
<?php else: ?>
<?php foreach ($products as $product): ?>
<tr>
<td><?php echo htmlspecialchars($product['id']); ?></td>
<td><?php echo htmlspecialchars($product['name']); ?></td>
<td><?php echo htmlspecialchars($product['product_role']); ?></td>
<td><?php echo number_format($product['price_net'], 2, ',', ' '); ?> zł</td>
<td><?php echo number_format($product['price_gross'], 2, ',', ' '); ?> zł</td>
<td>
<?php if ($product['is_active']): ?>
<span class="badge bg-success">Tak</span>
<?php else: ?>
<span class="badge bg-danger">Nie</span>
<?php endif; ?>
</td>
<td>
<a href="edit_product.php?id=<?php echo $product['id']; ?>" class="btn btn-sm btn-secondary">
<i class="bi bi-pencil-fill"></i> Edytuj
</a>
<a href="toggle_active.php?id=<?php echo $product['id']; ?>" class="btn btn-sm btn-info">
<?php echo $product['is_active'] ? 'Dezaktywuj' : 'Aktywuj'; ?>
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

31
admin/toggle_active.php Normal file
View File

@ -0,0 +1,31 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
require_role('admin');
require_once __DIR__ . '/../db/config.php';
if (!isset($_GET['id'])) {
header("Location: products.php");
exit;
}
$pdo = db();
$id = $_GET['id'];
// First, get the current status
$stmt = $pdo->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;

84
admin/users.php Normal file
View File

@ -0,0 +1,84 @@
<?php
session_start();
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/helpers.php';
require_role('admin');
$pdo = db();
$stmt = $pdo->query('SELECT * FROM users ORDER BY created_at DESC');
$users = $stmt->fetchAll();
$page_title = 'Użytkownicy';
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title); ?> - Panel Administracyjny</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body>
<?php include __DIR__ . '/menu.php'; ?>
<main class="container my-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2"><?php echo $page_title; ?></h1>
<a href="edit_user.php" class="btn btn-primary">
<i class="bi bi-plus-lg"></i> Dodaj użytkownika
</a>
</div>
<div class="card">
<div class="card-body">
<table class="table table-striped table-hover">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Email</th>
<th>Rola</th>
<th>Aktywny</th>
<th>Data utworzenia</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
<?php if (empty($users)):
?>
<tr>
<td colspan="6" class="text-center">Nie znaleziono użytkowników.</td>
</tr>
<?php else:
?>
<?php foreach ($users as $user): ?>
<tr>
<td><?php echo htmlspecialchars($user['id']); ?></td>
<td><?php echo htmlspecialchars($user['email']); ?></td>
<td><span class="badge bg-secondary"><?php echo htmlspecialchars($user['role']); ?></span></td>
<td>
<?php if ($user['is_active']): ?>
<span class="badge bg-success">Tak</span>
<?php else: ?>
<span class="badge bg-danger">Nie</span>
<?php endif; ?>
</td>
<td><?php echo htmlspecialchars(date('d.m.Y H:i', strtotime($user['created_at']))); ?></td>
<td>
<a href="edit_user.php?id=<?php echo $user['id']; ?>" class="btn btn-sm btn-secondary">
<i class="bi bi-pencil-fill"></i> Edytuj
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

35
assets/css/custom.css Normal file
View File

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

184
cart.php Normal file
View File

@ -0,0 +1,184 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once 'includes/auth.php';
require_login();
require_once 'includes/helpers.php';
$cart = $_SESSION['cart'] ?? [];
$cart_products = [];
$total_price = 0;
if (!empty($cart)) {
$product_ids = array_keys($cart);
$placeholders = implode(',', array_fill(0, count($product_ids), '?'));
try {
$pdo = db();
$client_id = $_SESSION['client_id'] ?? null;
$params = [];
$sql = 'SELECT p.*, ';
if ($client_id) {
$sql .= 'COALESCE(cp.price, p.price) as final_price FROM products p';
$sql .= ' LEFT JOIN client_prices cp ON p.id = cp.product_id AND cp.client_id = ?';
$params[] = $client_id;
} else {
$sql .= 'p.price as final_price FROM products p';
}
$sql .= " WHERE p.id IN ($placeholders)";
$params = array_merge($params, $product_ids);
$stmt = $pdo->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();
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title); ?> - B2B Commerce</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">
<img src="assets/pasted-20251209-065617-6bf1b4e6.png" alt="Logo" style="height: 40px;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0 align-items-center">
<li class="nav-item">
<a class="nav-link" href="index.php">Katalog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="cart.php">
<i class="bi bi-cart"></i> Koszyk
<span class="badge bg-primary rounded-pill"><?= count($_SESSION['cart'] ?? []) ?></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">Zamówienia</a>
</li>
<?php if ($user_role === 'admin'): ?>
<li class="nav-item">
<a class="nav-link" href="/admin/products.php">Admin</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> Witaj, <?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item p-2" href="profile.php">Profil</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item p-2" href="logout.php">Wyloguj</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="container my-5">
<h1 class="mb-4">Koszyk</h1>
<?php if (empty($cart_products)): ?>
<div class="alert alert-info" role="alert">
Twój koszyk jest pusty.
</div>
<a href="index.php" class="btn btn-primary">Wróć do sklepu</a>
<?php else: ?>
<table class="table">
<thead>
<tr>
<th>Produkt</th>
<th>Cena</th>
<th>Ilość</th>
<th>Razem</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($cart_products as $item): ?>
<tr>
<td><?php echo htmlspecialchars($item['name']); ?></td>
<td><?php echo number_format($item['price'], 2, ',', ' '); ?> zł</td>
<td>
<form action="cart_actions.php" method="POST" class="d-inline-flex align-items-center">
<input type="hidden" name="action" value="update">
<input type="hidden" name="product_id" value="<?php echo $item['id']; ?>">
<input type="hidden" name="redirect_to" value="cart.php">
<label for="quantity-<?php echo $item['id']; ?>" class="visually-hidden">Ilość</label>
<input type="number" id="quantity-<?php echo $item['id']; ?>" name="quantity" value="<?php echo $item['quantity']; ?>" min="1" class="form-control form-control-sm" style="width: 70px;" aria-label="Ilość">
<button type="submit" class="btn btn-secondary ms-2">Zaktualizuj</button>
</form>
</td>
<td><?php echo number_format($item['line_total'], 2, ',', ' '); ?> zł</td>
<td>
<form action="cart_actions.php" method="POST" class="d-inline">
<input type="hidden" name="action" value="remove">
<input type="hidden" name="product_id" value="<?php echo $item['id']; ?>">
<input type="hidden" name="redirect_to" value="cart.php">
<button type="submit" class="btn btn-sm btn-danger" aria-label="Usuń"><i class="bi bi-trash"></i></button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td colspan="3" class="text-end"><strong>Razem:</strong></td>
<td colspan="2"><strong><?php echo number_format($total_price, 2, ',', ' '); ?> zł</strong></td>
</tr>
</tfoot>
</table>
<div class="d-flex justify-content-between mt-4">
<a href="index.php" class="btn btn-outline-secondary">Wróć do sklepu</a>
<a href="checkout.php" class="btn btn-primary">Przejdź do zamówienia</a>
</div>
<?php endif; ?>
</main>
<footer class="text-center py-4 mt-auto text-muted bg-light">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> powered by LEA24. All Rights Reserved.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

63
cart_actions.php Normal file
View File

@ -0,0 +1,63 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once 'includes/lang.php';
require_once 'includes/auth.php';
require_login();
require_once 'includes/helpers.php';
// Initialize cart if it doesn't exist
if (!isset($_SESSION['cart'])) {
$_SESSION['cart'] = [];
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
switch ($action) {
case 'add':
$product_id = isset($_POST['product_id']) ? (int)$_POST['product_id'] : 0;
$quantity = isset($_POST['quantity']) ? (int)$_POST['quantity'] : 1;
if ($product_id > 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;
}

204
checkout.php Normal file
View File

@ -0,0 +1,204 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once 'includes/auth.php';
require_login();
require_once 'includes/helpers.php';
$cart = $_SESSION['cart'] ?? [];
$cart_products = [];
$total_price = 0;
if (empty($cart)) {
header('Location: index.php');
exit;
}
if (!empty($cart)) {
$product_ids = array_keys($cart);
$placeholders = implode(',', array_fill(0, count($product_ids), '?'));
try {
$pdo = db();
$client_id = $_SESSION['client_id'] ?? null;
$credit_info = null;
if ($client_id) {
$stmt = $pdo->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();
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title); ?> - ExtraB2B</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">
<img src="assets/pasted-20251209-065617-6bf1b4e6.png" alt="Logo" style="height: 40px;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0 align-items-center">
<li class="nav-item">
<a class="nav-link" href="index.php">Katalog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="cart.php">
<i class="bi bi-cart"></i> Koszyk
<span class="badge bg-primary rounded-pill"><?= count($_SESSION['cart'] ?? []) ?></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">Zamówienia</a>
</li>
<?php if ($user_role === 'admin'): ?>
<li class="nav-item">
<a class="nav-link" href="/admin/products.php">Admin</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> Witaj, <?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item p-2" href="profile.php">Profil</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item p-2" href="logout.php">Wyloguj</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="container my-5">
<h1 class="mb-4">Podsumowanie zamówienia</h1>
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">Twoje zamówienie</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th>Produkt</th>
<th>Ilość</th>
<th>Cena jedn.</th>
<th>Suma</th>
</tr>
</thead>
<tbody>
<?php foreach ($cart_products as $item): ?>
<tr>
<td><?php echo htmlspecialchars($item['name']); ?></td>
<td><?php echo $item['quantity']; ?></td>
<td><?php echo number_format($item['price'], 2, ',', ' '); ?> zł</td>
<td><?php echo number_format($item['line_total'], 2, ',', ' '); ?> zł</td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td colspan="3" class="text-end"><strong>Suma:</strong></td>
<td><strong><?php echo number_format($total_price, 2, ',', ' '); ?> zł</strong></td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">Opcje dostawy i płatności</div>
<div class="card-body">
<div class="alert alert-secondary">
Źródło dostawy: <strong><?php echo ($delivery_source === 'cs') ? 'Magazyn Centralny' : 'Dostawca zewnętrzny'; ?></strong>
</div>
<?php if ($credit_info && $credit_info['credit_enabled']): ?>
<div class="alert alert-info">
Dostępny kredyt kupiecki: <strong><?php echo number_format($credit_info['credit_balance'], 2, ',', ' '); ?> zł</strong>
</div>
<?php endif; ?>
<form action="order_process.php" method="POST">
<div class="mb-3">
<label for="payment_method" class="form-label">Metoda płatności</label>
<select class="form-select" id="payment_method" name="payment_method" required>
<option value="bank_transfer">Przelew bankowy</option>
<option value="online">Płatność online</option>
<?php if ($credit_info && $credit_info['credit_enabled'] && $credit_info['credit_balance'] >= $total_price): ?>
<option value="credit">Kredyt kupiecki</option>
<?php endif; ?>
</select>
</div>
<div class="mb-3">
<label for="notes" class="form-label">Uwagi do zamówienia</label>
<textarea class="form-control" id="notes" name="notes" rows="3"></textarea>
</div>
<input type="hidden" name="total_amount" value="<?php echo $total_price; ?>">
<button type="submit" class="btn btn-primary w-100">Potwierdź zamówienie</button>
</form>
</div>
</div>
</div>
</div>
</main>
<footer class="text-center py-4 mt-auto text-muted bg-light">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> ExtraB2B. All Rights Reserved.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

39
data/products.php Normal file
View File

@ -0,0 +1,39 @@
<?php
$products = [
[
"id" => 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"
]
];

62
db/migrate.php Normal file
View File

@ -0,0 +1,62 @@
<?php
require_once __DIR__ . '/config.php';
echo "Running migrations...\n";
try {
$pdo = db();
// 1. Create schema_migrations table if it doesn't exist
$pdo->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";

View File

@ -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
);

View File

@ -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
);

View File

@ -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
);

View File

@ -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)
);

View File

@ -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)
);

View File

@ -0,0 +1 @@
ALTER TABLE `users` ADD COLUMN `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;

View File

@ -0,0 +1 @@
ALTER TABLE products ADD COLUMN supplier_id INT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE order_items ADD COLUMN item_status VARCHAR(50) NOT NULL DEFAULT 'pending';

View File

@ -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
);

View File

@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN client_id INT NULL, ADD FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE SET NULL;

View File

@ -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
);

View File

@ -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;

View File

@ -0,0 +1 @@
ALTER TABLE products ADD COLUMN units_per_pallet INT NOT NULL DEFAULT 1;

View File

@ -0,0 +1 @@
ALTER TABLE orders ADD COLUMN delivery_source VARCHAR(20) NOT NULL DEFAULT 'cs';

View File

@ -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);

View File

@ -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
);

View File

@ -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)
);

View File

@ -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
);

View File

@ -0,0 +1 @@
ALTER TABLE products DROP COLUMN image_url;

View File

@ -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
);

View File

@ -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
);

View File

@ -0,0 +1 @@
ALTER TABLE products ADD COLUMN product_role ENUM('membrana', 'akcesoria') NOT NULL DEFAULT 'membrana';

View File

@ -0,0 +1 @@
ALTER TABLE products ADD COLUMN unit VARCHAR(10) NOT NULL DEFAULT 'szt';

View File

@ -0,0 +1,3 @@
ALTER TABLE products
ADD COLUMN price_net DECIMAL(12,2) NULL,
ADD COLUMN price_gross DECIMAL(12,2) NULL;

59
includes/auth.php Normal file
View File

@ -0,0 +1,59 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/../db/config.php';
function login($email, $password) {
$pdo = db();
$stmt = $pdo->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');
}
}
}

5
includes/footer.php Normal file
View File

@ -0,0 +1,5 @@
<footer class="text-center py-4 mt-auto text-muted bg-light">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> <?php echo t('footer_text'); ?>. All Rights Reserved.</p>
</div>
</footer>

287
includes/header.php Normal file
View File

@ -0,0 +1,287 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Language selection logic
if (isset($_GET['lang'])) {
$_SESSION['lang'] = $_GET['lang'] === 'en' ? 'en' : 'pl';
// Redirect to the same page without the lang parameter
header('Location: ' . strtok($_SERVER["REQUEST_URI"], '?'));
exit;
}
$lang = $_SESSION['lang'] ?? 'pl';
$translations = [
'pl' => [
'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();
?>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">
<img src="assets/pasted-20251209-065617-6bf1b4e6.png" alt="Logo" style="height: 40px;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0 align-items-center">
<li class="nav-item">
<a class="nav-link" href="index.php"><?= t('menu_catalog') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" href="cart.php">
<i class="bi bi-cart"></i> <?= t('title_cart') ?>
<span class="badge bg-primary rounded-pill"><?= count($_SESSION['cart'] ?? []) ?></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php"><?= t('menu_orders') ?></a>
</li>
<?php if ($user_role === 'admin'): ?>
<li class="nav-item">
<a class="nav-link" href="/admin/products.php">Admin</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> <?= t('header_welcome') ?>, <?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item p-2" href="profile.php"><?= t('menu_profile') ?></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item p-2" href="logout.php"><?= t('menu_logout') ?></a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="langDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-globe"></i> <?= strtoupper($current_lang) ?>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="langDropdown">
<li><a class="dropdown-item<?php if ($current_lang === 'pl') echo ' active'; ?>" href="?lang=pl">Polski (PL)</a></li>
<li><a class="dropdown-item<?php if ($current_lang === 'en') echo ' active'; ?>" href="?lang=en">English (EN)</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>

77
includes/helpers.php Normal file
View File

@ -0,0 +1,77 @@
<?php
// includes/helpers.php
/**
* Translates a status or payment method key into a human-readable string using the t() function.
*
* @param string $key The key to translate (e.g., 'pending_payment', 'bank_transfer').
* @return string The translated string.
*/
function get_status_translation(string $key): string {
// A set of known payment methods to prefix correctly.
$payment_methods = ['bank_transfer', 'online', 'credit'];
if (in_array($key, $payment_methods)) {
// It's a payment method, use 'payment_' prefix.
$translation_key = 'payment_' . $key;
} else {
// Assume it's an order status, use 'status_' prefix.
$translation_key = 'status_' . $key;
}
$translated = t($translation_key);
// If the translation key is returned, it means no translation was found.
// In that case, we provide a clean fallback.
if ($translated === $translation_key) {
return ucfirst(str_replace('_', ' ', $key));
}
return $translated;
}
function sanitize_filename($filename) {
// Keep the original extension
$extension = pathinfo($filename, PATHINFO_EXTENSION);
$filename_without_ext = pathinfo($filename, PATHINFO_FILENAME);
// Replace spaces and special characters with underscores in the filename part
$sanitized_filename = preg_replace('/[\s\\\/:*?"<>|.]+/', '_', $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';
}
}

232
includes/lang.php Normal file
View File

@ -0,0 +1,232 @@
<?php
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Language selection logic
if (isset($_GET['lang'])) {
$_SESSION['lang'] = $_GET['lang'] === 'en' ? 'en' : 'pl';
// Redirect to the same page without the lang parameter
header('Location: ' . strtok($_SERVER["REQUEST_URI"], '?'));
exit;
}
$lang = $_SESSION['lang'] ?? 'pl';
$translations = [
'pl' => [
'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;
}
?>

View File

@ -0,0 +1,79 @@
<?php
// includes/status_updater.php
require_once __DIR__ . '/../db/config.php';
/**
* Recalculates and updates the overall order status based on its items' statuses.
*
* @param int $order_id The ID of the order to update.
* @return bool True on success, false on failure or if no update was needed.
*/
function update_order_status($order_id) {
$pdo = db();
try {
// Start a transaction
$pdo->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;
}
}

361
index.php
View File

@ -1,150 +1,227 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
session_start();
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
require_once 'includes/auth.php';
require_login();
require_once 'includes/helpers.php';
try {
$pdo = db();
// Fetch products and their primary images
$sql = "SELECT
p.*,
COALESCE(cp.price, p.price_gross) as final_price,
p.price_net as final_price_net,
(SELECT file_path
FROM product_images
WHERE product_id = p.id
ORDER BY is_primary DESC, id ASC
LIMIT 1) AS image_path
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.is_active = 1
ORDER BY
CASE p.product_role WHEN 'membrana' THEN 1 WHEN 'akcesoria' THEN 2 ELSE 3 END, p.name ASC";
$stmt = $pdo->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';
?>
<!doctype html>
<html lang="en">
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($page_title) ?> - B2B Commerce</title>
<!-- SEO Meta Tags -->
<meta name="description" content="<?= htmlspecialchars($_SERVER['PROJECT_DESCRIPTION'] ?? 'A B2B E-commerce Platform') ?>">
<!-- Open Graph / Twitter Meta Tags (managed by the platform) -->
<meta property="og:title" content="<?= htmlspecialchars($page_title) ?>">
<meta property="og:description" content="<?= htmlspecialchars($_SERVER['PROJECT_DESCRIPTION'] ?? 'A B2B E-commerce Platform') ?>">
<meta property="og:image" content="<?= htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '') ?>">
<meta name="twitter:card" content="summary_large_image">
<!-- Bootstrap 5.3 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
</head>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">
<img src="assets/pasted-20251209-065617-6bf1b4e6.png" alt="Logo" style="height: 40px;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0 align-items-center">
<li class="nav-item">
<a class="nav-link" href="index.php">Katalog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="cart.php">
<i class="bi bi-cart"></i> Koszyk
<span class="badge bg-primary rounded-pill"><?= count($_SESSION['cart'] ?? []) ?></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">Zamówienia</a>
</li>
<?php if ($user_role === 'admin'): ?>
<li class="nav-item">
<a class="nav-link" href="/admin/products.php">Admin</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> Witaj, <?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item p-2" href="profile.php">Profil</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item p-2" href="logout.php">Wyloguj</a></li>
</ul>
</li>
</ul>
</div>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
</nav>
<main class="container my-5">
<h1 class="mb-4">Katalog</h1>
<?php if (!empty($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-4">
<?php foreach ($main_products as $product):
$image_url = !empty($product['image_path'])
? 'uploads/products/' . htmlspecialchars($product['image_path'])
: 'https://placehold.co/600x400/EEE/31343C?text=Brak+zdj%C4%99cia';
?>
<div class="col">
<div class="card h-100 product-card shadow-sm">
<a href="product.php?id=<?= (int)$product['id'] ?>">
<img src="<?= $image_url ?>" class="card-img-top" alt="<?= htmlspecialchars($product['name']) ?>" style="height: 200px; object-fit: cover;">
</a>
<div class="card-body d-flex flex-column">
<h5 class="card-title">
<a href="product.php?id=<?= (int)$product['id'] ?>" class="text-decoration-none text-dark">
<?= htmlspecialchars($product['name']) ?>
</a>
</h5>
<p class="card-text text-secondary small flex-grow-1"><?php
$desc = $product['description'];
echo htmlspecialchars(strlen($desc) > 100 ? substr($desc, 0, 100) . '...' : $desc);
?></p>
<p class="card-text fw-bold fs-5 mt-auto"><?= htmlspecialchars(number_format($product['final_price'], 2, ',', ' ')) ?> PLN / <?= htmlspecialchars($product['unit']) ?></p>
</div>
<div class="card-footer bg-white border-top-0 pb-3">
<form action="cart_actions.php" method="POST" class="d-grid">
<input type="hidden" name="action" value="add">
<input type="hidden" name="product_id" value="<?= $product['id'] ?>">
<input type="hidden" name="redirect_to" value="index.php">
<div class="input-group">
<input type="number" class="form-control" name="quantity" value="1" min="1" aria-label="Ilość" step="1">
<button type="submit" class="btn btn-primary">
<i class="bi bi-cart-plus"></i>
</button>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php if (!empty($accessories)): ?>
<hr class="my-5">
<h2 class="mb-4">Akcesoria i produkty uzupełniające</h2>
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 row-cols-lg-5 g-4">
<?php foreach ($accessories as $product):
$image_url = !empty($product['image_path'])
? 'uploads/products/' . htmlspecialchars($product['image_path'])
: 'https://placehold.co/600x400/EEE/31343C?text=Brak+zdj%C4%99cia';
?>
<div class="col">
<div class="card h-100 product-card shadow-sm">
<a href="product.php?id=<?= (int)$product['id'] ?>">
<img src="<?= $image_url ?>" class="card-img-top" alt="<?= htmlspecialchars($product['name']) ?>" style="height: 150px; object-fit: cover;">
</a>
<div class="card-body d-flex flex-column">
<h6 class="card-title">
<a href="product.php?id=<?= (int)$product['id'] ?>" class="text-decoration-none text-dark">
<?= htmlspecialchars($product['name']) ?>
</a>
</h6>
<p class="card-text fw-bold mt-auto"><?= htmlspecialchars(number_format($product['final_price'], 2, ',', ' ')) ?> PLN / <?= htmlspecialchars($product['unit']) ?></p>
</div>
<div class="card-footer bg-white border-top-0 pb-3">
<form action="cart_actions.php" method="POST" class="d-grid">
<input type="hidden" name="action" value="add">
<input type="hidden" name="product_id" value="<?= $product['id'] ?>">
<input type="hidden" name="redirect_to" value="index.php">
<div class="input-group">
<input type="number" class="form-control form-control-sm" name="quantity" value="1" min="1" aria-label="Ilość" step="1">
<button type="submit" class="btn btn-sm btn-primary">
<i class="bi bi-cart-plus"></i>
</button>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</main>
<footer class="text-center py-4 mt-auto text-muted bg-light">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> powered by LEA24. All Rights Reserved.</p>
</div>
</footer>
<!-- Bootstrap 5.3 JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
</html>

99
login.php Normal file
View File

@ -0,0 +1,99 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/includes/auth.php';
require_once __DIR__ . '/includes/helpers.php';
$lang = 'pl'; // Hardcoded to Polish
$error = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
$role = login($email, $password);
if ($role) {
switch ($role) {
case 'admin':
case 'finance':
case 'support':
header('Location: /admin/products.php');
break;
case 'client':
header('Location: /index.php');
break;
case 'supplier':
// Redirect to a future supplier panel
header('Location: /index.php'); // Placeholder
break;
default:
header('Location: /index.php');
break;
}
exit();
} else {
$error = 'Nieprawidłowy email lub hasło.';
}
}
// If user is already logged in, redirect them
if (is_logged_in()) {
$role = get_user_role();
switch ($role) {
case 'admin':
case 'finance':
case 'support':
header('Location: /admin/products.php');
break;
default:
header('Location: /index.php');
break;
}
exit();
}
$page_title = 'Logowanie';
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title); ?> - B2B Commerce</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<main class="container my-5">
<div class="row justify-content-center">
<div class="col-md-6 col-lg-4">
<div class="card mt-5">
<div class="card-body">
<div class="text-center mb-4">
<img src="/assets/pasted-20251209-065617-6bf1b4e6.png" alt="Logo" style="width: 150px;">
</div>
<h1 class="card-title text-center mb-4"><?php echo $page_title; ?></h1>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<form method="POST">
<div class="mb-3">
<label for="email" class="form-label">Adres e-mail</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Hasło</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Zaloguj</button>
</form>
</div>
</div>
</div>
</div>
</main>
</body>
</html>

9
logout.php Normal file
View File

@ -0,0 +1,9 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/includes/auth.php';
logout();
header('Location: /login.php');
exit();

93
order_confirmation.php Normal file
View File

@ -0,0 +1,93 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once 'includes/auth.php';
require_login();
require_once 'includes/helpers.php';
// Get order ID from session and then clear it
$order_id = $_SESSION['latest_order_id'] ?? null;
unset($_SESSION['latest_order_id']);
if (!$order_id) {
header('Location: index.php');
exit;
}
$page_title = 'Potwierdzenie zamówienia';
$user_role = get_user_role();
$lang = 'pl';
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title); ?> - B2B Commerce</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">
<img src="assets/pasted-20251209-065617-6bf1b4e6.png" alt="Logo" style="height: 40px;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0 align-items-center">
<li class="nav-item">
<a class="nav-link" href="index.php">Katalog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="cart.php">
<i class="bi bi-cart"></i> Koszyk
<span class="badge bg-primary rounded-pill"><?= count($_SESSION['cart'] ?? []) ?></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">Zamówienia</a>
</li>
<?php if ($user_role === 'admin'): ?>
<li class="nav-item">
<a class="nav-link" href="/admin/products.php">Admin</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> Witaj, <?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item p-2" href="profile.php">Profil</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item p-2" href="logout.php">Wyloguj</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="container my-5 text-center">
<div class="alert alert-success" role="alert">
<h4 class="alert-heading">Dziękujemy za złożenie zamówienia.</h4>
<p>Numer Twojego zamówienia to <strong>#<?php echo htmlspecialchars($order_id); ?></strong>.</p>
<hr>
<p class="mb-0">Możesz śledzić jego status w panelu <a href="orders.php" class="alert-link">Moje zamówienia</a>.</p>
</div>
<a href="index.php" class="btn btn-primary mt-3">Kontynuuj zakupy</a>
</main>
<footer class="text-center py-4 mt-auto text-muted bg-light">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> powered by LEA24. All Rights Reserved.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

212
order_details.php Normal file
View File

@ -0,0 +1,212 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once 'includes/auth.php';
require_login();
require_once 'includes/helpers.php';
function get_polish_status_translation(string $key): string {
$translations = [
'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',
'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';
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title); ?> - B2B Commerce</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">
<img src="assets/pasted-20251209-065617-6bf1b4e6.png" alt="Logo" style="height: 40px;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0 align-items-center">
<li class="nav-item">
<a class="nav-link" href="index.php">Katalog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="cart.php">
<i class="bi bi-cart"></i> Koszyk
<span class="badge bg-primary rounded-pill"><?= count($_SESSION['cart'] ?? []) ?></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">Zamówienia</a>
</li>
<?php if ($user_role === 'admin'): ?>
<li class="nav-item">
<a class="nav-link" href="/admin/products.php">Admin</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> Witaj, <?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item p-2" href="profile.php">Profil</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item p-2" href="logout.php">Wyloguj</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="container my-5">
<?php if ($error_message): ?>
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle-fill"></i> <?php echo htmlspecialchars($error_message); ?>
</div>
<a href="orders.php" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> Wróć do listy zamówień</a>
<?php elseif ($order): ?>
<h1 class="mb-4"><?php echo htmlspecialchars($page_title); ?></h1>
<div class="card mb-4">
<div class="card-header">Podsumowanie</div>
<div class="card-body">
<p><strong>Data zamówienia:</strong> <?php echo date('d.m.Y H:i', strtotime($order['created_at'])); ?></p>
<p><strong>Status:</strong> <span class="badge bg-info"><?php echo htmlspecialchars(get_polish_status_translation($order['status'])); ?></span></p>
<p><strong>Metoda płatności:</strong> <?php echo htmlspecialchars(get_polish_status_translation($order['payment_method'])); ?></p>
<p><strong>Suma:</strong> <?php echo number_format($order['total_amount'], 2, ',', ' '); ?> zł</p>
<p><strong>Uwagi:</strong> <?php echo nl2br(htmlspecialchars($order['notes'])); ?></p>
</div>
</div>
<div class="card">
<div class="card-header">Szczegóły zamówienia</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th>Zdjęcie</th>
<th>Produkt</th>
<th>Cena jednostkowa</th>
<th>Ilość</th>
<th>Suma częściowa</th>
</tr>
</thead>
<tbody>
<?php foreach ($order_items as $item):
$image_url = $product_images[$item['product_id']] ?? 'https://placehold.co/100x100/EEE/31343C?text=' . urlencode('Brak zdjęcia');
?>
<tr>
<td><img src="<?php echo htmlspecialchars($image_url); ?>" alt="<?php echo htmlspecialchars($item['product_name']); ?>" style="width: 50px; height: 50px; object-fit: cover;"></td>
<td><?php echo htmlspecialchars($item['product_name']); ?></td>
<td><?php echo number_format($item['unit_price'], 2, ',', ' '); ?> zł</td>
<td><?php echo $item['quantity']; ?></td>
<td><?php echo number_format($item['line_total'], 2, ',', ' '); ?> zł</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="mt-4">
<a href="orders.php" class="btn btn-secondary"><i class="bi bi-arrow-left"></i> Wróć do listy zamówień</a>
</div>
<?php endif; ?>
</main>
<footer class="text-center py-4 mt-auto text-muted bg-light">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> powered by LEA24. All Rights Reserved.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

128
order_process.php Normal file
View File

@ -0,0 +1,128 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once 'includes/lang.php';
require_once 'includes/auth.php';
require_login();
require_once 'includes/helpers.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: checkout.php');
exit;
}
$cart = $_SESSION['cart'] ?? [];
if (empty($cart)) {
header('Location: index.php');
exit;
}
$pdo = db();
try {
$pdo->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());
}

159
orders.php Normal file
View File

@ -0,0 +1,159 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once 'includes/auth.php';
require_login();
require_once 'includes/helpers.php';
function get_polish_status_translation(string $key): string {
$translations = [
'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',
'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';
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title); ?> - B2B Commerce</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">
<img src="assets/pasted-20251209-065617-6bf1b4e6.png" alt="Logo" style="height: 40px;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0 align-items-center">
<li class="nav-item">
<a class="nav-link" href="index.php">Katalog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="cart.php">
<i class="bi bi-cart"></i> Koszyk
<span class="badge bg-primary rounded-pill"><?= count($_SESSION['cart'] ?? []) ?></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">Zamówienia</a>
</li>
<?php if ($user_role === 'admin'): ?>
<li class="nav-item">
<a class="nav-link" href="/admin/products.php">Admin</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> Witaj, <?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item p-2" href="profile.php">Profil</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item p-2" href="logout.php">Wyloguj</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="container my-5">
<h1 class="mb-4">Historia zamówień</h1>
<?php if (!empty($error_message)): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($error_message); ?>
</div>
<?php elseif (empty($orders)): ?>
<div class="alert alert-info" role="alert">
Nie masz jeszcze żadnych zamówień.
</div>
<?php else: ?>
<table class="table table-hover">
<thead>
<tr>
<th>Numer zamówienia</th>
<th>Data zamówienia</th>
<th>Status</th>
<th>Suma</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($orders as $order): ?>
<tr>
<td>#<?php echo $order['id']; ?></td>
<td><?php echo date('d.m.Y H:i', strtotime($order['created_at'])); ?></td>
<td><span class="badge bg-info"><?php echo htmlspecialchars(get_polish_status_translation($order['status'])); ?></span></td>
<td><?php echo number_format($order['total_amount'], 2, ',', ' '); ?> zł</td>
<td>
<a href="order_details.php?id=<?php echo $order['id']; ?>" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i> Szczegóły
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</main>
<footer class="text-center py-4 mt-auto text-muted bg-light">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> powered by LEA24. All Rights Reserved.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

284
product.php Normal file
View File

@ -0,0 +1,284 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once 'includes/auth.php';
require_login();
require_once 'includes/helpers.php';
$product_id = $_GET['id'] ?? null;
if (!$product_id) {
header('Location: index.php');
exit;
}
try {
$pdo = db();
$stmt = $pdo->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']);
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $page_title; ?> - ExtraB2B</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<?php
// Note: This header is a modified, inline version for product.php to remove language features.
$user_role = get_user_role();
?>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">
<img src="assets/pasted-20251209-065617-6bf1b4e6.png" alt="Logo" style="height: 40px;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Przełącz nawigację">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0 align-items-center">
<li class="nav-item">
<a class="nav-link" href="index.php">Katalog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="cart.php">
<i class="bi bi-cart"></i> Koszyk
<span class="badge bg-primary rounded-pill"><?= count($_SESSION['cart'] ?? []) ?></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">Zamówienia</a>
</li>
<?php if ($user_role === 'admin'): ?>
<li class="nav-item">
<a class="nav-link" href="/admin/products.php">Admin</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> Witaj, <?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item p-2" href="profile.php">Profil</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item p-2" href="logout.php">Wyloguj</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="container my-5">
<a href="index.php" class="btn btn-outline-secondary mb-3"> Wróć do listy produktów </a>
<div class="row">
<!-- Product Image Gallery -->
<div class="col-lg-6 mb-4 mb-lg-0">
<div class="text-center">
<?php
$primary_image_url = 'https://placehold.co/600x400/EEE/31343C?text=Brak+zdj%C4%99cia';
if (!empty($product_images)) {
$primary_image_url = 'uploads/products/' . htmlspecialchars($product_images[0]['file_path']);
}
?>
<img src="<?= $primary_image_url ?>" alt="<?= htmlspecialchars($product['name']) ?>" class="img-fluid rounded shadow-sm mb-3" id="main-product-image">
</div>
<?php if (count($product_images) > 1): ?>
<div class="row gx-2 justify-content-center">
<?php foreach ($product_images as $image): ?>
<div class="col-2">
<a href="uploads/products/<?= htmlspecialchars($image['file_path']) ?>" class="product-thumbnail d-block border rounded">
<img src="uploads/products/<?= htmlspecialchars($image['file_path']) ?>" alt="Miniatura produktu" class="img-fluid">
</a>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- Product Details -->
<div class="col-lg-6">
<h1 class="mb-3"><?= htmlspecialchars($product['name']) ?></h1>
<div class="bg-light p-4 rounded mb-4">
<h2 class="h4 fw-bold"><?= htmlspecialchars(number_format($product['final_price'], 2, ',', ' ')) ?> PLN / <?= htmlspecialchars($product['unit']) ?></h2>
<small class="text-muted">Cena brutto</small>
<p class="mb-0"><?= htmlspecialchars(number_format($product['final_price_net'], 2, ',', ' ')) ?> PLN netto</p>
</div>
<form action="cart_actions.php" method="post" class="d-flex align-items-center">
<input type="hidden" name="action" value="add">
<input type="hidden" name="product_id" value="<?= $product['id'] ?>">
<div style="max-width: 200px;" class="me-3">
<label for="quantity" class="form-label">Ilość (<?= htmlspecialchars($product['unit']) ?>):</label>
<input type="number" id="quantity" name="quantity" class="form-control" value="1" min="1">
</div>
<button type="submit" class="btn btn-primary mt-4">
<i class="bi bi-cart-plus"></i> Dodaj do koszyka
</button>
</form>
</div>
</div>
<!-- Additional Info Tabs -->
<div class="mt-5">
<ul class="nav nav-tabs" id="productTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="description-tab" data-bs-toggle="tab" data-bs-target="#description" type="button" role="tab" aria-controls="description" aria-selected="true">Opis</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="specs-tab" data-bs-toggle="tab" data-bs-target="#specs" type="button" role="tab" aria-controls="specs" aria-selected="false">Dane techniczne</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="documents-tab" data-bs-toggle="tab" data-bs-target="#documents" type="button" role="tab" aria-controls="documents" aria-selected="false">Dokumenty</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="related-tab" data-bs-toggle="tab" data-bs-target="#related" type="button" role="tab" aria-controls="related" aria-selected="false">Produkty powiązane</button>
</li>
</ul>
<div class="tab-content p-3 border border-top-0" id="productTabsContent">
<div class="tab-pane fade show active" id="description" role="tabpanel" aria-labelledby="description-tab">
<p class="lead mb-4"><?= nl2br(htmlspecialchars($product['description'])) ?></p>
</div>
<div class="tab-pane fade" id="specs" role="tabpanel" aria-labelledby="specs-tab">
<?php
$attrs_stmt = $pdo->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 '<table class="table table-striped">';
echo '<tbody>';
foreach ($product_attributes as $attr) {
echo '<tr>';
echo '<th>' . htmlspecialchars($attr['name']) . '</th>';
echo '<td>' . htmlspecialchars($attr['value']) . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
} else {
echo '<p>Brak dodatkowych danych technicznych.</p>';
}
?>
</div>
<div class="tab-pane fade" id="documents" role="tabpanel" aria-labelledby="documents-tab">
<?php
$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 ($product_documents) {
echo '<ul class="list-group list-group-flush">';
foreach ($product_documents as $doc) {
echo '<li class="list-group-item"><a href="uploads/documents/' . htmlspecialchars($doc['file_path']) . '" download><i class="bi bi-file-earmark-arrow-down"></i> ' . htmlspecialchars($doc['file_name']) . '</a></li>';
}
echo '</ul>';
} else {
echo '<p class="mb-0">Brak dokumentów do pobrania.</p>';
}
?>
</div>
<div class="tab-pane fade" id="related" role="tabpanel" aria-labelledby="related-tab">
<div class="row">
<?php
$related_sql = "SELECT
p.*,
(SELECT file_path FROM product_images WHERE product_id = p.id ORDER BY is_primary DESC, id ASC LIMIT 1) AS image_path
FROM products p
JOIN product_relations pr ON p.id = pr.related_product_id
WHERE pr.product_id = ?";
$related_products_stmt = $pdo->prepare($related_sql);
$related_products_stmt->execute([$product_id]);
$related_products = $related_products_stmt->fetchAll(PDO::FETCH_ASSOC);
if ($related_products) {
foreach ($related_products as $related_product) {
$related_image_url = !empty($related_product['image_path'])
? 'uploads/products/' . htmlspecialchars($related_product['image_path'])
: 'https://placehold.co/300x300/EEE/31343C?text=Brak+zdj%C4%99cia';
echo '<div class="col-md-3 mb-3">';
echo '<div class="card h-100 product-card shadow-sm">';
echo '<a href="product.php?id=' . $related_product['id'] . '">';
echo '<img src="' . $related_image_url . '" class="card-img-top" alt="' . htmlspecialchars($related_product['name']) . '" style="height: 150px; object-fit: cover;">';
echo '</a>';
echo '<div class="card-body">';
echo '<h6 class="card-title"><a href="product.php?id=' . $related_product['id'] . '" class="text-decoration-none text-dark stretched-link">' . htmlspecialchars($related_product['name']) . '</a></h6>';
echo '</div>';
echo '</div>';
echo '</div>';
}
} else {
echo '<p class="mb-0">Brak produktów powiązanych.</p>';
}
?>
</div>
</div>
</div>
</div>
</main>
<footer class="text-center py-4 mt-auto text-muted bg-light">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> powered by LEA24. All Rights Reserved.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const mainImage = document.getElementById('main-product-image');
const thumbnails = document.querySelectorAll('.product-thumbnail');
thumbnails.forEach(thumbnail => {
thumbnail.addEventListener('click', function(event) {
event.preventDefault();
mainImage.src = this.href;
thumbnails.forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
});
</script>
</body>
</html>

134
profile.php Normal file
View File

@ -0,0 +1,134 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once 'includes/auth.php';
require_login();
require_once 'includes/helpers.php';
$page_title = 'Profil użytkownika';
$user_role = get_user_role();
$user_id = $_SESSION['user_id'];
$client_id = $_SESSION['client_id'] ?? null;
$user_email = '';
$client_name = '';
$error_message = '';
try {
$pdo = db();
// Fetch user email
$stmt = $pdo->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';
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($page_title); ?> - B2B Commerce</title>
<meta name="description" content="Zarządzaj swoim profilem w platformie B2B Commerce.">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">
<img src="assets/pasted-20251209-065617-6bf1b4e6.png" alt="Logo" style="height: 40px;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0 align-items-center">
<li class="nav-item">
<a class="nav-link" href="index.php">Katalog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="cart.php">
<i class="bi bi-cart"></i> Koszyk
<span class="badge bg-primary rounded-pill"><?= count($_SESSION['cart'] ?? []) ?></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">Zamówienia</a>
</li>
<?php if ($user_role === 'admin'): ?>
<li class="nav-item">
<a class="nav-link" href="/admin/products.php">Admin</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> Witaj, <?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item p-2" href="profile.php">Profil</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item p-2" href="logout.php">Wyloguj</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="container my-5">
<h1 class="mb-4"><?php echo htmlspecialchars($page_title); ?></h1>
<?php if ($error_message): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
<?php else: ?>
<div class="card">
<div class="card-body">
<h5 class="card-title">Witaj, <?php echo htmlspecialchars($_SESSION['username']); ?></h5>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<strong>Adres e-mail:</strong> <?php echo htmlspecialchars($user_email); ?>
</li>
<?php if ($client_name): ?>
<li class="list-group-item">
<strong>Klient:</strong> <?php echo htmlspecialchars($client_name); ?>
</li>
<?php endif; ?>
</ul>
</div>
</div>
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title">Zarządzanie hasłem</h5>
<p class="card-text">Funkcja w przygotowaniu.</p>
</div>
</div>
<?php endif; ?>
</main>
<footer class="text-center py-4 mt-auto text-muted bg-light">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> powered by LEA24. All Rights Reserved.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

239
related_suggestions.php Normal file
View File

@ -0,0 +1,239 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once 'includes/auth.php';
require_login();
require_once 'db/config.php';
require_once 'includes/helpers.php';
// Use product_id and qty from the URL
$product_id = isset($_GET['product_id']) ? (int)$_GET['product_id'] : 0;
$added_qty = isset($_GET['qty']) ? (int)$_GET['qty'] : 0;
// If product_id is invalid, redirect to cart
if ($product_id === 0) {
header('Location: cart.php');
exit;
}
$db = db();
$user_id = $_SESSION['user_id'];
// Fetch details for the added product, including the primary image
$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
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';
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($page_title) ?> - B2B Commerce</title>
<!-- SEO Meta Tags -->
<meta name="description" content="<?= htmlspecialchars($_SERVER['PROJECT_DESCRIPTION'] ?? 'A B2B E-commerce Platform') ?>">
<!-- Open Graph / Twitter Meta Tags (managed by the platform) -->
<meta property="og:title" content="<?= htmlspecialchars($page_title) ?>">
<meta property="og:description" content="<?= htmlspecialchars($_SERVER['PROJECT_DESCRIPTION'] ?? 'A B2B E-commerce Platform') ?>">
<meta property="og:image" content="<?= htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '') ?>">
<meta name="twitter:card" content="summary_large_image">
<!-- Bootstrap 5.3 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">
<img src="assets/pasted-20251209-065617-6bf1b4e6.png" alt="Logo" style="height: 40px;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0 align-items-center">
<li class="nav-item">
<a class="nav-link" href="index.php">Katalog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="cart.php">
<i class="bi bi-cart"></i> Koszyk
<span class="badge bg-primary rounded-pill"><?= count($_SESSION['cart'] ?? []) ?></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="orders.php">Zamówienia</a>
</li>
<?php if ($user_role === 'admin'): ?>
<li class="nav-item">
<a class="nav-link" href="/admin/products.php">Admin</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> Witaj, <?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item p-2" href="profile.php">Profil</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item p-2" href="logout.php">Wyloguj</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="container my-5">
<div class="alert alert-success text-center">
<strong>Produkt został pomyślnie dodany do koszyka!</strong>
</div>
<?php if ($added_product): ?>
<div class="card shadow-sm mb-4">
<div class="card-header">
<h4 class="mb-0">Dodałeś do koszyka:</h4>
</div>
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-2 text-center">
<img src="<?= htmlspecialchars($added_product['primary_image']); ?>" class="img-fluid rounded" style="max-height: 120px;" alt="<?= htmlspecialchars($added_product['name']); ?>">
</div>
<div class="col-md-5">
<h5 class="mb-1"><?= htmlspecialchars($added_product['name']); ?></h5>
<?php if ($added_qty > 0): ?>
<small class="text-muted">Ilość: <?= $added_qty; ?></small>
<?php endif; ?>
</div>
<div class="col-md-5">
<div class="d-flex justify-content-end align-items-center">
<div class="text-end">
<p class="mb-0 h5"><strong><?= number_format($added_product['final_price'], 2, ',', ' '); ?> zł</strong> <small>brutto</small></p>
<p class="mb-0 text-muted"><?= number_format($added_product['price_net'], 2, ',', ' '); ?> zł <small>netto</small></p>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if (!empty($related_products)): ?>
<h3 class="mt-5 mb-4">Polecamy także produkty powiązane:</h3>
<div class="list-group">
<?php foreach ($related_products as $product): ?>
<div class="list-group-item list-group-item-action">
<div class="row align-items-center">
<div class="col-md-2 text-center">
<a href="product.php?id=<?= $product['id']; ?>">
<img src="<?= htmlspecialchars(!empty($product['primary_image']) ? $product['primary_image'] : 'assets/pasted-20251212-131440-62c0087c.jpg'); ?>" class="img-fluid rounded" style="max-height: 100px;" alt="<?= htmlspecialchars($product['name']); ?>">
</a>
</div>
<div class="col-md-4">
<a href="product.php?id=<?= $product['id']; ?>" class="text-decoration-none text-dark">
<h5 class="mb-1"><?= htmlspecialchars($product['name']); ?></h5>
</a>
<p class="mb-1">
<?php if (!empty($product['unit'])): ?>
<small class="text-muted">Jednostka: <?= htmlspecialchars($product['unit']); ?></small>
<?php endif; ?>
</p>
</div>
<div class="col-md-3">
<div class="text-end">
<p class="mb-0 h5"><strong><?= number_format($product['final_price'], 2, ',', ' '); ?> zł</strong> <small>brutto</small></p>
<p class="mb-0 text-muted"><?= number_format($product['price_net'], 2, ',', ' '); ?> zł <small>netto</small></p>
</div>
</div>
<div class="col-md-3">
<form action="cart_actions.php" method="post" class="d-flex justify-content-end">
<input type="hidden" name="action" value="add">
<input type="hidden" name="product_id" value="<?= $product['id']; ?>">
<!-- Correctly redirect back to this suggestion page -->
<input type="hidden" name="redirect_to" value="related_suggestions.php?product_id=<?= $product_id; ?>">
<div class="input-group" style="max-width: 180px;">
<input type="number" name="quantity" class="form-control" value="1" min="1">
<button type="submit" class="btn btn-primary"><i class="bi bi-cart-plus"></i></button>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="mt-5 d-flex justify-content-between">
<a href="index.php" class="btn btn-outline-secondary btn-lg"> <i class="bi bi-arrow-left"></i> Kontynuuj zakupy</a>
<a href="cart.php" class="btn btn-success btn-lg">Przejdź do koszyka <i class="bi bi-arrow-right"></i></a>
</div>
</main>
<footer class="text-center py-4 mt-auto text-muted bg-light">
<div class="container">
<p class="mb-0">&copy; <?php echo date("Y"); ?> powered by LEA24. All Rights Reserved.</p>
</div>
</footer>
<!-- Bootstrap 5.3 JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

113
supplier/order_items.php Normal file
View File

@ -0,0 +1,113 @@
<?php
session_start();
require_once __DIR__ . '/../includes/lang.php';
require_once __DIR__ . '/../includes/auth.php';
require_role('supplier');
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/helpers.php';
require_once __DIR__ . '/../includes/status_updater.php';
$supplier_id = $_SESSION['user_id']; // Assuming the supplier is a user and their ID is in the session
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['order_item_id'], $_POST['item_status'])) {
$order_item_id = $_POST['order_item_id'];
$item_status = $_POST['item_status'];
$stmt_get_order = db()->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';
?>
<main class="container mt-4">
<div class="card">
<div class="card-header">
<h1><?php echo $pageTitle; ?></h1>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th><?php echo t('order_id'); ?></th>
<th><?php echo t('product'); ?></th>
<th><?php echo t('quantity'); ?></th>
<th><?php echo t('status'); ?></th>
<th><?php echo t('last_updated'); ?></th>
<th><?php echo t('action'); ?></th>
</tr>
</thead>
<tbody>
<?php if (empty($order_items)): ?>
<tr>
<td colspan="6" class="text-center"><?php echo t('no_order_items_found'); ?></td>
</tr>
<?php else: ?>
<?php foreach ($order_items as $item): ?>
<tr>
<td><a href="/order_details.php?id=<?php echo $item['order_id']; ?>"><?php echo htmlspecialchars($item['order_id']); ?></a></td>
<td><?php echo htmlspecialchars($item['product_name']); ?></td>
<td><?php echo htmlspecialchars($item['quantity']); ?></td>
<td><span class="badge bg-info"><?php echo htmlspecialchars(get_item_status_translation($item['item_status'])); ?></span></td>
<td><?php echo htmlspecialchars($item['updated_at']); ?></td>
<td>
<form method="POST" action="/supplier/order_items.php" class="d-flex">
<input type="hidden" name="order_item_id" value="<?php echo $item['order_item_id']; ?>">
<select name="item_status" class="form-select form-select-sm me-2">
<?php foreach ($item_statuses as $status): ?>
<option value="<?php echo $status; ?>" <?php echo ($item['item_status'] === $status) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars(get_item_status_translation($status)); ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit" class="btn btn-primary btn-sm"><?php echo t('save'); ?></button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</main>
<?php include '../includes/footer.php'; ?>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB