1208 lines
45 KiB
PHP
1208 lines
45 KiB
PHP
<?php
|
||
|
||
/**
|
||
* production_dashboard_v2.php - nuova dashboard del gestionale, pensata per le dita (tablet) e per il PC.
|
||
*
|
||
* Punto di partenza del lavoro quotidiano: linee adesso, cose da gestire, i tuoi preferiti,
|
||
* poi tutte le funzioni a schede. Stesse voci e stessi permessi della dashboard attuale.
|
||
* Numeri di produzione per ora di fine del lavoro (non per data di inserimento).
|
||
*/
|
||
include('include/headscript.php');
|
||
|
||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||
|
||
/* ===================== MENU (stesse voci e permessi della dashboard attuale + pagine nuove) ===================== */
|
||
$dashboardSections = [
|
||
[
|
||
'id' => 'secOperativo',
|
||
'title' => 'Operativo',
|
||
'subtitle' => 'Produzione di tutti i giorni',
|
||
'icon' => '🚀',
|
||
'buttons' => [
|
||
['label' => 'Programmazione', 'icon' => '🗓️', 'class' => 'btn-programmazione', 'url' => 'produzione_programmazione_drag.php', 'permission' => 'production.programming.view'],
|
||
['label' => 'Programmazione (nuova)', 'icon' => '📋', 'class' => 'btn-programmazione', 'url' => 'production_programming_v2.php', 'permission' => 'production.programming.view'],
|
||
['label' => 'Tabellone', 'icon' => '🧱', 'class' => 'btn-programmazione', 'url' => 'production_board.php', 'permission' => 'production.programming.view'],
|
||
['label' => 'Nuovi da Mago', 'icon' => '📥', 'class' => 'btn-mago', 'url' => 'production_mago_queue.php', 'permission' => 'production.programming.view', 'badge' => 'mago'],
|
||
['label' => 'Line View', 'icon' => '⚙️', 'class' => 'btn-status', 'url' => 'production_line_view2.php', 'permission' => 'production.line_view.view'],
|
||
['label' => 'Postazione tablet', 'icon' => '📱', 'class' => 'btn-status', 'url' => 'production_station.php', 'permission' => 'production.line_view.view'],
|
||
['label' => 'Magazzino', 'icon' => '📦', 'class' => 'btn-magazzino', 'url' => 'warehouse_dashboard.php', 'permission' => 'warehouse.dashboard.view'],
|
||
['label' => 'Smart-Alert', 'icon' => '⏰', 'class' => 'btn-scadenziario', 'url' => 'scadenzario/index.php', 'permission' => 'deadlines.view'],
|
||
],
|
||
],
|
||
[
|
||
'id' => 'secDirezione',
|
||
'title' => 'Direzione',
|
||
'subtitle' => 'Statistiche, fermi e supervisione',
|
||
'icon' => '📊',
|
||
'buttons' => [
|
||
['label' => 'Statistiche Produzione', 'icon' => '📈', 'class' => 'btn-statistiche', 'url' => 'production_stats.php', 'permission' => 'production.stats.view'],
|
||
['label' => 'Pause & Fermi', 'icon' => '🛑', 'class' => 'btn-pause-stats', 'url' => 'pause_stats.php', 'permission' => 'production.stats.view'],
|
||
['label' => 'Manager', 'icon' => '👔', 'class' => 'btn-manager', 'url' => 'manager_produzione.php', 'permission' => 'production.manager.view'],
|
||
['label' => 'Manager Stats', 'icon' => '📊', 'class' => 'btn-manager-stats', 'url' => 'manager_stats.php', 'permission' => 'production.manager_stats.view'],
|
||
['label' => 'Schermo reparto', 'icon' => '📺', 'class' => 'btn-kiosk', 'url' => 'kiosk_admin.php', 'permission' => 'production.programming.view'],
|
||
],
|
||
],
|
||
[
|
||
'id' => 'secVendite',
|
||
'title' => 'Business Overview',
|
||
'subtitle' => 'Ordini, fatturato, clienti e consegne (Mago)',
|
||
'icon' => '💶',
|
||
'buttons' => [
|
||
['label' => 'Sommario', 'icon' => '🚦', 'class' => 'btn-sales-summary', 'url' => 'sales_summary.php', 'permission' => 'sales.stats.view'],
|
||
['label' => 'Andamento ordini', 'icon' => '🧾', 'class' => 'btn-sales', 'url' => 'sales_orders_stats.php', 'permission' => 'sales.stats.view'],
|
||
['label' => 'Confronto anni', 'icon' => '📊', 'class' => 'btn-sales', 'url' => 'sales_orders_years.php', 'permission' => 'sales.stats.view'],
|
||
['label' => 'Scheda cliente', 'icon' => '🪪', 'class' => 'btn-sales', 'url' => 'sales_customer.php', 'permission' => 'sales.stats.view'],
|
||
['label' => 'Consegne', 'icon' => '🚚', 'class' => 'btn-sales-deliveries', 'url' => 'sales_orders_deliveries.php', 'permission' => 'sales.deliveries.view'],
|
||
],
|
||
],
|
||
[
|
||
'id' => 'secAnagrafiche',
|
||
'title' => 'Anagrafiche',
|
||
'subtitle' => 'Dati di base e setup di produzione',
|
||
'icon' => '🗂️',
|
||
'buttons' => [
|
||
['label' => 'Mescole', 'icon' => '⚗️', 'class' => 'btn-mescole', 'url' => 'mescole.php', 'permission' => 'masterdata.mescole.view'],
|
||
['label' => 'Elenco Profili', 'icon' => '🧩', 'class' => 'btn-matrici', 'url' => 'matrici.php', 'permission' => 'masterdata.matrici.view'],
|
||
['label' => 'Linee Produzione', 'icon' => '🏭', 'class' => 'btn-linee', 'url' => 'linee.php', 'permission' => 'masterdata.linee.view'],
|
||
['label' => 'Imballaggi', 'icon' => '📦', 'class' => 'btn-setup', 'url' => 'packaging_items.php', 'permission' => 'masterdata.packaging.view'],
|
||
['label' => 'Suppliers', 'icon' => '🏷️', 'class' => 'btn-setup', 'url' => 'suppliers.php', 'permission' => 'masterdata.suppliers.view'],
|
||
['label' => 'Setup', 'icon' => '⚙️', 'class' => 'btn-setup', 'url' => 'lookup_values.php', 'permission' => 'masterdata.lookup.view'],
|
||
['label' => 'Fogli di lavoro', 'icon' => '🗒️', 'class' => 'btn-setup', 'url' => 'worksheets.php', 'permission' => 'masterdata.worksheets.view'],
|
||
],
|
||
],
|
||
[
|
||
'id' => 'secServizi',
|
||
'title' => 'Servizi',
|
||
'subtitle' => 'Status, cause pausa, attrezzature',
|
||
'icon' => '🧰',
|
||
'buttons' => [
|
||
['label' => 'Status', 'icon' => '📋', 'class' => 'btn-setup', 'url' => 'production_status.php', 'permission' => 'services.status.view'],
|
||
['label' => 'Cause di Pausa', 'icon' => '🛑', 'class' => 'btn-problem', 'url' => 'production_pause_reasons.php', 'permission' => 'services.pause_reasons.view'],
|
||
['label' => 'Attrezzature', 'icon' => '🛠️', 'class' => 'btn-tools', 'url' => 'production_tools.php', 'permission' => 'services.tools.view'],
|
||
],
|
||
],
|
||
[
|
||
'id' => 'secPersonale',
|
||
'title' => 'Personale',
|
||
'subtitle' => 'Dipendenti, formazione, skill',
|
||
'icon' => '👥',
|
||
'buttons' => [
|
||
['label' => 'Dipendenti', 'icon' => '👥', 'class' => 'btn-employees', 'url' => 'employees.php', 'permission' => 'hr.employees.view'],
|
||
['label' => 'Mansioni', 'icon' => '🧩', 'class' => 'btn-setup', 'url' => 'job-roles.php', 'permission' => 'hr.employees.view'],
|
||
['label' => 'Departments', 'icon' => '🏢', 'class' => 'btn-departments', 'url' => 'departments.php', 'permission' => 'hr.departments.view'],
|
||
['label' => 'DPI', 'icon' => '🦺', 'class' => 'btn-setup', 'url' => 'ppe-items.php', 'permission' => 'hr.employees.view'],
|
||
['label' => 'Gestione Formazione', 'icon' => '🎓', 'class' => 'btn-setup', 'url' => 'trainings.php', 'permission' => 'hr.trainings.view'],
|
||
['label' => 'Skills', 'icon' => '🧠', 'class' => 'btn-setup', 'url' => 'skills.php', 'permission' => 'hr.skills.view'],
|
||
],
|
||
],
|
||
];
|
||
|
||
/* ===================== DATI ===================== */
|
||
$TZ = new DateTimeZone('Europe/Rome');
|
||
$UTC = new DateTimeZone('UTC');
|
||
$u = function (DateTime $d) use ($UTC) {
|
||
$x = clone $d;
|
||
return $x->setTimezone($UTC)->format('Y-m-d H:i:s');
|
||
};
|
||
const DV2_NOT_FINISHED = '1,6,2,7'; // da programmare, programmato, in produzione, pausa
|
||
|
||
/** Lavori conclusi tra due istanti locali (per ora di fine, salvata in UTC). */
|
||
$produced = function (DateTime $from, DateTime $to) use ($pdo, $u) {
|
||
$st = $pdo->prepare("SELECT COUNT(*) n, COALESCE(SUM(kgprod),0) kg, COALESCE(SUM(mtprod),0) mt, COALESCE(SUM(scarto),0) sc
|
||
FROM productiondata WHERE end_time >= ? AND end_time < ? AND id_status NOT IN (" . DV2_NOT_FINISHED . ")");
|
||
$st->execute([$u($from), $u($to)]);
|
||
$r = $st->fetch(PDO::FETCH_ASSOC);
|
||
$kg = (float)$r['kg'];
|
||
$sc = (float)$r['sc'];
|
||
return ['n' => (int)$r['n'], 'kg' => $kg, 'mt' => (float)$r['mt'], 'scarto' => ($kg + $sc) > 0 ? $sc / ($kg + $sc) * 100 : null];
|
||
};
|
||
|
||
$now = new DateTime('now', $TZ);
|
||
$today = new DateTime('today', $TZ);
|
||
$yest = (clone $today)->modify('-1 day');
|
||
$yestSame = (clone $yest)->setTime((int)$now->format('H'), (int)$now->format('i'));
|
||
$monthStart = new DateTime('first day of this month 00:00', $TZ);
|
||
$prevMonthStart = (clone $monthStart)->modify('-1 month');
|
||
$prevMonthSame = (clone $prevMonthStart)->modify('+' . ($now->getTimestamp() - $monthStart->getTimestamp()) . ' seconds');
|
||
|
||
$K = ['today' => null, 'yest' => null, 'month' => null, 'prevMonth' => null];
|
||
$stops = null;
|
||
$todo = ['mago' => 0, 'problemi' => 0, 'compilare' => 0, 'daprog' => 0, 'ritardo' => 0];
|
||
$lines = [];
|
||
try {
|
||
$K['today'] = $produced($today, $now);
|
||
$K['yest'] = $produced($yest, $yestSame);
|
||
$K['month'] = $produced($monthStart, $now);
|
||
$K['prevMonth'] = $produced($prevMonthStart, $prevMonthSame);
|
||
|
||
// ore di fermo della settimana
|
||
$mon = new DateTime('monday this week', $TZ);
|
||
$st = $pdo->prepare("SELECT COALESCE(SUM(GREATEST(0, TIMESTAMPDIFF(SECOND, GREATEST(start_pause, ?), COALESCE(end_pause, UTC_TIMESTAMP())))), 0)
|
||
FROM production_pauses WHERE COALESCE(end_pause, UTC_TIMESTAMP()) > ?");
|
||
$st->execute([$u($mon), $u($mon)]);
|
||
$stops = (int)$st->fetchColumn() / 3600;
|
||
|
||
// cose da gestire
|
||
$todo['problemi'] = (int)$pdo->query("SELECT COUNT(*) FROM productiondata WHERE id_status = 8")->fetchColumn();
|
||
$todo['compilare'] = (int)$pdo->query("SELECT COUNT(*) FROM productiondata WHERE id_status = 11")->fetchColumn();
|
||
$todo['daprog'] = (int)$pdo->query("SELECT COUNT(*) FROM productiondata WHERE id_status = 1")->fetchColumn();
|
||
$todo['ritardo'] = (int)$pdo->query("SELECT COUNT(*) FROM productiondata WHERE id_status IN (1,6) AND data_zibo IS NOT NULL AND data_zibo < CURDATE()")->fetchColumn();
|
||
try {
|
||
$todo['mago'] = (int)$pdo->query("SELECT COUNT(*) FROM mago_prod_queue WHERE queue_status = 'nuovo' OR (queue_status = 'importato' AND mago_changed = 1)")->fetchColumn();
|
||
} catch (Throwable $e) {
|
||
}
|
||
|
||
// linee adesso
|
||
foreach ($pdo->query("SELECT * FROM production_lines ORDER BY line_number, id")->fetchAll(PDO::FETCH_ASSOC) as $l) {
|
||
if (isset($l['status']) && $l['status'] !== '' && $l['status'] !== 'active') continue;
|
||
$lines[(int)$l['id']] = ['id' => (int)$l['id'], 'name' => $l['name'], 'color' => $l['color'] ?: '#64748b', 'job' => null, 'queue' => 0, 'next' => null];
|
||
}
|
||
$rows = $pdo->query("
|
||
SELECT p.id_linea, p.id_status, p.start_time, p.ore_previste, m.nome AS matrice,
|
||
(SELECT COALESCE(SUM(TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause)),0) FROM production_pauses pp WHERE pp.id_production = p.id AND pp.end_pause IS NOT NULL) AS pause_sec,
|
||
(SELECT pp.start_pause FROM production_pauses pp WHERE pp.id_production = p.id AND pp.end_pause IS NULL ORDER BY pp.start_pause DESC LIMIT 1) AS open_pause
|
||
FROM productiondata p LEFT JOIN matrice m ON m.id = p.idmatrice WHERE p.id_status IN (2,7) ORDER BY p.start_time")->fetchAll(PDO::FETCH_ASSOC);
|
||
foreach ($rows as $r) {
|
||
$lid = (int)$r['id_linea'];
|
||
if (!isset($lines[$lid]) || $lines[$lid]['job']) continue;
|
||
$lines[$lid]['job'] = [
|
||
'matrice' => $r['matrice'],
|
||
'paused' => (int)$r['id_status'] === 7,
|
||
'start_ms' => $r['start_time'] ? (new DateTime($r['start_time'], $UTC))->getTimestamp() * 1000 : null,
|
||
'open_pause_ms' => $r['open_pause'] ? (new DateTime($r['open_pause'], $UTC))->getTimestamp() * 1000 : null,
|
||
'pause_sec' => (int)$r['pause_sec'],
|
||
'ore' => (float)$r['ore_previste'],
|
||
];
|
||
}
|
||
foreach ($pdo->query("SELECT p.id_linea, m.nome FROM productiondata p LEFT JOIN matrice m ON m.id = p.idmatrice WHERE p.id_status = 6 ORDER BY p.priority IS NULL, p.priority, p.id") as $r) {
|
||
$lid = (int)$r['id_linea'];
|
||
if (!isset($lines[$lid])) continue;
|
||
$lines[$lid]['queue']++;
|
||
if ($lines[$lid]['next'] === null) $lines[$lid]['next'] = $r['nome'];
|
||
}
|
||
} catch (Throwable $e) {
|
||
// la dashboard resta usabile anche se un dato non è disponibile
|
||
}
|
||
|
||
$userName = '';
|
||
if (!empty($iduserlogin)) {
|
||
try {
|
||
$st = $pdo->prepare("SELECT COALESCE(NULLIF(TRIM(first_name), ''), username, email) FROM auth_users WHERE id = ?");
|
||
$st->execute([(int)$iduserlogin]);
|
||
$userName = (string)$st->fetchColumn();
|
||
} catch (Throwable $e) {
|
||
}
|
||
}
|
||
|
||
if (!function_exists('visibleButtons')) {
|
||
function visibleButtons(array $buttons)
|
||
{
|
||
return array_values(array_filter($buttons, fn($b) => empty($b['permission']) || userCan($b['permission'])));
|
||
}
|
||
}
|
||
|
||
function dv2Num($v, $d = 0)
|
||
{
|
||
return number_format((float)$v, $d, ',', '.');
|
||
}
|
||
|
||
/** Freccia di confronto (▲ ▼) con percentuale. */
|
||
function dv2Cmp($cur, $prev, $lowerBetter = false)
|
||
{
|
||
if ($cur === null || $prev === null) return '<span class="flat">—</span>';
|
||
if (!$prev && !$cur) return '<span class="flat">= uguale</span>';
|
||
if (!$prev) return '<span class="' . ($lowerBetter ? 'down' : 'up') . '">▲ nuovo</span>';
|
||
$p = round(($cur - $prev) / $prev * 100);
|
||
if ($p == 0) return '<span class="flat">= uguale</span>';
|
||
$good = $lowerBetter ? $p < 0 : $p > 0;
|
||
return '<span class="' . ($good ? 'up' : 'down') . '">' . ($p > 0 ? '▲' : '▼') . ' ' . abs($p) . '%</span>';
|
||
}
|
||
|
||
$hour = (int)$now->format('H');
|
||
$greet = $hour < 13 ? 'Buongiorno' : ($hour < 18 ? 'Buon pomeriggio' : 'Buonasera');
|
||
$GIORNI = ['domenica', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato'];
|
||
$MESI = ['', 'gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'];
|
||
$canProg = userCan('production.programming.view');
|
||
$canLine = userCan('production.line_view.view');
|
||
$canStats = userCan('production.stats.view');
|
||
?>
|
||
<?php
|
||
// voci visibili all'utente, per sezione (stessa regola dei permessi della dashboard attuale)
|
||
$visibleSections = [];
|
||
foreach ($dashboardSections as $sec) {
|
||
$b = visibleButtons($sec['buttons']);
|
||
if (!$b) continue;
|
||
foreach ($b as &$x) $x['badge_n'] = (($x['badge'] ?? '') === 'mago') ? (int)$todo['mago'] : 0;
|
||
unset($x);
|
||
$visibleSections[] = ['id' => $sec['id'], 'title' => $sec['title'], 'icon' => $sec['icon'], 'buttons' => $b];
|
||
}
|
||
$todoItems = [];
|
||
if ($canProg && $todo['mago']) $todoItems[] = ['n' => $todo['mago'], 'l' => 'Nuovi ordini da Mago', 'u' => 'production_mago_queue.php', 'c' => 'info', 'i' => '📥'];
|
||
if ($canProg && $todo['ritardo']) $todoItems[] = ['n' => $todo['ritardo'], 'l' => 'Data Zibo già passata', 'u' => 'production_programming_v2.php', 'c' => 'hot', 'i' => '⏰'];
|
||
if ($todo['problemi']) $todoItems[] = ['n' => $todo['problemi'], 'l' => 'Chiusi con problema', 'u' => $canLine ? 'production_line_view2.php' : '', 'c' => 'hot', 'i' => '⚠️'];
|
||
if ($todo['compilare']) $todoItems[] = ['n' => $todo['compilare'], 'l' => 'Dati di fine da compilare', 'u' => $canLine ? 'production_line_view2.php' : '', 'c' => 'warn', 'i' => '📝'];
|
||
if ($canProg && $todo['daprog']) $todoItems[] = ['n' => $todo['daprog'], 'l' => 'Da programmare', 'u' => 'production_programming_v2.php', 'c' => 'plain', 'i' => '🗓️'];
|
||
$t = $K['today'];
|
||
$y = $K['yest'];
|
||
$m = $K['month'];
|
||
$pm = $K['prevMonth'];
|
||
?>
|
||
<!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>Dashboard <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||
|
||
<style>
|
||
:root {
|
||
--ink: #1f2d3d;
|
||
--muted: #64748b;
|
||
--line: #e2e8f0;
|
||
--r: 20px;
|
||
}
|
||
|
||
body {
|
||
background: #eef2f7;
|
||
color: var(--ink);
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.hm {
|
||
max-width: 1320px;
|
||
margin: 0 auto;
|
||
width: 100%;
|
||
}
|
||
|
||
.hm button,
|
||
.hm a {
|
||
touch-action: manipulation;
|
||
}
|
||
|
||
.hm h4 {
|
||
font-weight: 900;
|
||
font-size: 1.25rem;
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.blk {
|
||
margin-bottom: 22px;
|
||
}
|
||
|
||
.blk-h {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.blk-h .act {
|
||
margin-left: auto;
|
||
}
|
||
|
||
/* ---------- testata ---------- */
|
||
.top {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
margin-bottom: 18px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.top h2 {
|
||
font-weight: 900;
|
||
margin: 0;
|
||
font-size: 1.75rem;
|
||
}
|
||
|
||
.top .dt {
|
||
color: var(--muted);
|
||
font-weight: 700;
|
||
font-size: 1.05rem;
|
||
}
|
||
|
||
.top .clock {
|
||
margin-left: auto;
|
||
font-size: 2.2rem;
|
||
font-weight: 900;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.pill-btn {
|
||
min-height: 56px;
|
||
border-radius: 16px;
|
||
border: 0;
|
||
background: #fff;
|
||
box-shadow: 0 4px 14px rgba(15, 23, 42, .08);
|
||
padding: 0 20px;
|
||
font-weight: 800;
|
||
font-size: 1.05rem;
|
||
color: var(--ink);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.pill-btn:active {
|
||
transform: scale(.97);
|
||
}
|
||
|
||
.pill-btn.on {
|
||
background: #0f172a;
|
||
color: #fff;
|
||
}
|
||
|
||
/* ---------- linee adesso ---------- */
|
||
.lines {
|
||
display: grid;
|
||
gap: 14px;
|
||
}
|
||
|
||
.ln {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 168px;
|
||
padding: 16px 18px;
|
||
border-radius: var(--r);
|
||
text-decoration: none;
|
||
color: var(--ink);
|
||
background: color-mix(in srgb, var(--c) 12%, #fff);
|
||
border: 2px solid color-mix(in srgb, var(--c) 30%, #fff);
|
||
box-shadow: 0 6px 16px rgba(15, 23, 42, .06);
|
||
}
|
||
|
||
a.ln:active {
|
||
transform: scale(.98);
|
||
}
|
||
|
||
a.ln:hover {
|
||
color: var(--ink);
|
||
}
|
||
|
||
.ln .h {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-weight: 900;
|
||
font-size: 1.15rem;
|
||
}
|
||
|
||
.ln .h .dot {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
background: var(--c);
|
||
}
|
||
|
||
.ln .st {
|
||
margin-left: auto;
|
||
font-size: .78rem;
|
||
font-weight: 900;
|
||
border-radius: 8px;
|
||
padding: 4px 10px;
|
||
color: #fff;
|
||
letter-spacing: .03em;
|
||
}
|
||
|
||
.ln .mat {
|
||
font-size: 1.9rem;
|
||
font-weight: 900;
|
||
margin-top: 10px;
|
||
line-height: 1;
|
||
}
|
||
|
||
.ln .tm {
|
||
font-size: 2.1rem;
|
||
font-weight: 900;
|
||
font-variant-numeric: tabular-nums;
|
||
margin-top: auto;
|
||
line-height: 1.1;
|
||
}
|
||
|
||
.ln .tm.run {
|
||
color: #15803d;
|
||
}
|
||
|
||
.ln .tm.pause {
|
||
color: #c2410c;
|
||
}
|
||
|
||
.ln .bar {
|
||
height: 8px;
|
||
border-radius: 99px;
|
||
background: rgba(255, 255, 255, .9);
|
||
overflow: hidden;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.ln .bar span {
|
||
display: block;
|
||
height: 100%;
|
||
background: #16a34a;
|
||
}
|
||
|
||
.ln .q {
|
||
font-size: .95rem;
|
||
color: var(--muted);
|
||
font-weight: 700;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.ln .go {
|
||
position: absolute;
|
||
right: 16px;
|
||
bottom: 14px;
|
||
font-size: 1.4rem;
|
||
color: color-mix(in srgb, var(--c) 70%, #000);
|
||
opacity: .55;
|
||
}
|
||
|
||
.nums {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.num {
|
||
background: #fff;
|
||
border-radius: 14px;
|
||
padding: 10px 16px;
|
||
box-shadow: 0 3px 10px rgba(15, 23, 42, .05);
|
||
font-weight: 700;
|
||
color: var(--muted);
|
||
font-size: .95rem;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.num b {
|
||
color: var(--ink);
|
||
font-size: 1.15rem;
|
||
margin-left: 4px;
|
||
}
|
||
|
||
.up {
|
||
color: #16a34a;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.down {
|
||
color: #dc2626;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.flat {
|
||
color: var(--muted);
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* ---------- da gestire ---------- */
|
||
.todo {
|
||
display: flex;
|
||
gap: 12px;
|
||
overflow-x: auto;
|
||
padding-bottom: 4px;
|
||
scrollbar-width: none;
|
||
}
|
||
|
||
.td {
|
||
flex: 1 0 190px;
|
||
min-height: 96px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
padding: 14px 18px;
|
||
border-radius: var(--r);
|
||
background: #fff;
|
||
box-shadow: 0 4px 14px rgba(15, 23, 42, .06);
|
||
text-decoration: none;
|
||
color: var(--ink);
|
||
border-left: 6px solid var(--k);
|
||
}
|
||
|
||
a.td:active {
|
||
transform: scale(.98);
|
||
}
|
||
|
||
.td .n {
|
||
font-size: 2.2rem;
|
||
font-weight: 900;
|
||
color: var(--k);
|
||
line-height: 1;
|
||
}
|
||
|
||
.td .l {
|
||
font-weight: 800;
|
||
font-size: 1rem;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.td .i {
|
||
font-size: .85rem;
|
||
color: var(--muted);
|
||
font-weight: 700;
|
||
}
|
||
|
||
.td.hot {
|
||
--k: #dc2626;
|
||
}
|
||
|
||
.td.warn {
|
||
--k: #ea580c;
|
||
}
|
||
|
||
.td.info {
|
||
--k: #0284c7;
|
||
}
|
||
|
||
.td.plain {
|
||
--k: #475569;
|
||
}
|
||
|
||
.td.ok {
|
||
--k: #16a34a;
|
||
}
|
||
|
||
/* ---------- riquadri (preferiti e funzioni) ---------- */
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||
gap: 14px;
|
||
}
|
||
|
||
.tile {
|
||
position: relative;
|
||
min-height: 132px;
|
||
border-radius: var(--r);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10px;
|
||
padding: 14px 10px;
|
||
text-align: center;
|
||
text-decoration: none;
|
||
color: var(--ink);
|
||
font-weight: 800;
|
||
font-size: 1.02rem;
|
||
line-height: 1.2;
|
||
background: #fff;
|
||
box-shadow: 0 4px 14px rgba(15, 23, 42, .06);
|
||
border: 2px solid transparent;
|
||
user-select: none;
|
||
-webkit-user-select: none;
|
||
}
|
||
|
||
.tile:active {
|
||
transform: scale(.96);
|
||
}
|
||
|
||
.tile:hover {
|
||
color: var(--ink);
|
||
border-color: color-mix(in srgb, var(--a) 35%, #fff);
|
||
}
|
||
|
||
.tile .ti {
|
||
width: 64px;
|
||
height: 64px;
|
||
border-radius: 18px;
|
||
background: color-mix(in srgb, var(--a) 22%, #fff);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 2rem;
|
||
}
|
||
|
||
.tile .bdg {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 10px;
|
||
background: #dc2626;
|
||
color: #fff;
|
||
border-radius: 999px;
|
||
font-size: .85rem;
|
||
font-weight: 900;
|
||
padding: 3px 9px;
|
||
}
|
||
|
||
.tile .star {
|
||
display: none;
|
||
position: absolute;
|
||
top: 6px;
|
||
left: 6px;
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 50%;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 1.4rem;
|
||
background: #f1f5f9;
|
||
color: #94a3b8;
|
||
}
|
||
|
||
.editing .tile .star {
|
||
display: flex;
|
||
}
|
||
|
||
.editing .tile.fav .star {
|
||
background: #fef3c7;
|
||
color: #d97706;
|
||
}
|
||
|
||
.editing .tile {
|
||
border-style: dashed;
|
||
border-color: #cbd5e1;
|
||
}
|
||
|
||
.fav-empty {
|
||
background: #fff;
|
||
border-radius: var(--r);
|
||
padding: 22px;
|
||
text-align: center;
|
||
color: var(--muted);
|
||
font-weight: 700;
|
||
border: 2px dashed #cbd5e1;
|
||
}
|
||
|
||
/* schede delle sezioni */
|
||
.tabs {
|
||
display: flex;
|
||
gap: 10px;
|
||
overflow-x: auto;
|
||
padding-bottom: 6px;
|
||
margin-bottom: 14px;
|
||
scrollbar-width: none;
|
||
}
|
||
|
||
.tab {
|
||
flex-shrink: 0;
|
||
min-height: 52px;
|
||
padding: 0 18px;
|
||
border-radius: 14px;
|
||
border: 0;
|
||
background: #fff;
|
||
font-weight: 800;
|
||
font-size: 1rem;
|
||
color: var(--muted);
|
||
box-shadow: 0 3px 10px rgba(15, 23, 42, .05);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.tab.on {
|
||
background: #0f172a;
|
||
color: #fff;
|
||
}
|
||
|
||
.tab:active {
|
||
transform: scale(.97);
|
||
}
|
||
|
||
.hide {
|
||
display: none !important;
|
||
}
|
||
|
||
/* ricerca a tutto schermo */
|
||
.srch {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: #eef2f7;
|
||
z-index: 2000;
|
||
padding: 22px;
|
||
overflow-y: auto;
|
||
display: none;
|
||
}
|
||
|
||
.srch.on {
|
||
display: block;
|
||
}
|
||
|
||
.srch .bar {
|
||
display: flex;
|
||
gap: 12px;
|
||
max-width: 1320px;
|
||
margin: 0 auto 18px;
|
||
}
|
||
|
||
.srch input {
|
||
flex: 1;
|
||
min-height: 64px;
|
||
border-radius: 18px;
|
||
border: 2px solid #cbd5e1;
|
||
font-size: 1.4rem;
|
||
font-weight: 700;
|
||
padding: 0 22px;
|
||
}
|
||
|
||
.srch input:focus {
|
||
outline: none;
|
||
border-color: #2563eb;
|
||
}
|
||
|
||
.srch .grid {
|
||
max-width: 1320px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
/* colori delle voci (gli stessi della dashboard attuale) */
|
||
.btn-programmazione {
|
||
--a: #7c7afa;
|
||
}
|
||
|
||
.btn-status {
|
||
--a: #22c55e;
|
||
}
|
||
|
||
.btn-mago {
|
||
--a: #f59e0b;
|
||
}
|
||
|
||
.btn-scadenziario {
|
||
--a: #fb923c;
|
||
}
|
||
|
||
.btn-statistiche {
|
||
--a: #eab308;
|
||
}
|
||
|
||
.btn-manager {
|
||
--a: #3b82f6;
|
||
}
|
||
|
||
.btn-manager-stats {
|
||
--a: #22c55e;
|
||
}
|
||
|
||
.btn-pause-stats,
|
||
.btn-problem {
|
||
--a: #ef4444;
|
||
}
|
||
|
||
.btn-magazzino,
|
||
.btn-linee {
|
||
--a: #0ea5e9;
|
||
}
|
||
|
||
.btn-mescole {
|
||
--a: #f59e0b;
|
||
}
|
||
|
||
.btn-matrici {
|
||
--a: #f43f5e;
|
||
}
|
||
|
||
.btn-tools {
|
||
--a: #8b5cf6;
|
||
}
|
||
|
||
.btn-employees {
|
||
--a: #6366f1;
|
||
}
|
||
|
||
.btn-departments {
|
||
--a: #60a5fa;
|
||
}
|
||
|
||
.btn-setup {
|
||
--a: #94a3b8;
|
||
}
|
||
|
||
.btn-sales-summary {
|
||
--a: #16a34a;
|
||
}
|
||
|
||
.btn-sales {
|
||
--a: #34d399;
|
||
}
|
||
|
||
.btn-sales-deliveries {
|
||
--a: #facc15;
|
||
}
|
||
|
||
.btn-kiosk {
|
||
--a: #334155;
|
||
}
|
||
|
||
/* widget scadenze (come nella dashboard attuale) */
|
||
.my-deadlines-widgets {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: .75rem;
|
||
margin-bottom: 1rem;
|
||
width: 100%;
|
||
}
|
||
|
||
.my-deadlines-widgets:empty {
|
||
display: none;
|
||
}
|
||
|
||
.my-deadlines-widgets .my-deadlines-widgets {
|
||
display: contents;
|
||
}
|
||
|
||
.my-deadlines-widgets .mdw {
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: .75rem;
|
||
padding: .9rem 1rem;
|
||
border-radius: 16px;
|
||
text-decoration: none;
|
||
color: #fff;
|
||
box-shadow: 0 2px 6px rgba(0, 0, 0, .08);
|
||
min-height: 64px;
|
||
}
|
||
|
||
.my-deadlines-widgets .mdw-red {
|
||
background: linear-gradient(135deg, #dc3545 0%, #b02a37 100%);
|
||
}
|
||
|
||
.my-deadlines-widgets .mdw-orange {
|
||
background: linear-gradient(135deg, #e8930c 0%, #c77a00 100%);
|
||
}
|
||
|
||
.my-deadlines-widgets .mdw-gray {
|
||
background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);
|
||
}
|
||
|
||
.my-deadlines-widgets .mdw-icon {
|
||
width: 38px;
|
||
height: 38px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(255, 255, 255, .22);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.my-deadlines-widgets .mdw-body {
|
||
flex: 1;
|
||
line-height: 1.2;
|
||
min-width: 0;
|
||
}
|
||
|
||
.my-deadlines-widgets .mdw-count {
|
||
font-size: 1.5rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.my-deadlines-widgets .mdw-label {
|
||
font-size: .8rem;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
@media (max-width: 900px) {
|
||
.ln {
|
||
min-height: 150px;
|
||
padding: 14px;
|
||
}
|
||
|
||
.ln .mat {
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.ln .tm {
|
||
font-size: 1.7rem;
|
||
}
|
||
|
||
.my-deadlines-widgets .mdw {
|
||
flex: 1 1 100%;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.lines {
|
||
grid-template-columns: 1fr !important;
|
||
}
|
||
|
||
.top .clock {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 560px) {
|
||
.grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<div class="wrapper toggled" id="appWrapper">
|
||
<?php include('include/navbar.php'); ?>
|
||
<?php include('include/topbar.php'); ?>
|
||
|
||
<div class="page-wrapper">
|
||
<div class="page-content">
|
||
<div class="hm">
|
||
|
||
<div class="top">
|
||
<div>
|
||
<h2><?= $greet ?><?= $userName !== '' ? ', ' . htmlspecialchars($userName, ENT_QUOTES, 'UTF-8') : '' ?></h2>
|
||
<div class="dt"><?= $GIORNI[(int)$now->format('w')] . ' ' . $now->format('j') . ' ' . $MESI[(int)$now->format('n')] ?></div>
|
||
</div>
|
||
<div class="clock" id="clock"><?= $now->format('H:i') ?></div>
|
||
<button class="pill-btn" id="btnSearch">🔎 Cerca</button>
|
||
</div>
|
||
|
||
<div class="my-deadlines-widgets"><?php
|
||
foreach ([__DIR__ . '/scadenzario/include/my_deadlines_widget.php', __DIR__ . '/include/training_widget.php'] as $w) {
|
||
if (is_file($w)) include($w);
|
||
}
|
||
?></div>
|
||
|
||
<!-- LINEE ADESSO -->
|
||
<?php if ($lines): ?>
|
||
<div class="blk">
|
||
<div class="blk-h">
|
||
<h4>⚙️ Linee adesso</h4>
|
||
</div>
|
||
<div class="lines" style="grid-template-columns:repeat(<?= min(4, count($lines)) ?>, minmax(0,1fr))">
|
||
<?php foreach ($lines as $l):
|
||
$j = $l['job']; ?>
|
||
<a class="ln" style="--c:<?= htmlspecialchars($l['color']) ?>" <?= $canLine ? 'href="production_station.php?line=' . $l['id'] . '"' : '' ?>>
|
||
<div class="h"><span class="dot"></span><?= htmlspecialchars($l['name']) ?>
|
||
<span class="st" style="background:<?= $j ? ($j['paused'] ? '#f59e0b' : '#16a34a') : '#94a3b8' ?>"><?= $j ? ($j['paused'] ? 'IN PAUSA' : 'IN PRODUZIONE') : 'LIBERA' ?></span>
|
||
</div>
|
||
<?php if ($j): ?>
|
||
<div class="mat"><?= htmlspecialchars((string)$j['matrice']) ?></div>
|
||
<div class="tm <?= $j['paused'] ? 'pause' : 'run' ?>" data-job='<?= htmlspecialchars(json_encode($j), ENT_QUOTES) ?>'>--:--:--</div>
|
||
<?php if ($j['ore']): ?><div class="bar"><span></span></div><?php endif; ?>
|
||
<?php else: ?>
|
||
<div class="mat" style="color:#94a3b8">—</div>
|
||
<div class="q" style="margin-top:auto"><?= $l['next'] ? 'Prossimo: <b style="color:#1f2d3d">' . htmlspecialchars($l['next']) . '</b>' : 'Nessun lavoro in coda' ?></div>
|
||
<?php endif; ?>
|
||
<div class="q"><?= $l['queue'] ?> in coda</div>
|
||
<?php if ($canLine): ?><span class="go">›</span><?php endif; ?>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php if ($t): ?>
|
||
<div class="nums">
|
||
<a class="num" <?= $canStats ? 'href="production_stats.php"' : '' ?>>Oggi <b><?= dv2Num($t['kg']) ?> kg</b> · <?= dv2Num($t['mt']) ?> m · <?= dv2Cmp($t['kg'], $y['kg']) ?></a>
|
||
<a class="num" <?= $canStats ? 'href="production_stats.php"' : '' ?>>Mese <b><?= dv2Num($m['kg']) ?> kg</b> · <?= dv2Cmp($m['kg'], $pm['kg']) ?></a>
|
||
<a class="num" <?= $canStats ? 'href="production_stats.php"' : '' ?>>Scarto mese <b><?= $m['scarto'] === null ? '—' : dv2Num($m['scarto'], 2) . '%' ?></b></a>
|
||
<a class="num" <?= $canStats ? 'href="pause_stats.php"' : '' ?>>Fermi settimana <b><?= dv2Num($stops ?? 0, 1) ?> h</b></a>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<!-- DA GESTIRE -->
|
||
<div class="blk">
|
||
<div class="blk-h">
|
||
<h4>📌 Da gestire</h4>
|
||
</div>
|
||
<div class="todo">
|
||
<?php foreach ($todoItems as $it): ?>
|
||
<a class="td <?= $it['c'] ?>" <?= $it['u'] ? 'href="' . $it['u'] . '"' : '' ?>>
|
||
<span class="n"><?= $it['n'] ?></span>
|
||
<span><span class="l"><?= htmlspecialchars($it['l']) ?></span><br><span class="i"><?= $it['i'] ?> apri</span></span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
<?php if (!$todoItems): ?><div class="td ok"><span class="n">✓</span><span class="l">Tutto in ordine</span></div><?php endif; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PREFERITI -->
|
||
<div class="blk" id="blkFav">
|
||
<div class="blk-h">
|
||
<h4>⭐ I tuoi preferiti</h4>
|
||
<button class="pill-btn act" id="btnEdit">✏️ Modifica</button>
|
||
</div>
|
||
<div class="grid" id="favGrid"></div>
|
||
</div>
|
||
|
||
<!-- TUTTE LE FUNZIONI -->
|
||
<div class="blk" id="blkAll">
|
||
<div class="blk-h">
|
||
<h4>🧭 Tutte le funzioni</h4>
|
||
</div>
|
||
<div class="tabs" id="tabs"></div>
|
||
<div class="grid" id="allGrid"></div>
|
||
</div>
|
||
<?php if (!$visibleSections): ?><div class="fav-empty">Nessuna funzione disponibile per il tuo profilo.</div><?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<?php include('jsinclude.php'); ?>
|
||
<?php include('include/footer.php'); ?>
|
||
</div>
|
||
|
||
<!-- RICERCA -->
|
||
<div class="srch" id="srch">
|
||
<div class="bar">
|
||
<input type="search" id="q" placeholder="Cosa cerchi? (es. mescole, consegne)" autocomplete="off">
|
||
<button class="pill-btn" id="srchClose">✕ Chiudi</button>
|
||
</div>
|
||
<div class="grid" id="srchGrid"></div>
|
||
</div>
|
||
|
||
<script>
|
||
$(function() {
|
||
const SECTIONS = <?= json_encode($visibleSections, JSON_UNESCAPED_UNICODE) ?>;
|
||
const ALL = SECTIONS.flatMap(s => s.buttons.map(b => ({
|
||
...b,
|
||
sec: s.title
|
||
})));
|
||
const FAV_KEY = 'dv2_fav_<?= (int)($iduserlogin ?? 0) ?>';
|
||
const store = {
|
||
get(k, d) {
|
||
try {
|
||
return JSON.parse(localStorage.getItem(k)) ?? d;
|
||
} catch (e) {
|
||
return d;
|
||
}
|
||
},
|
||
set(k, v) {
|
||
try {
|
||
localStorage.setItem(k, JSON.stringify(v));
|
||
} catch (e) {}
|
||
}
|
||
};
|
||
const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({
|
||
'&': '&',
|
||
'<': '<',
|
||
'>': '>',
|
||
'"': '"',
|
||
"'": '''
|
||
} [c]));
|
||
|
||
// preferiti: all'inizio le prime voci di Operativo, poi quelli scelti dall'utente
|
||
let fav = store.get(FAV_KEY, null);
|
||
if (!Array.isArray(fav)) fav = (SECTIONS[0] ? SECTIONS[0].buttons.slice(0, 6).map(b => b.url) : []);
|
||
fav = fav.filter(u => ALL.some(b => b.url === u));
|
||
let editing = false;
|
||
let tab = store.get('dv2_tab', SECTIONS[0] ? SECTIONS[0].id : '');
|
||
if (!SECTIONS.some(s => s.id === tab) && SECTIONS[0]) tab = SECTIONS[0].id;
|
||
|
||
const tile = b => `<a class="tile ${esc(b.class)} ${fav.includes(b.url) ? 'fav' : ''}" href="${esc(b.url)}" data-url="${esc(b.url)}">
|
||
<span class="star">${fav.includes(b.url) ? '★' : '☆'}</span>
|
||
<span class="ti">${esc(b.icon)}</span><span>${esc(b.label)}</span>
|
||
${b.badge_n ? `<span class="bdg">${b.badge_n}</span>` : ''}
|
||
</a>`;
|
||
|
||
function render() {
|
||
$('body').toggleClass('editing', editing);
|
||
$('#btnEdit').toggleClass('on', editing).html(editing ? '✓ Fatto' : '✏️ Modifica');
|
||
const favItems = fav.map(u => ALL.find(b => b.url === u)).filter(Boolean);
|
||
$('#favGrid').html(favItems.length ? favItems.map(tile).join('') :
|
||
`<div class="fav-empty" style="grid-column:1/-1">${editing ? 'Tocca ☆ sulle funzioni qui sotto per aggiungerle' : 'Nessun preferito: tocca ✏️ Modifica per sceglierli'}</div>`);
|
||
$('#tabs').html(SECTIONS.map(s => `<button class="tab ${s.id === tab ? 'on' : ''}" data-tab="${esc(s.id)}">${esc(s.icon)} ${esc(s.title)}</button>`).join(''));
|
||
const sec = SECTIONS.find(s => s.id === tab);
|
||
$('#allGrid').html(sec ? sec.buttons.map(tile).join('') : '');
|
||
// in modifica le funzioni restano tutte visibili per scegliere
|
||
if (editing) $('#blkAll')[0].scrollIntoView({
|
||
behavior: 'smooth',
|
||
block: 'start'
|
||
});
|
||
}
|
||
|
||
$('#btnEdit').on('click', () => {
|
||
editing = !editing;
|
||
render();
|
||
});
|
||
$(document).on('click', '.tab', function() {
|
||
tab = this.dataset.tab;
|
||
store.set('dv2_tab', tab);
|
||
render();
|
||
});
|
||
// in modifica il tocco su una funzione la aggiunge o la toglie dai preferiti
|
||
$(document).on('click', '.tile', function(e) {
|
||
if (!editing) return;
|
||
e.preventDefault();
|
||
const u = this.dataset.url;
|
||
fav = fav.includes(u) ? fav.filter(x => x !== u) : [...fav, u];
|
||
store.set(FAV_KEY, fav);
|
||
const y = window.scrollY;
|
||
editing = true;
|
||
$('body').addClass('editing');
|
||
const favItems = fav.map(x => ALL.find(b => b.url === x)).filter(Boolean);
|
||
$('#favGrid').html(favItems.length ? favItems.map(tile).join('') : '<div class="fav-empty" style="grid-column:1/-1">Tocca ☆ sulle funzioni qui sotto per aggiungerle</div>');
|
||
const sec = SECTIONS.find(s => s.id === tab);
|
||
$('#allGrid').html(sec ? sec.buttons.map(tile).join('') : '');
|
||
window.scrollTo(0, y);
|
||
});
|
||
|
||
/* ---------- ricerca a tutto schermo ---------- */
|
||
function openSearch() {
|
||
$('#srch').addClass('on');
|
||
$('#q').val('').trigger('focus');
|
||
search();
|
||
}
|
||
|
||
function search() {
|
||
const q = $('#q').val().trim().toLowerCase();
|
||
const hits = ALL.filter(b => !q || (b.label + ' ' + b.sec).toLowerCase().includes(q));
|
||
$('#srchGrid').html(hits.length ? hits.map(tile).join('') : '<div class="fav-empty" style="grid-column:1/-1">Nessuna funzione trovata</div>');
|
||
}
|
||
$('#btnSearch').on('click', openSearch);
|
||
$('#srchClose').on('click', () => $('#srch').removeClass('on'));
|
||
$('#q').on('input', search).on('keydown', function(e) {
|
||
if (e.key === 'Enter') {
|
||
const f = $('#srchGrid .tile').first();
|
||
if (f.length) location.href = f.attr('href');
|
||
}
|
||
if (e.key === 'Escape') $('#srch').removeClass('on');
|
||
});
|
||
$(document).on('keydown', function(e) {
|
||
if (e.key === '/' && !$(e.target).is('input, textarea, select')) {
|
||
e.preventDefault();
|
||
openSearch();
|
||
}
|
||
});
|
||
|
||
/* ---------- orologio e timer delle linee ---------- */
|
||
const skew = Date.now() - <?= time() * 1000 ?>;
|
||
const hms = ms => {
|
||
const s = Math.max(0, Math.floor(ms / 1000));
|
||
return `${String(Math.floor(s / 3600)).padStart(2, '0')}:${String(Math.floor(s % 3600 / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`;
|
||
};
|
||
|
||
function tick() {
|
||
const t = Date.now() - skew;
|
||
const d = new Date(t);
|
||
$('#clock').text(`${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`);
|
||
$('[data-job]').each(function() {
|
||
const j = $(this).data('job');
|
||
if (!j.start_ms) return;
|
||
const cur = j.open_pause_ms ? t - j.open_pause_ms : 0;
|
||
const net = Math.max(0, t - j.start_ms - j.pause_sec * 1000 - cur);
|
||
$(this).text(hms(j.paused ? cur : net));
|
||
if (j.ore) {
|
||
const p = net / (j.ore * 3600e3);
|
||
$(this).closest('.ln').find('.bar span').css({
|
||
width: Math.min(100, p * 100) + '%',
|
||
background: p > 1 ? '#dc2626' : (j.paused ? '#f59e0b' : '#16a34a')
|
||
});
|
||
}
|
||
});
|
||
}
|
||
render();
|
||
tick();
|
||
setInterval(tick, 1000);
|
||
// aggiorna linee e numeri ogni 5 minuti (non mentre si modifica o si cerca)
|
||
setInterval(() => {
|
||
if (!editing && !$('#srch').hasClass('on')) location.reload();
|
||
}, 300000);
|
||
});
|
||
</script>
|
||
</body>
|
||
|
||
</html>
|