zibo-dashboard/public/userarea/departments.php
2026-04-30 08:22:24 +02:00

799 lines
32 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
include('include/headscript.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
/* ==========================================
AJAX HANDLERS
========================================== */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['ajax']) && $_POST['ajax'] == '1') {
header('Content-Type: application/json');
$action = $_POST['action'] ?? '';
try {
if ($action === 'add') {
$name = trim($_POST['name'] ?? '');
$code = trim($_POST['code'] ?? '');
$description = trim($_POST['description'] ?? '');
$color = trim($_POST['color'] ?? '#6c757d');
$sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] !== '' ? (int)$_POST['sort_order'] : 999;
$is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : 1;
if ($name === '') {
echo json_encode([
'success' => false,
'message' => 'Department name is required.'
]);
exit;
}
if ($code === '') {
$code = strtoupper(str_replace(' ', '_', $name));
$code = preg_replace('/[^A-Z0-9_]/', '', $code);
} else {
$code = strtoupper($code);
$code = preg_replace('/[^A-Z0-9_]/', '', $code);
}
if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $color)) {
$color = '#6c757d';
}
$is_active = $is_active === 1 ? 1 : 0;
$check = $pdo->prepare("
SELECT COUNT(*)
FROM departments
WHERE name = :name OR code = :code
");
$check->execute([
'name' => $name,
'code' => $code
]);
if ((int)$check->fetchColumn() > 0) {
echo json_encode([
'success' => false,
'message' => 'A department with the same name or code already exists.'
]);
exit;
}
$sql = "INSERT INTO departments
(name, code, description, color, sort_order, is_active, created_at, updated_at)
VALUES
(:name, :code, :description, :color, :sort_order, :is_active, NOW(), NOW())";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'name' => $name,
'code' => $code !== '' ? $code : null,
'description' => $description !== '' ? $description : null,
'color' => $color,
'sort_order' => $sort_order,
'is_active' => $is_active
]);
echo json_encode(['success' => true]);
exit;
}
if ($action === 'edit') {
$id = (int)($_POST['id'] ?? 0);
$name = trim($_POST['name'] ?? '');
$code = trim($_POST['code'] ?? '');
$description = trim($_POST['description'] ?? '');
$color = trim($_POST['color'] ?? '#6c757d');
$sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] !== '' ? (int)$_POST['sort_order'] : 999;
$is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : 1;
if ($id <= 0) {
echo json_encode([
'success' => false,
'message' => 'Invalid department ID.'
]);
exit;
}
if ($name === '') {
echo json_encode([
'success' => false,
'message' => 'Department name is required.'
]);
exit;
}
if ($code === '') {
$code = strtoupper(str_replace(' ', '_', $name));
$code = preg_replace('/[^A-Z0-9_]/', '', $code);
} else {
$code = strtoupper($code);
$code = preg_replace('/[^A-Z0-9_]/', '', $code);
}
if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $color)) {
$color = '#6c757d';
}
$is_active = $is_active === 1 ? 1 : 0;
$check = $pdo->prepare("
SELECT COUNT(*)
FROM departments
WHERE (name = :name OR code = :code)
AND id <> :id
");
$check->execute([
'name' => $name,
'code' => $code,
'id' => $id
]);
if ((int)$check->fetchColumn() > 0) {
echo json_encode([
'success' => false,
'message' => 'Another department with the same name or code already exists.'
]);
exit;
}
$sql = "UPDATE departments
SET name = :name,
code = :code,
description = :description,
color = :color,
sort_order = :sort_order,
is_active = :is_active,
updated_at = NOW()
WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'name' => $name,
'code' => $code !== '' ? $code : null,
'description' => $description !== '' ? $description : null,
'color' => $color,
'sort_order' => $sort_order,
'is_active' => $is_active,
'id' => $id
]);
echo json_encode(['success' => true]);
exit;
}
if ($action === 'delete') {
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
echo json_encode([
'success' => false,
'message' => 'Invalid department ID.'
]);
exit;
}
/*
* Future-proof check:
* If later you add employees.department_id, this prevents deleting
* a department already used by employees.
*/
$columnCheck = $pdo->prepare("
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'employees'
AND COLUMN_NAME = 'department_id'
");
$columnCheck->execute();
$hasDepartmentId = (int)$columnCheck->fetchColumn() > 0;
if ($hasDepartmentId) {
$usageCheck = $pdo->prepare("
SELECT COUNT(*)
FROM employees
WHERE department_id = :id
");
$usageCheck->execute(['id' => $id]);
if ((int)$usageCheck->fetchColumn() > 0) {
echo json_encode([
'success' => false,
'message' => 'This department is linked to one or more employees and cannot be deleted.'
]);
exit;
}
}
$stmt = $pdo->prepare("DELETE FROM departments WHERE id = :id");
$stmt->execute(['id' => $id]);
echo json_encode(['success' => true]);
exit;
}
echo json_encode([
'success' => false,
'message' => 'Unknown action.'
]);
exit;
} catch (Exception $e) {
echo json_encode([
'success' => false,
'message' => $e->getMessage()
]);
exit;
}
}
/* ==========================================
PAGE DATA
========================================== */
$sql = "
SELECT *
FROM departments
ORDER BY sort_order ASC, name ASC
";
$stmtDepartments = $pdo->query($sql);
$departments = $stmtDepartments->fetchAll(PDO::FETCH_ASSOC);
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Gestione Departments - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<!-- jQuery and Bootstrap -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- DataTables -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<style>
body {
font-size: 1.05rem;
background: #f8fafc;
}
.card {
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.back-dashboard {
background-color: #cfe3ff !important;
color: #1f2d3d !important;
border: 1px solid #bcd4f4 !important;
border-radius: 10px;
font-weight: 600;
font-size: 1rem;
padding: 10px 18px;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease-in-out;
}
.back-dashboard:hover {
background-color: #b9d3ff !important;
transform: translateY(-2px);
}
.btn-add {
background-color: #0d6efd;
color: #fff;
border-radius: 8px;
padding: 10px 20px;
font-weight: 500;
transition: all 0.2s ease-in-out;
}
.btn-add:hover {
background-color: #0b5ed7;
transform: scale(1.02);
}
.table thead {
background-color: #cfe3ff;
color: #1f2d3d;
}
.modal-content {
border-radius: 16px;
}
#tabellaDepartments thead th {
text-align: center;
vertical-align: middle;
}
.badge-status {
padding: 0.25rem 0.6rem;
border-radius: 999px;
font-size: 0.8rem;
font-weight: 600;
}
.badge-status.active {
background-color: #d1fae5;
color: #065f46;
}
.badge-status.inactive {
background-color: #e5e7eb;
color: #374151;
}
.department-color-dot {
display: inline-block;
width: 18px;
height: 18px;
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.2);
vertical-align: middle;
margin-right: 6px;
}
.department-code {
font-family: Consolas, Monaco, monospace;
font-size: 0.9rem;
background: #f1f5f9;
padding: 4px 8px;
border-radius: 8px;
color: #334155;
}
.description-cell {
max-width: 320px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
}
</style>
</head>
<body>
<div class="wrapper toggled">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card p-3">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Gestione Departments</h5>
<button type="button" class="btn back-dashboard" onclick="location.href='production_dashboard.php'">
↩️ Torna alla Dashboard
</button>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="fw-semibold mb-0">Elenco Reparti / Departments</h6>
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#addDepartmentModal">
Aggiungi Department
</button>
</div>
<div class="table-responsive">
<table id="tabellaDepartments" class="table table-striped align-middle text-center" style="width:100%;">
<thead>
<tr>
<th>ID</th>
<th>Color</th>
<th>Name</th>
<th>Code</th>
<th>Description</th>
<th>Order</th>
<th>Status</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($departments)): ?>
<?php foreach ($departments as $row): ?>
<?php
$id = (int)$row['id'];
$name = $row['name'] ?? '';
$code = $row['code'] ?? '';
$description = $row['description'] ?? '';
$color = $row['color'] ?? '#6c757d';
$sortOrder = (int)($row['sort_order'] ?? 999);
$isActive = (int)($row['is_active'] ?? 1);
$statusClass = $isActive === 1 ? 'active' : 'inactive';
$statusLabel = $isActive === 1 ? 'Active' : 'Inactive';
$createdAt = !empty($row['created_at'])
? date('d/m/Y H:i', strtotime($row['created_at']))
: '-';
?>
<tr>
<td><?= $id ?></td>
<td>
<span class="department-color-dot" style="background-color: <?= htmlspecialchars($color, ENT_QUOTES) ?>;"></span>
<?= htmlspecialchars($color) ?>
</td>
<td class="fw-semibold">
<?= htmlspecialchars($name) ?>
</td>
<td>
<?php if ($code !== ''): ?>
<span class="department-code"><?= htmlspecialchars($code) ?></span>
<?php else: ?>
-
<?php endif; ?>
</td>
<td class="description-cell" title="<?= htmlspecialchars($description, ENT_QUOTES) ?>">
<?= $description !== '' ? htmlspecialchars($description) : '-' ?>
</td>
<td><?= $sortOrder ?></td>
<td>
<span class="badge-status <?= $statusClass ?>">
<?= htmlspecialchars($statusLabel) ?>
</span>
</td>
<td><?= $createdAt ?></td>
<td>
<button
class="btn btn-sm btn-outline-secondary edit-department"
data-id="<?= $id ?>"
data-name="<?= htmlspecialchars($name, ENT_QUOTES) ?>"
data-code="<?= htmlspecialchars($code, ENT_QUOTES) ?>"
data-description="<?= htmlspecialchars($description, ENT_QUOTES) ?>"
data-color="<?= htmlspecialchars($color, ENT_QUOTES) ?>"
data-sort_order="<?= $sortOrder ?>"
data-is_active="<?= $isActive ?>">
✏️ Modifica
</button>
<button
class="btn btn-sm btn-outline-danger delete-department"
data-id="<?= $id ?>"
data-name="<?= htmlspecialchars($name, ENT_QUOTES) ?>">
🗑️ Cancella
</button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<!-- ADD DEPARTMENT MODAL -->
<div class="modal fade" id="addDepartmentModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header" style="background-color:#cfe3ff;">
<h5 class="modal-title">Aggiungi Department</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="addDepartmentForm">
<div class="mb-3">
<label class="form-label fw-semibold">Name</label>
<input type="text" class="form-control" id="addName" name="name" placeholder="e.g. Produzione" required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Code</label>
<input type="text" class="form-control" id="addCode" name="code" placeholder="Optional, e.g. PRODUZIONE">
<small class="text-muted">If empty, it will be generated automatically from the name.</small>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Description</label>
<textarea class="form-control" id="addDescription" name="description" rows="3" placeholder="Optional notes"></textarea>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-semibold">Color</label>
<input type="color" class="form-control form-control-color" id="addColor" name="color" value="#6c757d">
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-semibold">Sort Order</label>
<input type="number" class="form-control" id="addSortOrder" name="sort_order" value="999" min="0">
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Status</label>
<select class="form-select" id="addIsActive" name="is_active">
<option value="1" selected>Active</option>
<option value="0">Inactive</option>
</select>
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">💾 Save</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- EDIT DEPARTMENT MODAL -->
<div class="modal fade" id="editDepartmentModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header" style="background-color:#cfe3ff;">
<h5 class="modal-title">Modifica Department</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="editDepartmentForm">
<input type="hidden" id="editDepartmentId">
<div class="mb-3">
<label class="form-label fw-semibold">Name</label>
<input type="text" class="form-control" id="editName" name="name" required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Code</label>
<input type="text" class="form-control" id="editCode" name="code">
<small class="text-muted">If empty, it will be generated automatically from the name.</small>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Description</label>
<textarea class="form-control" id="editDescription" name="description" rows="3"></textarea>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-semibold">Color</label>
<input type="color" class="form-control form-control-color" id="editColor" name="color" value="#6c757d">
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-semibold">Sort Order</label>
<input type="number" class="form-control" id="editSortOrder" name="sort_order" value="999" min="0">
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Status</label>
<select class="form-select" id="editIsActive" name="is_active">
<option value="1">Active</option>
<option value="0">Inactive</option>
</select>
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">💾 Save Changes</button>
</div>
</form>
</div>
</div>
</div>
</div>
<?php include('jsinclude.php'); ?>
<script>
$(document).ready(function() {
$('#tabellaDepartments').DataTable({
order: [
[5, 'asc'],
[2, 'asc']
],
pageLength: 25,
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json',
emptyTable: 'Nessun department presente'
}
});
/* -------- ADD DEPARTMENT -------- */
$("#addDepartmentForm").on("submit", function(e) {
e.preventDefault();
const payload = new URLSearchParams();
payload.append('ajax', '1');
payload.append('action', 'add');
payload.append('name', $("#addName").val().trim());
payload.append('code', $("#addCode").val().trim());
payload.append('description', $("#addDescription").val().trim());
payload.append('color', $("#addColor").val());
payload.append('sort_order', $("#addSortOrder").val());
payload.append('is_active', $("#addIsActive").val());
fetch("", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: payload.toString()
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: "success",
title: "Saved!",
confirmButtonColor: "#3085d6"
}).then(() => location.reload());
} else {
Swal.fire({
icon: "error",
title: "Error",
text: data.message || "Unable to save department."
});
}
})
.catch(err => {
Swal.fire({
icon: "error",
title: "Error",
text: "Communication error."
});
console.error(err);
});
});
/* -------- OPEN EDIT MODAL -------- */
$(document).on("click", ".edit-department", function() {
const btn = $(this);
$("#editDepartmentId").val(btn.data("id"));
$("#editName").val(btn.data("name"));
$("#editCode").val(btn.data("code"));
$("#editDescription").val(btn.data("description"));
$("#editColor").val(btn.data("color") || '#6c757d');
$("#editSortOrder").val(btn.data("sort_order"));
$("#editIsActive").val(String(btn.data("is_active")));
$("#editDepartmentModal").modal("show");
});
/* -------- SAVE EDIT -------- */
$("#editDepartmentForm").on("submit", function(e) {
e.preventDefault();
const payload = new URLSearchParams();
payload.append('ajax', '1');
payload.append('action', 'edit');
payload.append('id', $("#editDepartmentId").val());
payload.append('name', $("#editName").val().trim());
payload.append('code', $("#editCode").val().trim());
payload.append('description', $("#editDescription").val().trim());
payload.append('color', $("#editColor").val());
payload.append('sort_order', $("#editSortOrder").val());
payload.append('is_active', $("#editIsActive").val());
fetch("", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: payload.toString()
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: "success",
title: "Updated!",
confirmButtonColor: "#3085d6"
}).then(() => location.reload());
} else {
Swal.fire({
icon: "error",
title: "Error",
text: data.message || "Unable to update department."
});
}
})
.catch(err => {
Swal.fire({
icon: "error",
title: "Error",
text: "Communication error."
});
console.error(err);
});
});
/* -------- DELETE DEPARTMENT -------- */
$(document).on("click", ".delete-department", function() {
const id = $(this).data("id");
const name = $(this).data("name");
Swal.fire({
title: "Confermi la cancellazione?",
text: name ? ("Department: " + name) : "This department will be deleted.",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#d33",
cancelButtonColor: "#6c757d",
confirmButtonText: "Sì, cancella",
cancelButtonText: "Annulla"
}).then((result) => {
if (!result.isConfirmed) return;
const payload = new URLSearchParams();
payload.append('ajax', '1');
payload.append('action', 'delete');
payload.append('id', id);
fetch("", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: payload.toString()
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: "success",
title: "Deleted!",
confirmButtonColor: "#3085d6"
}).then(() => location.reload());
} else {
Swal.fire({
icon: "error",
title: "Error",
text: data.message || "Unable to delete department."
});
}
})
.catch(err => {
Swal.fire({
icon: "error",
title: "Error",
text: "Communication error."
});
console.error(err);
});
});
});
});
</script>
</body>
</html>