Repository navigation
Expand file tree
/
Copy pathapp.js
More file actions
147 lines (147 loc) · 42 KB
/
Copy pathapp.js
File metadata and controls
147 lines (147 loc) · 42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
(function GearApp(){
"use strict";
const KEY="gearloop-public-showcase-20261109-v1";
const SEED=window.GEAR_DEMO_SEED;
const clone=x=>JSON.parse(JSON.stringify(x));
let db;try{db=JSON.parse(localStorage.getItem(KEY)||"null")||clone(SEED)}catch(e){db=clone(SEED)}
const $=s=>document.querySelector(s);
const fmt=new Intl.NumberFormat("he-IL");
const money=x=>"₪"+fmt.format(Number(x)||0);
const esc=v=>String(v??"").replace(/[&<>"']/g,ch=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[ch]));
const dateLabel=v=>{const [y,m,d]=String(v).split("-");return d&&m?`${d}.${m}`:"—"};
const dateFull=v=>{const [y,m,d]=String(v).split("-");return y?`${d}.${m}.${y}`:"—"};
const dates=(a,b)=>`${dateLabel(a)}–${dateLabel(b)}`;
const COLORS=["#73e7f4","#a8a0ff","#8ef3bf","#ffd485","#f0a0db"];
let tab=(location.hash.replace("#","")||"dashboard");
let filter="all", category="הכל", search="", timer=0;
const navGroups=[
{label:"WORKSPACE",items:[["dashboard","סקירה כללית","dashboard"],["bookings","הזמנות והפקות","calendar"],["planning","תכנון ולו״ז","planning"],["warehouse","רצפת המחסן","warehouse"]]},
{label:"MANAGEMENT",items:[["equipment","קטלוג ציוד","equipment"],["kits","ערכות וחבילות","kits"],["fleet","צי רכבים ושינוע","fleet"],["finance","הצעות וכספים","finance"]]},
{label:"INTELLIGENCE",items:[["operations","משימות ובקרה","tasks"],["reports","דוחות ותובנות","reports"]]}
];
const glyphs={
dashboard:'<rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/>',
calendar:'<rect x="3" y="5" width="18" height="16" rx="2"/><path d="M7 3v4M17 3v4M3 10h18"/><path d="m9 15 2 2 4-4"/>',
planning:'<rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4M7 14h4M7 17h7"/>',
warehouse:'<path d="M3 21V9l9-6 9 6v12H3Z"/><path d="M3 10h18M8 21v-8h8v8M8 17h8"/>',
equipment:'<rect x="3" y="3" width="8" height="8" rx="1"/><rect x="13" y="3" width="8" height="8" rx="1"/><rect x="3" y="13" width="8" height="8" rx="1"/><rect x="13" y="13" width="8" height="8" rx="1"/>',
kits:'<path d="m12 3 9 5-9 5-9-5 9-5Z"/><path d="m3 12 9 5 9-5M3 16l9 5 9-5M12 13v8"/>',
fleet:'<path d="M3 7h11v11H3zM14 11h4l3 4v3h-7z"/><circle cx="7" cy="19" r="2"/><circle cx="17" cy="19" r="2"/>',
finance:'<rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8h8M8 12h8M8 16h5"/><path d="m15 16 2 2 3-3"/>',
tasks:'<rect x="4" y="3" width="16" height="18" rx="2"/><path d="m8 8 2 2 4-4M8 15h8"/>',
reports:'<path d="M4 20V11M10 20V4M16 20v-8M22 20v-14M2 21h20"/>',
arrow:'<path d="M5 12h14M13 6l6 6-6 6"/>',
plus:'<path d="M12 5v14M5 12h14"/>',
check:'<path d="m5 12 4 4L19 6"/>',
refresh:'<path d="M20 11a8 8 0 0 0-14-5L4 8M4 3v5h5M4 13a8 8 0 0 0 14 5l2-2M15 16h5v5"/>'
};
const ico=(name)=>`<svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${glyphs[name]||glyphs.dashboard}</svg>`;
const types={"טיוטה":"st-draft","מאושר":"st-approved","ליקוט":"st-picking","יצא לשטח":"st-out","הוחזר":"st-complete","נשלח":"st-sent","אושר":"st-approved","זמין":"st-available","בליקוט":"st-picking","בדרך לאירוע":"st-enroute","חזר למחסן":"st-ready","גבוה":"st-warning","קריטי":"st-danger","רגיל":"st-neutral"};
const badge=(label,cls)=>`<span class="status ${cls||types[label]||"st-neutral"}">${esc(label)}</span>`;
function persist(){try{localStorage.setItem(KEY,JSON.stringify(db))}catch(e){}}
function notify(message){const t=$("#toast");t.textContent=message;t.classList.remove("hidden");clearTimeout(timer);timer=setTimeout(()=>t.classList.add("hidden"),2900)}
function log(message){db.recent.unshift({text:message,when:"עכשיו · בדפדפן זה",type:"ok"});db.recent=db.recent.slice(0,14)}
function navigate(dest){tab=dest;filter="all";category="הכל";if(location.hash!==`#${dest}`)location.hash=dest;render();window.scrollTo(0,0);$("#sidebar").classList.remove("nav-open")}
function navRender(){const counts={bookings:db.bookings.filter(b=>b.status!=="הוחזר").length,warehouse:db.bookings.filter(b=>["ליקוט","יצא לשטח"].includes(b.status)).length,operations:db.tasks.filter(t=>!t.done).length};
$("#nav").innerHTML=navGroups.map(g=>`<div class="nav-label">${g.label}</div>`+g.items.map(([id,name,icon])=>`<button class="nav-link ${tab===id?"active":""}" type="button" data-action="navigate" data-route="${id}">${ico(icon)}<span>${name}</span>${counts[id]?`<span class="nav-count">${counts[id]}</span>`:""}</button>`).join("")).join("");
}
function head(section,description,kicker="OPERATIONS / CONTROL",actions=""){return `<div class="page-head"><div><span class="eyebrow"><span>✦</span> ${kicker}</span><h1 class="page-title">${section}</h1><p class="page-subtitle">${description}</p></div><div class="head-actions">${actions||`<span class="date-tag">◷ 09 בנובמבר 2026</span>`}</div></div>`}
function metric(label,val,foot,icon,color){return `<div class="kpi-card"><div class="kpi-label">${label}</div><div class="kpi-icon" style="${color?`background:${color}25;color:${color}`:""}">${ico(icon)}</div><div class="kpi-val">${val}</div><div class="kpi-foot">${foot}</div></div>`}
function bookingTable(list,limit=100,compact=false){const items=list.slice(0,limit);return items.length?`<div class="table-wrap"><table class="data-table"><thead><tr><th>מספר / אירוע</th><th>לקוח</th><th>תאריכים</th><th>שלב</th><th>שווי</th>${compact?"":"<th>מוכנות</th>"}<th>פעולה</th></tr></thead><tbody>${items.map(b=>`<tr><td><span class="table-title">${esc(b.title)}</span><span class="table-meta mono">${esc(b.id)}</span></td><td>${esc(b.client)}</td><td><span class="mono">${dates(b.start,b.end)}</span></td><td>${badge(b.status)}</td><td><span class="table-amount">${money(b.amount)}</span></td>${compact?"":`<td style="min-width:100px"><div class="progress-row"><small>${b.readiness}%</small></div><div class="progress-line"><span style="width:${b.readiness}%"></span></div></td>`}<td><button class="table-action" data-action="booking-detail" data-id="${esc(b.id)}">פתח ←</button></td></tr>`).join("")}</tbody></table></div>`:`<div class="empty"><strong>לא נמצאו הזמנות</strong>נסה לשנות את מסנן החיפוש.</div>`}
function timeline(list,base="2026-11-09"){const t0=new Date(base+"T00:00:00").getTime();const one=86400000;const daynames=["ב׳","ג׳","ד׳","ה׳","ו׳","ש׳","א׳"];let title=`<div class="timeline-head">אירוע / ימים</div><div class="timeline-header">${Array.from({length:7},(_,i)=>{const d=new Date(t0+i*one);return `<div class="timeline-cell-head ${i===0?"today":""}"><b>${d.getDate()}</b>${daynames[i]}</div>`}).join("")}</div>`;let rows=list.slice(0,7).map((b,j)=>{const start=Math.round((new Date(b.start+"T00:00:00").getTime()-t0)/one);const end=Math.round((new Date(b.end+"T00:00:00").getTime()-t0)/one);const a=Math.max(0,start),z=Math.min(6,end);const visible=z>=a;const style=visible?`right:${(a/7)*100+0.5}%;width:${((z-a+1)/7)*100-1}%`:"";const shade=b.status==="מאושר"||b.status==="טיוטה"?"yell":b.status==="הוחזר"?"grn":"";return `<div class="timeline-row-title"><strong>${esc(b.title)}</strong><small>${esc(b.client)}</small></div><div class="timeline-track">${visible?`<div class="timeline-bar ${shade}" style="${style}" title="${esc(dates(b.start,b.end))}">${esc(b.id)}</div>`:""}</div>`}).join("");return `<div class="timeline">${title}${rows||"<div class='empty'>אין אירועים בטווח זה</div>"}</div>`}
function activity(limit=6){return `<div class="activity">${db.recent.slice(0,limit).map(x=>`<div class="activity-item"><span class="activity-bullet">${x.type==="warning"?"!":x.type==="ok"?"✓":"↗"}</span><div><strong>${esc(x.text)}</strong><small>${esc(x.when)}</small></div></div>`).join("")}</div>`}
function progressRow(name,value,maximum=100){const n=Math.round(value/maximum*100);return `<div><div class="progress-row"><small>${esc(name)}</small><b>${n}%</b></div><div class="progress-line"><span style="width:${Math.min(100,n)}%"></span></div></div>`}
function alertList(){return db.alerts.length?db.alerts.map(a=>`<div class="alert-row ${a.level}"><span class="alert-symbol">${a.level==="danger"?"!":a.level==="warning"?"◷":"✓"}</span><div><strong>${esc(a.title)}</strong><small>${esc(a.detail)}</small></div><button class="tiny-link" data-action="resolve-alert" data-id="${esc(a.id)}">טופל</button></div>`).join(""):`<div class="empty"><strong>אין התראות פתוחות</strong>כל בדיקות הדמו סומנו כטופלו.</div>`}
function dashboard(){
const open=db.bookings.filter(b=>b.status!=="הוחזר"&&b.status!=="טיוטה");const ready=Math.round(open.reduce((s,b)=>s+b.readiness,0)/(open.length||1));
const avail=db.equipment.reduce((s,e)=>s+e.available,0),total=db.equipment.reduce((s,e)=>s+e.total,0),sum=db.bookings.filter(b=>b.status!=="הוחזר").reduce((s,b)=>s+b.amount,0);
return head("מרכז השליטה","תמונת מצב של כלל ההזמנות, הציוד, הצוותים וההובלות. כל הנתונים להמחשה בלבד.","LIVE WORKSPACE / OVERVIEW",`<span class="date-tag">◷ יום שני, 09.11.2026</span><button class="outline-btn" data-action="reset-request">${ico("refresh")} איפוס הדמו</button>`)+`
<section class="hero"><div class="hero-copy"><span class="hero-mini">✦ YOUR ENTIRE OPERATION. ONE LOOP.</span><h2>רואים את התמונה.<br><span>מפעילים את כל המערכת.</span></h2><p>מתכנון הפקה ועד החזרת הפריט האחרון — בקרה רציפה על הזמנות, מלאי, מחסן, צוותים והובלות, במקום אחד.</p><div class="hero-buttons"><button class="primary-btn" data-action="open-new">+ צור הזמנה חדשה</button><button class="ghost-btn" data-action="navigate" data-route="planning">צפה בלוח התכנון ${ico("arrow")}</button></div></div><div class="hero-gauge" style="background:conic-gradient(from 260deg,#83e7ec 0 ${ready}%,#416b7c ${ready}% 100%)"><div class="gauge-text"><strong>${ready}%</strong><span>מוכנות ממוצעת</span></div></div></section>
<div class="kpi-grid">${metric("הזמנות פעילות",fmt.format(open.length),`<b>↗ 3</b> אירועים מתקרבים`,"calendar")}${metric("יחידות ציוד זמינות",fmt.format(avail),`מתוך ${fmt.format(total)} יחידות ברישום`,"equipment")}${metric("שווי הזמנות פתוחות",money(sum),`<b>↗ 12.8%</b> תרחיש הדגמה`,"finance","#a8a0ff")}${metric("משימות לביצוע",db.tasks.filter(t=>!t.done).length,`<b>${db.alerts.length} התראות</b> דורשות תשומת לב`,"tasks","#ffd485")}</div>
<div class="dashboard-grid">
<section class="panel"><div class="panel-header"><div><h3>לוח תכנון · 7 הימים הקרובים</h3><div class="hint">מעקב ויזואלי אחר חלונות פעילות והפקות</div></div><button class="panel-action" data-action="navigate" data-route="planning">כל התכנון ←</button></div>${timeline(db.bookings)}</section>
<section class="panel"><div class="panel-header"><div><h3>התראות שדורשות טיפול</h3><div class="hint">סיכונים ותלויות תפעוליות</div></div>${badge(db.alerts.length+" פתוחות","st-warning")}</div>${alertList()}</section>
<section class="panel"><div class="panel-header"><div><h3>אירועים והזמנות במעקב</h3><div class="hint">לחיצה על שורה פותחת כרטיס הזמנה פעיל</div></div><button class="panel-action" data-action="navigate" data-route="bookings">כל ההזמנות ←</button></div>${bookingTable(db.bookings.filter(b=>b.status!=="הוחזר"),4,true)}</section>
<div class="dashboard-side-stack"><section class="panel"><div class="panel-header"><div><h3>תפוסת מלאי</h3><div class="hint">אחוז מהציוד בשימוש או בשיבוץ</div></div><button class="panel-action" data-action="navigate" data-route="equipment">קטלוג ←</button></div><div class="bars">${["צילום","תאורה","סאונד","במה","וידאו"].map((cat,i)=>{const es=db.equipment.filter(x=>x.category===cat),a=es.reduce((s,e)=>s+e.total-e.available,0),t=es.reduce((s,e)=>s+e.total,0);return `<div class="bar-row"><span class="bar-label">${cat}</span><div class="bar-rail"><span class="bar-fill" style="width:${t?Math.round(100*a/t):0}%;background:${COLORS[i]}"></span></div><span class="bar-percent">${t?Math.round(100*a/t):0}%</span></div>`}).join("")}</div></section><section class="panel"><div class="panel-header"><div><h3>פעילות אחרונה</h3><div class="hint">יומן פעולות והתרחשויות בסביבת הדמו</div></div></div>${activity(4)}</section></div>
</div><p class="lock-note section-gap">ⓘ זהו דמו עצמאי: הפעולות מוצגות ומעודכנות בדפדפן בלבד. אין חיבור לתשלומים, ללקוחות, לשרתים או לתפעול חי.</p>`;
}
function bookings(){
const list=db.bookings.filter(b=>(filter==="all"||filter==="active"&&["ליקוט","יצא לשטח"].includes(b.status)||filter==="approved"&&b.status==="מאושר"||filter==="draft"&&b.status==="טיוטה"||filter==="returned"&&b.status==="הוחזר")&&[b.id,b.title,b.client,b.venue].join(" ").toLowerCase().includes(search.toLowerCase()));
const f=([id,name])=>`<button class="filter ${filter===id?"active":""}" data-action="filter" data-val="${id}">${name}</button>`;
return head("הזמנות והפקות","ניהול משולב של לקוחות, מועדים, שלבי ליקוט, יציאה לשטח והחזרות.","RENTALS / BOOKINGS",`<button class="primary-btn" data-action="open-new">+ הזמנה חדשה</button>`)+`<div class="kpi-grid">${metric("כל ההזמנות",db.bookings.length,"כולל טיוטות והחזרות","calendar")}${metric("מאושרות",db.bookings.filter(b=>b.status==="מאושר").length,"ממתינות לתחילת ליקוט","check")}${metric("ביצוע / בשטח",db.bookings.filter(b=>["ליקוט","יצא לשטח"].includes(b.status)).length,"תהליכים שנמצאים בתנועה","fleet")}${metric("היקף כולל",money(db.bookings.reduce((s,b)=>s+b.amount,0)),"ערכים מומצאים לצורכי הדגמה","finance")}</div><section class="panel"><div class="toolbar"><div class="filters" style="margin:0">${[["all","הכל"],["active","ביצוע"],["approved","מאושרות"],["draft","טיוטות"],["returned","הוחזרו"]].map(f).join("")}</div><span class="toolbar-text">${list.length} רשומות · תצוגת הדגמה</span></div>${bookingTable(list)}</section>`;
}
function planning(){
const today="2026-11-09",end="2026-11-15";const list=db.bookings.filter(b=>b.end>=today&&b.start<=end);
return head("לוח תכנון והפקות","תכנון לפי טווחי שימוש, תאריכי הזמנות ועומסים על צוותים וציוד.","PLANNING / TIMELINE",`<button class="primary-btn" data-action="open-new">+ שיבוץ חדש</button>`)+`<section class="panel"><div class="panel-header"><div><h3>אירועים · שבוע עבודה</h3><div class="hint">תאריכי הזמנה מתוך נתוני התנסות · 09–15 בנובמבר</div></div><span class="hint-pill">${list.length} שיבוצים</span></div>${timeline(list)}<p class="lock-note">תרשים זה מדגים ציר זמן ותלות בציוד. סרגלי הזמן אינם ניתנים לגרירה בדמו הציבורי.</p></section><div class="dashboard-grid section-gap"><section class="panel"><div class="panel-header"><div><h3>אירועים קרובים</h3><div class="hint">כרטיסי פעולה לניהול תוכנית ההפקה</div></div></div>${bookingTable(list.slice(0,4),4,true)}</section><section class="panel"><div class="panel-header"><div><h3>בדיקת עומסים</h3><div class="hint">ניצול משאבי ציוד מרכזיים</div></div></div><div class="bars">${["תאורה","סאונד","במה","צילום"].map(c=>{let e=db.equipment.filter(x=>x.category===c),tot=e.reduce((s,x)=>s+x.total,0),v=e.reduce((s,x)=>s+x.total-x.available,0);return `<div class="bar-row"><span class="bar-label">${c}</span><div class="bar-rail"><span class="bar-fill" style="width:${Math.round(v/tot*100)}%"></span></div><span class="bar-percent">${Math.round(v/tot*100)}%</span></div>`}).join("")}</div></section></div>`;
}
function warehouse(){
const lanes=[["ממתינות לאישור",b=>["טיוטה","מאושר"].includes(b.status)],["בליקוט",b=>b.status==="ליקוט"],["יצאו לשטח",b=>b.status==="יצא לשטח"],["הוחזרו",b=>b.status==="הוחזר"]];
const next={"טיוטה":"מאושר","מאושר":"ליקוט","ליקוט":"יצא לשטח","יצא לשטח":"הוחזר"};
return head("רצפת המחסן","ליקוט, יציאה לשטח והחזרה — תהליך ביצוע הדגמתי על גבי כרטיסי הזמנות.","WAREHOUSE / EXECUTION",`<span class="hint-pill">FLOW: אישור → ליקוט → יציאה → החזרה</span>`)+`<div class="notice"><strong>התנסות חופשית:</strong> לחיצה על ״העבר לשלב הבא״ מעדכנת את כרטיס ההזמנה, מונה השלבים ויומן האירועים בדפדפן. אין סריקה פיזית ואין שינוי במלאי אמיתי.</div><div class="warehouse-board">${lanes.map(([name,fn],i)=>{const bs=db.bookings.filter(fn);return `<div class="board-lane"><div class="lane-head"><span>${name}</span><span class="lane-num">${bs.length}</span></div>${bs.map(b=>`<div class="board-ticket"><span class="mono muted tiny">${esc(b.id)}</span><strong>${esc(b.title)}</strong><small>${esc(b.client)} · ${dates(b.start,b.end)}</small><div class="ticket-foot">${badge(b.status)}${next[b.status]?`<button class="table-action" data-action="next-status" data-id="${esc(b.id)}">העבר ←</button>`:"<span class='muted tiny'>הושלם</span>"}</div></div>`).join("")||"<div class='empty' style='padding:24px 8px'><strong>אין פריטים</strong>העמודה ריקה</div>"}</div>`}).join("")}</div><div class="dashboard-grid section-gap"><section class="panel"><div class="panel-header"><div><h3>פעילות מחסן אחרונה</h3><div class="hint">שינויי סטטוס נשמרים בדפדפן</div></div></div>${activity(5)}</section><section class="panel"><div class="panel-header"><div><h3>מחסנים במערכת</h3><div class="hint">חלוקה בין אתרי אחסון מדומים</div></div></div><div class="bars">${["ראשי","מערב","מזרח","חוץ"].map(name=>{const es=db.equipment.filter(e=>e.warehouse===name);return `<div class="bar-row"><span class="bar-label">מחסן ${name}</span><div class="bar-rail"><span class="bar-fill" style="width:${Math.min(100,es.reduce((s,e)=>s+e.total,0)/220*100)}%"></span></div><span class="bar-percent">${es.length}</span></div>`}).join("")}</div></section></div>`;
}
function equipment(){
const cats=["הכל",...new Set(db.equipment.map(x=>x.category))],items=db.equipment.filter(e=>(category==="הכל"||e.category===category)&&[e.id,e.name,e.category,e.warehouse].join(" ").toLowerCase().includes(search.toLowerCase()));
return head("קטלוג הציוד","מלאי מפורט, סריאלים לדוגמה, מצב זמינות ובדיקות תחזוקה.","ASSETS / INVENTORY",`<span class="date-tag">${db.equipment.length} דגמי ציוד</span>`)+`<div class="toolbar"><div class="filters" style="margin:0">${cats.map(c=>`<button class="filter ${category===c?"active":""}" data-action="category" data-val="${esc(c)}">${esc(c)}</button>`).join("")}</div><span class="toolbar-text">${items.length} פריטים</span></div><div class="equipment-grid">${items.map(e=>{const percent=Math.round(e.available/e.total*100);const icon=e.category==="צילום"?"▣":e.category==="תאורה"?"✺":e.category==="סאונד"?"♫":e.category==="במה"?"▤":e.category==="וידאו"?"◈":"⚡";return `<article class="equipment-card"><div class="eq-card-top"><span class="eq-category">${esc(e.category)}</span><span class="eq-symbol">${icon}</span></div><h3>${esc(e.name)}</h3><div class="eq-code">${esc(e.id)}</div><div class="eq-split"><strong>${fmt.format(e.available)} <small>/ ${fmt.format(e.total)} זמינים</small></strong><span class="muted tiny">${esc(e.warehouse)}</span></div><div class="progress-line"><span style="width:${percent}%;${percent<40?"background:#f7ad85":""}"></span></div><div class="eq-foot"><span>בריאות ציוד <b>${e.health}%</b></span><button class="tiny-link" data-action="equipment-detail" data-id="${e.id}">כרטיס ציוד ←</button></div></article>`}).join("")}</div>${!items.length?"<div class='empty'>לא נמצאו פריטים</div>":""}`;
}
function kits(){
return head("ערכות וחבילות ציוד","חבילות מרובות רכיבים להרכבת הפקות, עם בדיקת זמינות רעיונית.","BUNDLES / SMART KITS",`<span class="hint-pill">3 ערכות לדוגמה</span>`)+`<div class="notice"><strong>דמו התנהגותי:</strong> אפשר לצרף ערכה להזמנה מדומה קיימת. פעולה זו מקשרת את רכיבי הערכה להזמנה, ואינה שומרת הזמנה בשרת או מבצעת הקצאת מלאי פיזית.</div><div class="kit-grid">${db.kits.map((k,i)=>`<article class="kit-card"><div class="kit-icon">${["◎","▱","◈"][i]}</div><span class="eyebrow">PACKAGE ${String(i+1).padStart(2,"0")}</span><h3>${esc(k.name)}</h3><p>${esc(k.note)}</p><div class="kit-list">${k.components.map(([id,n])=>{const e=db.equipment.find(x=>x.id===id);return `<div class="kit-item"><span>${esc(e?.name||id)}</span><b>×${n}</b></div>`}).join("")}</div><button class="primary-btn" data-action="kit-apply" data-id="${k.id}">+ הוסף ערכה להזמנה</button></article>`).join("")}</div>`;
}
function fleet(){
const onRoad=db.vehicles.filter(v=>v.status==="בדרך לאירוע").length;
return head("צי רכבים והובלות","לוחות הובלה, נהגים, קיבולת וסטטוס יציאה במסגרת סביבת הדגמה.","LOGISTICS / FLEET",`<span class="date-tag">◷ תכנון נסיעות · 09.11</span>`)+`<div class="kpi-grid">${metric("כלי רכב",db.vehicles.length,"צי מדומה","fleet")}${metric("בדרך לאירוע",onRoad,"תנועה מדומה בלבד","planning")}${metric("זמינים",db.vehicles.filter(v=>v.status==="זמין").length,"ממתינים לשיבוץ","check")}${metric("העמסה ממוצעת",Math.round(db.vehicles.reduce((s,v)=>s+v.load,0)/db.vehicles.length)+"%","אחוזי קיבולת מומצאים","warehouse")}</div><div class="fleet-grid">${db.vehicles.map(v=>`<div class="fleet-card"><div class="fleet-card-top"><div class="fleet-vehicle"><span class="truck-icon">↦</span><div><h3>${esc(v.name)}</h3><small class="mono">${esc(v.plate)}</small></div></div>${badge(v.status)}</div><div class="fleet-route">⌁ ${esc(v.route)}</div><div class="progress-row"><small>ניצול קיבולת</small><b>${v.load}%</b></div><div class="progress-line"><span style="width:${v.load}%"></span></div><div class="fleet-meta"><span>נהג: <b>${esc(v.driver)}</b></span><span>ETA: <b>${esc(v.eta)}</b></span></div><button class="outline-btn" data-action="advance-trip" data-id="${v.id}">עדכן מצב נסיעה ↗</button></div>`).join("")}</div><p class="lock-note section-gap">ללא איתור GPS, ניווט אמיתי או קשר למערכות שינוע חיצוניות.</p>`;
}
function finance(){
const total=db.quotes.reduce((s,x)=>s+x.amount,0),accepted=db.quotes.filter(x=>x.status==="אושר").reduce((s,x)=>s+x.amount,0);
return head("הצעות מחיר ופיננסים","מעקב גרסאות והתקדמות מסחרית, ללא חיובים, חשבוניות או גבייה אמיתית.","COMMERCIAL / QUOTES",`<span class="hint-pill">₪ ILS · DATA SIMULATION</span>`)+`<div class="kpi-grid">${metric("שווי הצעות",money(total),"תרחיש מסחרי מדומה","finance")}${metric("הצעות מאושרות",db.quotes.filter(x=>x.status==="אושר").length,"אפשר לאשר הצעות נוספות","check")}${metric("סך מאושר",money(accepted),"אינו סכום שנגבה","reports")}${metric("בטיפול",db.quotes.filter(x=>x.status!=="אושר").length,"טיוטות והצעות שנשלחו","tasks")}</div><section class="panel"><div class="panel-header"><div><h3>לוח הצעות מסחריות</h3><div class="hint">מספרי הצעות, לקוח, שווי ותהליך אישור מדומה</div></div></div><div class="table-wrap"><table class="data-table"><thead><tr><th>מספר / נושא</th><th>לקוח</th><th>תאריך</th><th>שווי</th><th>סטטוס</th><th>פעולה</th></tr></thead><tbody>${db.quotes.map(q=>`<tr><td><b class="table-title">${esc(q.title)}</b><span class="table-meta mono">${esc(q.id)}</span></td><td>${esc(q.client)}</td><td><span class="mono">${esc(q.updated)}</span></td><td><b class="table-amount">${money(q.amount)}</b></td><td>${badge(q.status)}</td><td><button class="table-action" data-action="accept-quote" data-id="${q.id}" ${q.status==="אושר"?"disabled":""}>${q.status==="אושר"?"אושר ✓":"אשר הצעה ←"}</button></td></tr>`).join("")}</tbody></table></div></section><div class="notice section-gap"><strong>מגבלת הדגמה:</strong> לא קיימים כאן חיבורי תשלום, חשבונאות, הפקת חשבוניות או אישור חתימה מחייב. הערכים והאישורים הם מצגי הדגמה בלבד.</div>`;
}
function operations(){
return head("משימות ובקרה","פעולות יום, התראות תחזוקה ומעקב ביצוע בין צוותי ההפקה והמחסן.","OPERATIONS / TASKS",`<span class="hint-pill">${db.tasks.filter(x=>!x.done).length} משימות פתוחות</span>`)+`<div class="dashboard-grid"><section class="panel"><div class="panel-header"><div><h3>משימות תפעול</h3><div class="hint">לחיצה על סימון מעדכנת התקדמות ומייצרת אירוע</div></div></div>${db.tasks.map(t=>`<div class="task-line ${t.done?"done":""}"><button class="check-button ${t.done?"checked":""}" data-action="toggle-task" data-id="${t.id}" aria-label="סימון משימה">${t.done?"✓":""}</button><div><strong>${esc(t.title)}</strong><small>${esc(t.project)} · ${esc(t.owner)} · ${esc(t.due)}</small></div>${badge(t.priority)}</div>`).join("")}</section><div class="dashboard-side-stack"><section class="panel"><div class="panel-header"><div><h3>בקרת התראות</h3><div class="hint">התראות ציוד, מלאי וסדרי עדיפות</div></div></div>${alertList()}</section><section class="panel"><div class="panel-header"><div><h3>מדדי השלמה</h3></div></div>${progressRow("משימות שהושלמו",db.tasks.filter(x=>x.done).length,db.tasks.length)}<div class="section-gap">${progressRow("הזמנות במוכנות מלאה",db.bookings.filter(x=>x.readiness===100).length,db.bookings.length)}</div></section></div></div>`;
}
function chart(){
const data=db.revenue, w=850,h=230,pad=16,max=Math.max(...data)*1.12, pts=data.map((d,i)=>[pad+i*(w-2*pad)/(data.length-1),h-14-d/max*(h-35)]),path=pts.map((p,i)=>`${i?"L":"M"}${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(" "),fill=path+` L${pts[pts.length-1][0]} ${h-14} L${pad} ${h-14} Z`;return `<div class="chart-wrap"><svg viewBox="0 0 ${w} ${h}" role="img" aria-label="תרשים שווי הכנסות מדומות ל-12 תקופות"><defs><linearGradient id="area" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#6ee7f5" stop-opacity=".36"/><stop offset="100%" stop-color="#6ee7f5" stop-opacity=".01"/></linearGradient></defs>${[.25,.5,.75,1].map(n=>`<path d="M${pad} ${h-14-n*(h-35)}H${w-pad}" stroke="#345065" stroke-width="1" stroke-dasharray="5 5"/>`).join("")}<path d="${fill}" fill="url(#area)"/><path d="${path}" fill="none" stroke="#6ee7f5" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>${pts.map(p=>`<circle cx="${p[0]}" cy="${p[1]}" r="3.6" fill="#6ee7f5" stroke="#0e2332" stroke-width="2"/>`).join("")}</svg></div><div class="chart-labels">${["DEC","JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV"].map(s=>`<span>${s}</span>`).join("")}</div>`}
function reports(){
const a=db.equipment.reduce((s,e)=>s+e.available,0),t=db.equipment.reduce((s,e)=>s+e.total,0),used=t-a;
return head("דוחות ותובנות","מדדי פעילות, ניצול ציוד ותרחישי מגמה לשם המחשת ממשק אנליטי.","INTELLIGENCE / ANALYTICS",`<span class="hint-pill">SYNTHETIC METRICS</span>`)+`<div class="notice"><strong>חשוב לדעת:</strong> הגרפים, המגמות ומדדי הכספים נבנו מנתוני דוגמה מומצאים; אין להם קשר לביצועים עסקיים אמיתיים.</div><div class="report-grid"><section class="panel"><div class="panel-header"><div><h3>מגמת שווי פעילות מדומה</h3><div class="hint">12 נקודות זמן · ערכים להמחשה בלבד</div></div>${badge("תצוגת דמו","st-info")}</div><div class="report-number">₪124,000 <small>↗ 17.6%</small></div>${chart()}</section><section class="panel"><div class="panel-header"><div><h3>ניתוח מלאי</h3><div class="hint">חלוקה לפי מצב זמינות</div></div></div><div class="metric-visual"><div class="ring" style="background:conic-gradient(var(--cyan) 0 ${Math.round(100*a/t)}%,#344a60 ${Math.round(100*a/t)}% 100%)"><strong>${Math.round(100*a/t)}%</strong></div><div class="legend-list" style="flex:1"><div class="legend-item"><span class="dot" style="background:var(--cyan)"></span><span>זמין</span><span class="percent">${fmt.format(a)}</span></div><div class="legend-item"><span class="dot" style="background:#526b83"></span><span>מוקצה / מושאל</span><span class="percent">${fmt.format(used)}</span></div><div class="legend-item"><span class="dot" style="background:#a8a0ff"></span><span>סוגי ציוד</span><span class="percent">${db.equipment.length}</span></div></div></div><div class="section-gap">${progressRow("בריאות ציוד ממוצעת",Math.round(db.equipment.reduce((s,e)=>s+e.health,0)/db.equipment.length))}</div></section></div><div class="dashboard-grid section-gap"><section class="panel"><div class="panel-header"><div><h3>עומס לפי קטגוריית ציוד</h3><div class="hint">ניצול מוצג על בסיס מלאי המדגם</div></div></div><div class="bars">${["סאונד","תאורה","צילום","במה","וידאו","חשמל"].map((c,i)=>{const l=db.equipment.filter(x=>x.category===c),tt=l.reduce((s,e)=>s+e.total,0),aa=l.reduce((s,e)=>s+e.available,0),v=Math.round((tt-aa)/tt*100);return `<div class="bar-row"><span class="bar-label">${c}</span><span class="bar-rail"><span class="bar-fill" style="width:${v}%;background:${COLORS[i%5]}"></span></span><span class="bar-percent">${v}%</span></div>`}).join("")}</div></section><section class="panel"><div class="panel-header"><div><h3>התפלגות שלבי הזמנות</h3><div class="hint">מצב נוכחי בסביבת הדמו</div></div></div><div class="notebook-list">${["טיוטה","מאושר","ליקוט","יצא לשטח","הוחזר"].map(s=>`<div class="progress-row"><span>${badge(s)}</span><b>${db.bookings.filter(b=>b.status===s).length}</b></div>`).join("")}</div></section></div>`;
}
function bookingModal(id){const b=db.bookings.find(x=>x.id===id);if(!b)return;
const parts=b.equipment.map(id=>db.equipment.find(x=>x.id===id)).filter(Boolean);modal(`<h2 class="modal-title">${esc(b.title)}</h2><p class="modal-sub"><span class="mono">${esc(b.id)}</span> · ${esc(b.client)} · נתוני הדגמה בלבד</p><div class="tag-list">${badge(b.status)}<span class="tag">${b.readiness}% מוכנות</span><span class="tag">${esc(b.team)} אנשי צוות</span></div><div class="detail-grid"><div class="detail-cell"><small>מועד</small><strong>${dates(b.start,b.end)}</strong></div><div class="detail-cell"><small>מיקום</small><strong>${esc(b.venue)}</strong></div><div class="detail-cell"><small>מנהל/ת</small><strong>${esc(b.owner)}</strong></div><div class="detail-cell"><small>שווי מדומה</small><strong>${money(b.amount)}</strong></div></div><h3 class="section-heading section-gap">ציוד משויך</h3><ul class="detail-items">${parts.map(e=>`<li><span>${esc(e.name)}</span><span class="mono muted">${e.id}</span></li>`).join("")||"<li>לא שויך ציוד</li>"}</ul><div class="form-actions"><button class="outline-btn" data-action="close-modal">סגור</button>${b.status!=="הוחזר"?`<button class="primary-btn" data-action="next-status" data-id="${b.id}">העבר לשלב הבא ←</button>`:""}</div>`)}
function equipmentModal(id){const e=db.equipment.find(x=>x.id===id);if(!e)return;modal(`<h2 class="modal-title">${esc(e.name)}</h2><p class="modal-sub"><span class="mono">${e.id}</span> · ציוד לדוגמה · מחסן ${esc(e.warehouse)}</p><div class="detail-grid"><div class="detail-cell"><small>סך יחידות</small><strong>${e.total}</strong></div><div class="detail-cell"><small>זמינות</small><strong>${e.available}</strong></div><div class="detail-cell"><small>מחיר יומי מדומה</small><strong>${money(e.daily)}</strong></div><div class="detail-cell"><small>מדד בריאות</small><strong>${e.health}%</strong></div></div><div class="section-gap">${progressRow("יחידות זמינות",e.available,e.total)}</div><h3 class="section-heading section-gap">דוגמאות לזיהוי סריאלי</h3><div class="tag-list">${Array.from({length:Math.min(5,e.total)},(_,i)=>`<span class="tag mono">${e.id}-S${String(i+1).padStart(3,"0")}</span>`).join("")}</div><p class="lock-note">הסריאלים והמחירים הינם דוגמה להמחשה. אין כאן סריקות ברקוד אמיתיות.</p><div class="form-actions"><button class="primary-btn" data-action="close-modal">סגור כרטיס</button></div>`)}
function newBookingModal(){modal(`<h2 class="modal-title">יצירת הזמנה חדשה</h2><p class="modal-sub">התנסו בפתיחת הזמנה. הרשומה תישמר רק בדפדפן הנוכחי ולא תישלח לשום שרת.</p><form id="create-booking"><div class="form-grid"><div class="form-field full"><label for="booking-title">שם ההפקה</label><input id="booking-title" name="title" placeholder="לדוגמה: אירוע הדגמה 2026" maxlength="100" required></div><div class="form-field full"><label for="booking-client">לקוח</label><input id="booking-client" name="client" placeholder="לקוח בדוי לדוגמה" maxlength="70" required></div><div class="form-field"><label for="booking-start">תאריך התחלה</label><input id="booking-start" type="date" name="start" value="2026-11-18" required></div><div class="form-field"><label for="booking-end">תאריך סיום</label><input id="booking-end" type="date" name="end" value="2026-11-19" required></div><div class="form-field"><label for="booking-amount">שווי מדומה (₪)</label><input id="booking-amount" type="number" name="amount" min="0" max="10000000" step="100" value="25000" required></div><div class="form-field"><label for="booking-venue">מיקום / אולם</label><input id="booking-venue" name="venue" maxlength="70" value="חלל אירועים לדוגמה"></div></div><div class="form-actions"><button class="outline-btn" data-action="close-modal" type="button">ביטול</button><button class="primary-btn" type="submit">צור הזמנה בדמו ←</button></div></form>`)}
function kitModal(id){const kit=db.kits.find(x=>x.id===id);if(!kit)return;modal(`<h2 class="modal-title">שיוך ערכה להזמנה</h2><p class="modal-sub">${esc(kit.name)} · רכיבי הציוד יקושרו להזמנה בדפדפן בלבד.</p><form id="apply-kit"><input name="kit" value="${kit.id}" type="hidden"><div class="form-field"><label for="kit-booking">בחר הזמנה</label><select name="booking" id="kit-booking">${db.bookings.filter(x=>x.status!=="הוחזר").map(b=>`<option value="${b.id}">${esc(b.id)} — ${esc(b.title)}</option>`).join("")}</select></div><ul class="detail-items">${kit.components.map(([eid,n])=>{const e=db.equipment.find(x=>x.id===eid);return `<li>${esc(e?.name||eid)} <b>×${n}</b></li>`}).join("")}</ul><div class="form-actions"><button class="outline-btn" type="button" data-action="close-modal">ביטול</button><button class="primary-btn" type="submit">שייך ערכה ←</button></div></form>`)}
function modal(content){$("#dialog").innerHTML=content;$("#overlay").classList.remove("hidden");document.body.style.overflow="hidden";const first=$("#dialog input:not([type=hidden])");if(first)first.focus()}
function closeModal(){$("#overlay").classList.add("hidden");document.body.style.overflow="";$("#dialog").innerHTML=""}
function nextStatus(id){const b=db.bookings.find(x=>x.id===id),n={"טיוטה":"מאושר","מאושר":"ליקוט","ליקוט":"יצא לשטח","יצא לשטח":"הוחזר"}[b?.status];if(!n){notify("ההזמנה כבר הושלמה");return}b.status=n;b.readiness=n==="מאושר"?Math.max(b.readiness,50):n==="ליקוט"?80:100;log(`הזמנת ${b.id} הועברה לשלב ${n}`);persist();closeModal();render();notify(`${b.id} · עודכן ל-${n}`)}
function resetModal(){modal(`<h2 class="modal-title">איפוס נתוני הדמו</h2><p class="modal-sub">הפעולה משחזרת את ההזמנות, המשימות והתרחישים למצב ההתחלתי. כל השינויים שביצעתם בדפדפן יימחקו.</p><div class="form-actions"><button class="outline-btn" data-action="close-modal">ביטול</button><button class="primary-btn" data-action="reset-confirm">שחזר נתוני דוגמה</button></div>`)}
function render(){if(!navGroups.some(g=>g.items.some(x=>x[0]===tab)))tab="dashboard";navRender();const fn={dashboard,bookings,planning,warehouse,equipment,kits,fleet,finance,operations,reports}[tab];$("#view").innerHTML=fn();const current=navGroups.flatMap(g=>g.items).find(x=>x[0]===tab);$("#crumb").textContent=current?.[1]||"סקירה כללית";$("#topDate").textContent="09 NOV 2026"}
document.addEventListener("click",e=>{
const btn=e.target.closest("[data-action]");if(!btn)return;
const action=btn.dataset.action,id=btn.dataset.id,val=btn.dataset.val;
if(action==="navigate")navigate(btn.dataset.route);
if(action==="open-new")newBookingModal();
if(action==="close-modal")closeModal();
if(action==="reset-request")resetModal();
if(action==="reset-confirm"){db=clone(SEED);persist();filter="all";search="";$("#globalSearch").value="";closeModal();navigate("dashboard");notify("נתוני ההדגמה אופסו")}
if(action==="booking-detail")bookingModal(id);
if(action==="equipment-detail")equipmentModal(id);
if(action==="filter"){filter=val;render()}
if(action==="category"){category=val;render()}
if(action==="next-status")nextStatus(id);
if(action==="kit-apply")kitModal(id);
if(action==="advance-trip"){const v=db.vehicles.find(x=>x.id===id);if(!v)return;v.status=v.status==="זמין"?"בליקוט":v.status==="בליקוט"?"בדרך לאירוע":v.status==="בדרך לאירוע"?"חזר למחסן":"זמין";log(`סטטוס הובלה ${v.id} עודכן: ${v.status}`);persist();render();notify("תנועת הרכב עודכנה בסביבת הדמו")}
if(action==="accept-quote"){const q=db.quotes.find(x=>x.id===id);if(!q)return;q.status="אושר";log(`הצעת ${q.id} סומנה כאושרה (סימולציה)`);persist();render();notify("הצעת המחיר אושרה בדמו בלבד")}
if(action==="toggle-task"){const t=db.tasks.find(x=>x.id===id);if(!t)return;t.done=!t.done;log(`משימה ${t.id} ${t.done?"הושלמה":"נפתחה מחדש"}`);persist();render();notify(t.done?"משימה הושלמה":"משימה נפתחה מחדש")}
if(action==="resolve-alert"){const a=db.alerts.find(x=>x.id===id);if(!a)return;db.alerts=db.alerts.filter(x=>x.id!==id);log(`התראה ${a.id} סומנה כטופלה`);persist();render();notify("ההתראה סומנה כטופלה")}
});
document.addEventListener("submit",e=>{
if(e.target.id==="create-booking"){e.preventDefault();const fd=new FormData(e.target),o=Object.fromEntries(fd);if(o.end<o.start){notify("תאריך הסיום חייב להיות אחרי ההתחלה");return}const id="GL-"+String(Math.max(...db.bookings.map(x=>Number(x.id.replace(/\D/g,""))||0))+1);db.bookings.unshift({id,title:String(o.title).slice(0,100),client:String(o.client).slice(0,70),venue:String(o.venue).slice(0,70),start:o.start,end:o.end,status:"טיוטה",amount:Math.max(0,Math.min(10000000,Number(o.amount)||0)),owner:"צוות הדמו",equipment:[],team:0,readiness:10});log(`נפתחה הזמנה חדשה ${id}`);persist();closeModal();navigate("bookings");notify(`הזמנה ${id} נוצרה בהצלחה בדמו`)}
if(e.target.id==="apply-kit"){e.preventDefault();const fd=new FormData(e.target),b=db.bookings.find(x=>x.id===fd.get("booking")),k=db.kits.find(x=>x.id===fd.get("kit"));if(!b||!k)return;b.equipment=[...new Set([...b.equipment,...k.components.map(c=>c[0])])];b.readiness=Math.min(100,b.readiness+8);log(`ערכת ${k.id} צורפה להזמנת ${b.id}`);persist();closeModal();navigate("bookings");notify(`ערכת ${k.name} קושרה להזמנה ${b.id}`)}
});
$("#globalSearch").addEventListener("input",e=>{search=e.target.value;if(!["bookings","equipment"].includes(tab)&&search.length){tab="bookings";location.hash="bookings"}render()});
$("#topNew").addEventListener("click",newBookingModal);
$("#resetSidebar").addEventListener("click",resetModal);
$("#mobileNav").addEventListener("click",()=>$("#sidebar").classList.toggle("nav-open"));
$("#overlay").addEventListener("click",e=>{if(e.target===$("#overlay"))closeModal()});
document.addEventListener("keydown",e=>{if(e.key==="Escape"){closeModal();$("#sidebar").classList.remove("nav-open")}if(e.key==="/"&&!["INPUT","TEXTAREA","SELECT"].includes(document.activeElement?.tagName)){e.preventDefault();$("#globalSearch").focus()}});
window.addEventListener("hashchange",()=>{const h=location.hash.slice(1);if(h&&h!==tab){tab=h;render();window.scrollTo(0,0)}});
render();
})();