348 lines
18 KiB
PHP
348 lines
18 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/includes/layout.php';
|
|
require_once __DIR__ . '/includes/NotificationService.php';
|
|
|
|
ensure_schema();
|
|
|
|
$errors = [];
|
|
|
|
// Try to pre-fill from profile if logged in
|
|
$prefillName = $_SESSION['shipper_name'] ?? '';
|
|
$prefillCompany = '';
|
|
if (isset($_SESSION['user_id'])) {
|
|
try {
|
|
$stmt = db()->prepare("SELECT u.full_name, p.company_name FROM users u LEFT JOIN shipper_profiles p ON u.id = p.user_id WHERE u.id = ?");
|
|
$stmt->execute([$_SESSION['user_id']]);
|
|
$profile = $stmt->fetch();
|
|
if ($profile) {
|
|
$prefillName = $profile['full_name'];
|
|
$prefillCompany = $profile['company_name'] ?? '';
|
|
}
|
|
} catch (Throwable $e) {}
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'create_shipment') {
|
|
$shipperName = trim($_POST['shipper_name'] ?? '');
|
|
$shipperCompany = trim($_POST['shipper_company'] ?? '');
|
|
$origin = trim($_POST['origin_city'] ?? '');
|
|
$destination = trim($_POST['destination_city'] ?? '');
|
|
$cargo = trim($_POST['cargo_description'] ?? '');
|
|
$weight = trim($_POST['weight_tons'] ?? '');
|
|
$pickupDate = trim($_POST['pickup_date'] ?? '');
|
|
$deliveryDate = trim($_POST['delivery_date'] ?? '');
|
|
$payment = $_POST['payment_method'] ?? 'thawani';
|
|
|
|
if ($shipperName === '' || $shipperCompany === '' || $origin === '' || $destination === '' || $cargo === '' || $weight === '' || $pickupDate === '' || $deliveryDate === '') {
|
|
$errors[] = t('error_required');
|
|
} elseif (!is_numeric($weight)) {
|
|
$errors[] = t('error_invalid');
|
|
}
|
|
|
|
if (!$errors) {
|
|
$shipperId = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : null;
|
|
|
|
$stmt = db()->prepare(
|
|
"INSERT INTO shipments (shipper_id, shipper_name, shipper_company, origin_city, destination_city, cargo_description, weight_tons, pickup_date, delivery_date, payment_method)
|
|
VALUES (:shipper_id, :shipper_name, :shipper_company, :origin_city, :destination_city, :cargo_description, :weight_tons, :pickup_date, :delivery_date, :payment_method)"
|
|
);
|
|
$stmt->execute([
|
|
':shipper_id' => $shipperId,
|
|
':shipper_name' => $shipperName,
|
|
':shipper_company' => $shipperCompany,
|
|
':origin_city' => $origin,
|
|
':destination_city' => $destination,
|
|
':cargo_description' => $cargo,
|
|
':weight_tons' => $weight,
|
|
':pickup_date' => $pickupDate,
|
|
':delivery_date' => $deliveryDate,
|
|
':payment_method' => $payment,
|
|
]);
|
|
|
|
$newShipmentId = db()->lastInsertId();
|
|
$_SESSION['shipper_name'] = $shipperName;
|
|
$_SESSION['shipper_company_session'] = $shipperCompany; // for rudimentary filtering
|
|
|
|
// Notify Shipper (Confirmation)
|
|
if ($shipperId) {
|
|
$user = db()->query("SELECT * FROM users WHERE id = $shipperId")->fetch();
|
|
if ($user) {
|
|
NotificationService::send('shipment_created', $user, [
|
|
'shipment_id' => $newShipmentId,
|
|
'origin' => $origin,
|
|
'destination' => $destination
|
|
]);
|
|
}
|
|
}
|
|
|
|
set_flash('success', t('success_shipment'));
|
|
header('Location: ' . url_with_lang('shipper_dashboard.php'));
|
|
exit;
|
|
}
|
|
}
|
|
|
|
$shipments = [];
|
|
$filterCompany = $_SESSION['shipper_company_session'] ?? $prefillCompany;
|
|
|
|
try {
|
|
if ($filterCompany) {
|
|
$stmt = db()->prepare("SELECT * FROM shipments WHERE shipper_company = ? ORDER BY created_at DESC LIMIT 20");
|
|
$stmt->execute([$filterCompany]);
|
|
} else {
|
|
$stmt = db()->query("SELECT * FROM shipments ORDER BY created_at DESC LIMIT 20");
|
|
}
|
|
$shipments = $stmt->fetchAll();
|
|
} catch (Throwable $e) {
|
|
$shipments = [];
|
|
}
|
|
|
|
$stats = [
|
|
'total' => 0,
|
|
'active' => 0,
|
|
'delivered' => 0,
|
|
];
|
|
|
|
try {
|
|
if ($filterCompany) {
|
|
$stats['total'] = (int)db()->prepare("SELECT COUNT(*) FROM shipments WHERE shipper_company = ?")->execute([$filterCompany]) ? (int)db()->query("SELECT COUNT(*) FROM shipments WHERE shipper_company = " . db()->quote($filterCompany))->fetchColumn() : 0;
|
|
$stats['active'] = (int)db()->query("SELECT COUNT(*) FROM shipments WHERE shipper_company = " . db()->quote($filterCompany) . " AND status != 'delivered'")->fetchColumn();
|
|
$stats['delivered'] = (int)db()->query("SELECT COUNT(*) FROM shipments WHERE shipper_company = " . db()->quote($filterCompany) . " AND status = 'delivered'")->fetchColumn();
|
|
} else {
|
|
$stats['total'] = (int)db()->query("SELECT COUNT(*) FROM shipments")->fetchColumn();
|
|
$stats['active'] = (int)db()->query("SELECT COUNT(*) FROM shipments WHERE status != 'delivered'")->fetchColumn();
|
|
$stats['delivered'] = (int)db()->query("SELECT COUNT(*) FROM shipments WHERE status = 'delivered'")->fetchColumn();
|
|
}
|
|
} catch (Throwable $e) {}
|
|
|
|
// Fetch countries for dropdowns
|
|
$countries = [];
|
|
try {
|
|
$countries = db()->query("SELECT * FROM countries ORDER BY name_en ASC")->fetchAll();
|
|
} catch (Throwable $e) {}
|
|
|
|
render_header(t('shipper_dashboard'), 'shipper');
|
|
$flash = get_flash();
|
|
?>
|
|
|
|
<div class="page-intro mb-4">
|
|
<h1 class="section-title mb-1"><?= e(t('shipper_dashboard')) ?></h1>
|
|
<p class="muted mb-0"><?= e(t('welcome_back') ?? 'Welcome to your dashboard. Manage your cargo shipments here.') ?></p>
|
|
</div>
|
|
|
|
<!-- Stats Row -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-4">
|
|
<div class="panel p-4 text-center h-100 shadow-sm border-0 rounded-4 position-relative overflow-hidden" style="background: linear-gradient(135deg, #ffffff, #f8f9fa);">
|
|
<div class="position-absolute opacity-10" style="inset-inline-end: 10px; top: 15px;"><i class="bi bi-box-seam" style="font-size: 3.5rem;"></i></div>
|
|
<div class="text-primary mb-2 position-relative"><i class="bi bi-box-seam fs-2"></i></div>
|
|
<h3 class="h2 mb-0 fw-bold position-relative"><?= $stats['total'] ?></h3>
|
|
<p class="text-muted small text-uppercase mb-0 fw-bold position-relative" style="letter-spacing: 0.5px;"><?= e(t('total_shipments_posted')) ?></p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="panel p-4 text-center h-100 shadow-sm border-0 rounded-4 position-relative overflow-hidden" style="background: linear-gradient(135deg, #ffffff, #f8f9fa);">
|
|
<div class="position-absolute opacity-10" style="inset-inline-end: 10px; top: 15px;"><i class="bi bi-truck" style="font-size: 3.5rem;"></i></div>
|
|
<div class="text-warning mb-2 position-relative"><i class="bi bi-truck fs-2"></i></div>
|
|
<h3 class="h2 mb-0 fw-bold position-relative"><?= $stats['active'] ?></h3>
|
|
<p class="text-muted small text-uppercase mb-0 fw-bold position-relative" style="letter-spacing: 0.5px;"><?= e(t('active_shipments')) ?></p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="panel p-4 text-center h-100 shadow-sm border-0 rounded-4 position-relative overflow-hidden" style="background: linear-gradient(135deg, #ffffff, #f8f9fa);">
|
|
<div class="position-absolute opacity-10" style="inset-inline-end: 10px; top: 15px;"><i class="bi bi-check-circle" style="font-size: 3.5rem;"></i></div>
|
|
<div class="text-success mb-2 position-relative"><i class="bi bi-check-circle fs-2"></i></div>
|
|
<h3 class="h2 mb-0 fw-bold position-relative"><?= $stats['delivered'] ?></h3>
|
|
<p class="text-muted small text-uppercase mb-0 fw-bold position-relative" style="letter-spacing: 0.5px;"><?= e(t('delivered_shipments')) ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-lg-5">
|
|
<div class="panel p-4 shadow-sm border-0 rounded-4 h-100">
|
|
<h2 class="h5 fw-bold mb-4"><i class="bi bi-plus-circle-fill text-primary me-2"></i><?= e(t('new_shipment')) ?></h2>
|
|
<?php if ($flash): ?>
|
|
<div class="alert alert-success" data-auto-dismiss="true"><?= e($flash['message']) ?></div>
|
|
<?php endif; ?>
|
|
<?php if ($errors): ?>
|
|
<div class="alert alert-warning"><?= e(implode(' ', $errors)) ?></div>
|
|
<?php endif; ?>
|
|
<form method="post">
|
|
<input type="hidden" name="action" value="create_shipment">
|
|
<div class="mb-3">
|
|
<label class="form-label text-muted small fw-bold"><?= e(t('shipper_name')) ?></label>
|
|
<input class="form-control" name="shipper_name" value="<?= e($prefillName) ?>" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label text-muted small fw-bold"><?= e(t('shipper_company')) ?></label>
|
|
<input class="form-control" name="shipper_company" value="<?= e($prefillCompany) ?>" required>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label text-muted small fw-bold">Origin Country</label>
|
|
<select class="form-select" id="origin_country" required>
|
|
<option value="">Select Country</option>
|
|
<?php foreach ($countries as $country): ?>
|
|
<option value="<?= e($country['id']) ?>"><?= e($country['name_en']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label text-muted small fw-bold"><?= e(t('origin')) ?></label>
|
|
<select class="form-select" name="origin_city" id="origin_city" disabled required>
|
|
<option value="">Select City</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label text-muted small fw-bold">Destination Country</label>
|
|
<select class="form-select" id="destination_country" required>
|
|
<option value="">Select Country</option>
|
|
<?php foreach ($countries as $country): ?>
|
|
<option value="<?= e($country['id']) ?>"><?= e($country['name_en']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label text-muted small fw-bold"><?= e(t('destination')) ?></label>
|
|
<select class="form-select" name="destination_city" id="destination_city" disabled required>
|
|
<option value="">Select City</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label text-muted small fw-bold"><?= e(t('cargo')) ?></label>
|
|
<input class="form-control" name="cargo_description" placeholder="<?= e(t('cargo_placeholder') ?? 'e.g. 20 Pallets of Electronics') ?>" required>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label text-muted small fw-bold"><?= e(t('weight')) ?></label>
|
|
<input class="form-control" name="weight_tons" type="number" step="0.01" min="0.1" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label text-muted small fw-bold"><?= e(t('payment_method')) ?></label>
|
|
<select class="form-select" name="payment_method">
|
|
<option value="thawani"><?= e(t('payment_thawani')) ?></option>
|
|
<option value="bank_transfer"><?= e(t('payment_bank')) ?></option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-6">
|
|
<label class="form-label text-muted small fw-bold"><?= e(t('pickup_date')) ?></label>
|
|
<input class="form-control" name="pickup_date" type="date" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label text-muted small fw-bold"><?= e(t('delivery_date')) ?></label>
|
|
<input class="form-control" name="delivery_date" type="date" required>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-primary w-100 py-2 fw-bold rounded-pill shadow-sm" type="submit"><?= e(t('submit_shipment')) ?></button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-7">
|
|
<div class="panel p-4 shadow-sm border-0 rounded-4 h-100 d-flex flex-column">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2 class="h5 mb-0 fw-bold"><i class="bi bi-list-ul text-muted me-2"></i><?= e(t('shipments_list')) ?></h2>
|
|
<span class="badge bg-light text-dark border"><?= e(count($shipments)) ?> <?= e(t('total_label') ?? 'total') ?></span>
|
|
</div>
|
|
|
|
<?php if (!$shipments): ?>
|
|
<div class="text-center p-5 text-muted flex-grow-1 d-flex flex-column justify-content-center">
|
|
<i class="bi bi-inbox fs-1 mb-3 d-block opacity-50"></i>
|
|
<p class="mb-0"><?= e(t('no_shipments')) ?></p>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="table-responsive flex-grow-1">
|
|
<table class="table align-middle mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-uppercase small text-muted border-top-0"><?= e(t('route_label') ?? 'Route') ?></th>
|
|
<th class="text-uppercase small text-muted border-top-0"><?= e(t('status')) ?></th>
|
|
<th class="text-uppercase small text-muted border-top-0"><?= e(t('offer')) ?></th>
|
|
<th class="text-uppercase small text-muted border-top-0 text-end"><?= e(t('actions')) ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($shipments as $row): ?>
|
|
<tr>
|
|
<td>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<span class="fw-medium"><?= e($row['origin_city']) ?></span>
|
|
<i class="bi bi-arrow-right text-muted small"></i>
|
|
<span class="fw-medium"><?= e($row['destination_city']) ?></span>
|
|
</div>
|
|
<small class="text-muted"><?= e(date('M d', strtotime($row['pickup_date']))) ?></small>
|
|
</td>
|
|
<td><span class="badge <?= e($row['status']) ?> rounded-pill px-3 py-2"><?= e(status_label($row['status'])) ?></span></td>
|
|
<td>
|
|
<?php if ($row['offer_price']): ?>
|
|
<div class="fw-bold text-success">$<?= e($row['offer_price']) ?></div>
|
|
<small class="text-muted"><?= e($row['offer_owner']) ?></small>
|
|
<?php else: ?>
|
|
<span class="text-muted small"><?= e(t('no_offers')) ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="text-end">
|
|
<a class="btn btn-sm p-1 border-0 bg-transparent text-primary" href="<?= e(url_with_lang('shipment_detail.php', ['id' => $row['id']])) ?>" title="<?= e(t('view')) ?>">
|
|
<i class="bi bi-eye"></i>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
function setupCityLoader(countrySelectId, citySelectId) {
|
|
const countrySelect = document.getElementById(countrySelectId);
|
|
const citySelect = document.getElementById(citySelectId);
|
|
|
|
countrySelect.addEventListener('change', function() {
|
|
const countryId = this.value;
|
|
citySelect.innerHTML = '<option value="">Loading...</option>';
|
|
citySelect.disabled = true;
|
|
|
|
if (countryId) {
|
|
fetch('api/get_cities.php?country_id=' + countryId)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
citySelect.innerHTML = '<option value="">Select City</option>';
|
|
data.forEach(city => {
|
|
const option = document.createElement('option');
|
|
option.value = city.name_en; // Using name as value for DB compatibility
|
|
option.textContent = city.name_en;
|
|
citySelect.appendChild(option);
|
|
});
|
|
citySelect.disabled = false;
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching cities:', error);
|
|
citySelect.innerHTML = '<option value="">Error loading cities</option>';
|
|
});
|
|
} else {
|
|
citySelect.innerHTML = '<option value="">Select City</option>';
|
|
citySelect.disabled = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
setupCityLoader('origin_country', 'origin_city');
|
|
setupCityLoader('destination_country', 'destination_city');
|
|
});
|
|
</script>
|
|
|
|
<?php render_footer(); ?>
|