MVP, do pokazania klientowi. Bez wersji ENG
143
admin/attribute_keys.php
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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;
|
||||
}
|
||||
BIN
assets/pasted-20251209-065617-6bf1b4e6.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
assets/pasted-20251212-131131-f6f18157.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/pasted-20251212-131440-62c0087c.jpg
Normal file
|
After Width: | Height: | Size: 108 KiB |
184
cart.php
Normal 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">© <?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
@ -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
@ -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">© <?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
@ -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
@ -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";
|
||||
|
||||
13
db/migrations/001_create_products_table.sql
Normal 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
|
||||
);
|
||||
8
db/migrations/002_create_users_table.sql
Normal 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
|
||||
);
|
||||
7
db/migrations/003_seed_admin_user.sql
Normal 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
|
||||
);
|
||||
12
db/migrations/004_create_orders_table.sql
Normal 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)
|
||||
);
|
||||
13
db/migrations/005_create_order_items_table.sql
Normal 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)
|
||||
);
|
||||
1
db/migrations/006_add_updated_at_to_users.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE `users` ADD COLUMN `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
|
||||
1
db/migrations/007_add_supplier_id_to_products.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE products ADD COLUMN supplier_id INT NULL;
|
||||
1
db/migrations/008_add_item_status_to_order_items.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE order_items ADD COLUMN item_status VARCHAR(50) NOT NULL DEFAULT 'pending';
|
||||
15
db/migrations/009_create_clients_table.sql
Normal 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
|
||||
);
|
||||
1
db/migrations/010_add_client_id_to_users.sql
Normal 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;
|
||||
8
db/migrations/011_create_client_prices_table.sql
Normal 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
|
||||
);
|
||||
4
db/migrations/012_add_trade_credit_to_clients.sql
Normal 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;
|
||||
1
db/migrations/013_add_units_per_pallet_to_products.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE products ADD COLUMN units_per_pallet INT NOT NULL DEFAULT 1;
|
||||
1
db/migrations/015_add_delivery_source_to_orders.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE orders ADD COLUMN delivery_source VARCHAR(20) NOT NULL DEFAULT 'cs';
|
||||
10
db/migrations/016_seed_demo_data.sql
Normal 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);
|
||||
6
db/migrations/017_create_attribute_keys_table.sql
Normal 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
|
||||
);
|
||||
11
db/migrations/018_create_product_attributes_table.sql
Normal 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)
|
||||
);
|
||||
9
db/migrations/019_create_product_images_table.sql
Normal 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
|
||||
);
|
||||
1
db/migrations/020_remove_image_url_from_products.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE products DROP COLUMN image_url;
|
||||
7
db/migrations/021_create_product_relations_table.sql
Normal 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
|
||||
);
|
||||
8
db/migrations/022_create_product_documents_table.sql
Normal 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
|
||||
);
|
||||
1
db/migrations/023_add_product_role_to_products.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE products ADD COLUMN product_role ENUM('membrana', 'akcesoria') NOT NULL DEFAULT 'membrana';
|
||||
1
db/migrations/024_add_unit_to_products.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE products ADD COLUMN unit VARCHAR(10) NOT NULL DEFAULT 'szt';
|
||||
3
db/migrations/025_add_net_gross_prices_to_products.sql
Normal 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
@ -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
@ -0,0 +1,5 @@
|
||||
<footer class="text-center py-4 mt-auto text-muted bg-light">
|
||||
<div class="container">
|
||||
<p class="mb-0">© <?php echo date("Y"); ?> <?php echo t('footer_text'); ?>. All Rights Reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
287
includes/header.php
Normal 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
@ -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
@ -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;
|
||||
}
|
||||
?>
|
||||
79
includes/status_updater.php
Normal 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
@ -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">© <?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
@ -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
@ -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
@ -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">© <?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
@ -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">© <?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
@ -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
@ -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">© <?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
@ -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">© <?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
@ -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">© <?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
@ -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">© <?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
@ -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'; ?>
|
||||
BIN
uploads/documents/1/unnamed_file.pdf
Normal file
BIN
uploads/documents/2/unnamed_file.pdf
Normal file
BIN
uploads/documents/7/unnamed_file.pdf
Normal file
BIN
uploads/products/1/prod_1_693bb4b8cafc84.71050013.jpg
Normal file
|
After Width: | Height: | Size: 679 KiB |
BIN
uploads/products/1/prod_1_693bb5570d0001.42808802.jpg
Normal file
|
After Width: | Height: | Size: 679 KiB |
BIN
uploads/products/1/prod_1_693bbe3b50f255.26236493.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
uploads/products/2/prod_2_693bbe4f269239.01053848.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
uploads/products/3/prod_3_693bbccab68f28.84224252.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
uploads/products/4/prod_4_693bbcd7f0cf89.88681384.jpg
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
uploads/products/5/prod_5_693bbc6c51ada0.64391905.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
uploads/products/6/prod_6_693bbcb53d3ad4.54723030.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
uploads/products/7/prod_7_693bbc95f3ad62.80108781.JPG
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
uploads/products/7/prod_7_693bbc95f42182.47320741.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
uploads/products/prod_1_693afe1d3edc78.69259636.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
uploads/products/prod_1_693b00fe28f657.18624169.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
uploads/products/prod_1_693b03ee837bd6.83842630.png
Normal file
|
After Width: | Height: | Size: 491 KiB |
BIN
uploads/products/prod_1_693b03ee8562b4.70893384.png
Normal file
|
After Width: | Height: | Size: 776 KiB |
BIN
uploads/products/prod_1_693b03ee85d3f3.04162035.jpg
Normal file
|
After Width: | Height: | Size: 53 KiB |