/* ═══════════════════════════════════════════════════════════ Views — Teams (inventory matrix), Bunker, Settings (catalog) FIXES: - Catalog delete now works even when ammo is in use: shows confirmation modal, removes from all guns + bunker - Rename/remap: merge one ammo type into another (fixes name mismatches after import) - Reset single gun: zero out all ammo for one gun - Delete buttons always clickable (no longer disabled when inUse) ═══════════════════════════════════════════════════════════ */ (function () { const { useState: useStateV } = React; const { GUNS: GUNS_V, CHARGE_BEHAVIORS: CB_V, CHARGE_TYPE_NAMES: CTN_V, parseKey: parseKeyV, keyLabel: keyLabelV, ammoQtyBadge: badgeV, totalAmmo: totalAmmoV, totalAmmoGuns: totalAmmoGunsV, totalAmmoBunker: totalAmmoBunkerV, calcGroupTotal: calcGroupTotalV, makeShellKey: mskV, makeFuzeKey: mfkV, makeChargeKey: mckV, makeTypeRecord: mtrV, genId: genIdV, Modal: ModalV, } = window; // go/GoGitMerge icon function MergeIcon({ size = 14 }) { return ( ); } const CAT_META = { shell: { label: 'פגזים', one: 'פגז', icon: '🎯', unit: 'פגזים' }, charge: { label: 'חנ"ה', one: 'חנ"ה', icon: '⚡', unit: 'יחידות' }, fuze: { label: 'מרעומים', one: 'מרעום', icon: '🟡', unit: 'מרעומים' }, }; const CAT_ORDER = ['shell', 'charge', 'fuze']; function readinessTier(state, g) { if (state.readiness?.red === g) return { cls: 'dot-danger', label: 'כוננות צוות אדום' }; if (state.readiness?.yellow === g) return { cls: 'dot-warn', label: 'כוננות צוות צהוב' }; return { cls: 'dot-ok', label: 'כוננות צוות ירוק' }; } // ── Teams / inventory matrix ───────────────────────────────── function TeamsView({ state, dispatch }) { const regs = state.registeredTypes; const totals = totalAmmoV(state); // all (guns + bunker) const totalsGun = totalAmmoGunsV(state); // on-ground only const totalsRamsav = totalAmmoBunkerV(state); // רמסע/bunker only const setRedLine = (key, v) => dispatch({ type: 'SET_RED_LINE', key, value: Math.max(0, parseInt(v) || 0) }); const setReadiness = (level, gun) => dispatch({ type: 'SET_READINESS', level, gun }); const [filter, setFilter] = useStateV('all'); const [resetGun, setResetGun] = useStateV(null); const doResetGun = () => { if (!resetGun) return; dispatch({ type: 'RESET_GUN_INVENTORY', gun: resetGun }); setResetGun(null); }; // ── Single gun card view ────────────────────────────────── function GunDetailView({ gun }) { const inv = state.guns[gun] || {}; const tier = readinessTier(state, gun); return (
צוות {gun} {tier.label}
{CAT_ORDER.map(cat => { const rows = regs.filter(r => r.category === cat); if (!rows.length) return null; return (
{CAT_META[cat].icon} {CAT_META[cat].label}
{rows.map(r => { const qty = inv[r.key] || 0; const allGuns = totalsGun[r.key] || 0; const ramsav = totalsRamsav[r.key] || 0; const total = allGuns + ramsav; const rl = state.redLines?.[r.key] || 0; const crit = rl > 0 && qty < rl; return ( ); })}
סוג תחמושת ערום קרקע קו אדום סה"כ קרקע רמסע סה"כ כולל
{r.label} {crit && } {badgeV(qty)} setRedLine(r.key, e.target.value)} style={{ width: '70px', textAlign: 'center' }} /> {allGuns} {ramsav} {total}
); })}
); } // ── Full matrix: guns as rows, ammo as columns ──────────── function AllGunsMatrix() { return (
{CAT_ORDER.map(cat => { const cols = regs.filter(r => r.category === cat); if (!cols.length) return null; return (
{CAT_META[cat].icon} {CAT_META[cat].label} {cols.length} סוגים
{cols.map(r => { const rl = state.redLines?.[r.key] || 0; const crit = rl > 0 && (totalsGun[r.key] || 0) < rl; return ( ); })} {GUNS_V.map(g => { const inv = state.guns[g] || {}; const tier = readinessTier(state, g); const gunTotal = cols.reduce((s, r) => s + (inv[r.key] || 0), 0); return ( {cols.map(r => { const qty = inv[r.key] || 0; const rl = state.redLines?.[r.key] || 0; const crit = rl > 0 && qty < rl; return ( ); })} ); })} {/* Totals: on-ground */} {cols.map(r => ( ))} {/* Totals: רמסע/bunker */} {cols.map(r => ( ))} {/* Totals: grand total */} {cols.map(r => { const total = (totalsGun[r.key] || 0) + (totalsRamsav[r.key] || 0); const rl = state.redLines?.[r.key] || 0; return ( ); })} {/* Red line inputs */} {cols.map(r => ( ))}
צוות {r.label}{crit ? ' ⚠' : ''} סה"כ
{g}
{badgeV(qty)} {gunTotal}
סה"כ ערום קרקע {totalsGun[r.key] || 0}
רמסע {totalsRamsav[r.key] || 0}
סה"כ כולל {total} {rl > 0 &&
קו: {rl}
}
קו אדום setRedLine(r.key, e.target.value)} style={{ width: '58px', textAlign: 'center', fontSize: '.75rem' }} />
); })}
); } return (
{/* ── Readiness + filter tabs in one bar ── */}

מטריצת מלאי צוותים

{/* Filter tabs */}
{GUNS_V.map(g => { const t = readinessTier(state, g); const isActive = filter === g; return ( ); })}
{/* Readiness selectors */}
{/* ── Content: single gun or full matrix ── */} {filter === 'all' ? : } {/* Reset gun confirmation */} {resetGun && ( setResetGun(null)}>
⚠ כל התחמושת של צוות {resetGun} תאופס לאפס. פינת פסולים לא תושפע. פעולה זו אינה הפיכה.
)}
); } // ── Bunker / pallets ───────────────────────────────────────── function EditPalletModal({ pallet, regs, dispatch, onClose }) { const [qtys, setQtys] = useStateV(() => { const o = {}; regs.forEach(r => { o[r.key] = String(pallet.ammo[r.key] || 0); }); return o; }); const submit = () => { const ammo = {}; regs.forEach(r => { const q = parseInt(qtys[r.key]) || 0; if (q > 0) ammo[r.key] = q; }); dispatch({ type: 'EDIT_PALLET_STOCK', palletId: pallet.id, ammo }); onClose(); }; return (
{regs.map(r => (
{r.label} {r.unit} setQtys(p => ({ ...p, [r.key]: e.target.value }))} style={{ width: '78px', textAlign: 'center' }} />
))}
); } function BunkerView({ state, dispatch, onAddPallet }) { const regs = state.registeredTypes; const [editing, setEditing] = useStateV(null); const [confirmClear, setConfirmClear] = useStateV(null); return (

מחסן ומשטחים

{state.bunker.length === 0 ? (
📦
אין משטחים במחסן — קלוט משטח חדש
) : (
{state.bunker.map(p => { const items = regs.filter(r => (p.ammo[r.key] || 0) > 0); const sum = items.reduce((s, r) => s + (p.ammo[r.key] || 0), 0); return (
משטח #{p.id} {sum} פריטים
{items.length === 0 ? ריק : items.map(r => (
{r.label}{badgeV(p.ammo[r.key])}
))}
); })}
)} {editing && setEditing(null)} />} {confirmClear && ( setConfirmClear(null)}>
המשטח יוסר לצמיתות מהמחסן. פעולה זו אינה הפיכה.
)}
); } // ── Settings / central catalog ─────────────────────────────── function AddTypeRow({ cat, regs, dispatch }) { const [shellName, setShellName] = useStateV(''); const [fuzeName, setFuzeName] = useStateV(''); const [chargeType, setChargeType] = useStateV(CTN_V[0]); const [series, setSeries] = useStateV(''); const buildRecord = () => { if (cat === 'shell') { const n = shellName.trim(); if (!n) return null; return mtrV(mskV(n)); } if (cat === 'fuze') { const n = fuzeName.trim(); if (!n) return null; return mtrV(mfkV(n)); } const s = series.trim(); if (!s) return null; return mtrV(mckV(chargeType, s)); }; const rec = buildRecord(); const exists = rec && regs.find(r => r.key === rec.key); const add = () => { if (!rec || exists) return; dispatch({ type: 'ADD_REGISTERED_TYPE', record: rec }); setShellName(''); setFuzeName(''); setSeries(''); }; return (
{cat === 'shell' &&
setShellName(e.target.value)} onKeyDown={e => e.key === 'Enter' && add()} />
} {cat === 'fuze' &&
setFuzeName(e.target.value)} onKeyDown={e => e.key === 'Enter' && add()} />
} {cat === 'charge' && <>
setSeries(e.target.value)} onKeyDown={e => e.key === 'Enter' && add()} />
}
{exists &&
⚠ סוג זה כבר קיים בקטלוג
}
); } // ── Remap modal: merge srcKey inventory into dstKey ────────── // Used to fix name mismatches after import. Moves all qty from // src to dst across all guns + bunker, then removes src from catalog. function RemapModal({ srcRec, regs, dispatch, onClose }) { const [dstKey, setDstKey] = useStateV(''); const samecat = regs.filter(r => r.key !== srcRec.key && r.category === srcRec.category); const canSubmit = !!dstKey; const doRemap = () => { if (!dstKey) return; dispatch({ type: 'REMAP_AMMO_TYPE', srcKey: srcRec.key, dstKey }); onClose(); }; return (
כל הכמויות של {srcRec.label} (בצוותים + מחסן) יועברו לסוג היעד שתבחר, ו-{srcRec.label} יוסר מהקטלוג. שימושי לתיקון שמות לאחר ייבוא.
{!samecat.length && (
אין סוגים אחרים באותה קטגוריה. הוסף קודם את סוג היעד לקטלוג.
)}
); } // ── Delete confirmation modal (force-delete even if in use) ── function DeleteConfirmModal({ rec, totals, dispatch, onClose }) { const qty = totals[rec.key] || 0; const doDelete = () => { dispatch({ type: 'FORCE_DELETE_TYPE', key: rec.key }); onClose(); }; return (
{qty > 0 ? (
⚠ קיים מלאי פעיל: {qty} יח' של {rec.label} בצוותים ובמחסן.
המחיקה תסיר את הסוג מהקטלוג ותמחק את כל הכמויות מכל הצוותים והמשטחים.
) : (
מחיקת {rec.label} מהקטלוג. אין מלאי פעיל.
)}
); } function ConfigView({ state, dispatch }) { const regs = state.registeredTypes; const totals = totalAmmoV(state); const [editKey, setEditKey] = useStateV(null); const [draft, setDraft] = useStateV(''); const [deleteRec, setDeleteRec] = useStateV(null); const [remapRec, setRemapRec] = useStateV(null); const M85 = window.M85_DEFAULT.key; const startEdit = r => { setEditKey(r.key); setDraft(r.label); }; const saveEdit = () => { if (draft.trim()) dispatch({ type: 'UPDATE_REGISTERED_TYPE', key: editKey, label: draft.trim() }); setEditKey(null); }; return (

קטלוג תחמושת מרכזי

זהו המקור היחיד לשמות הפגזים, החנ"ות והמרעומים. כל הטפסים, החישובים והדוחות במערכת יורשים את הקטלוג הזה.

{CAT_ORDER.map(cat => { const rows = regs.filter(r => r.category === cat); return (
{CAT_META[cat].icon} {CAT_META[cat].label} {rows.length} סוגים
{rows.length === 0 ?
אין סוגים — הוסף למטה
: (
{rows.map(r => { const inUse = (totals[r.key] || 0) > 0; const protectedRow = r.key === M85; return ( ); })}
שם / תוויתמפתח מערכתיחידהבמלאיפעולות
{editKey === r.key ? ( setDraft(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') saveEdit(); if (e.key === 'Escape') setEditKey(null); }} style={{ maxWidth: '220px' }} /> ) : {r.label}} {r.key} {r.unit} {inUse ? {totals[r.key]} : }
{editKey === r.key ? ( <> ) : ( <> )}
)}
); })} {deleteRec && ( setDeleteRec(null)} /> )} {remapRec && ( setRemapRec(null)} /> )} {/* ── Ammo groups panel ── */}
); } // ── Ammo groups management ─────────────────────────────────── // Groups let you define a named collection of ammo types // (e.g. "פגזי שדה" = OF-540 + M107) and set a red line on // the COMBINED total (on-ground + רמסע). function AmmoGroupsPanel({ state, dispatch }) { const regs = state.registeredTypes; const groups = state.ammoGroups || []; const [editing, setEditing] = useStateV(null); // group being edited const [adding, setAdding] = useStateV(false); // show add form return (
📦 קבוצות תחמושת

הגדר קבוצות של סוגי תחמושת לניטור מצטבר עם קו אדום על הסה"כ (קרקע + רמסע).

{groups.length === 0 && !adding && (
אין קבוצות — הוסף קבוצה חדשה
)} {groups.map(g => { const { gunTotal, bunkerTotal, total } = calcGroupTotalV(g, state); const crit = g.redLine > 0 && total < g.redLine; return editing?.id === g.id ? setEditing(null)} /> : (
{g.label} {crit && ⚠ מתחת לקו} {(g.keys || []).map(k => keyLabelV(k, regs)).join(' · ')}
{gunTotal} קרקע
{bunkerTotal} רמסע
{total} סה"כ{g.redLine > 0 ? ` / קו ${g.redLine}` : ''}
); })} {adding && ( setAdding(false)} isNew /> )}
); } function GroupEditForm({ group, regs, dispatch, onClose, isNew }) { const [label, setLabel] = useStateV(group?.label || ''); const [keys, setKeys] = useStateV(group?.keys || []); const [redLine, setRedLine] = useStateV(String(group?.redLine || 0)); const toggleKey = k => setKeys(prev => prev.includes(k) ? prev.filter(x => x !== k) : [...prev, k]); const save = () => { if (!label.trim() || keys.length === 0) return; const rl = parseInt(redLine) || 0; if (isNew) { dispatch({ type: 'ADD_AMMO_GROUP', label: label.trim(), keys, redLine: rl }); } else { dispatch({ type: 'EDIT_AMMO_GROUP', id: group.id, label: label.trim(), keys, redLine: rl }); } onClose(); }; return (
setLabel(e.target.value)} placeholder="לדוגמה: פגזי שדה" autoFocus />
setRedLine(e.target.value)} style={{ fontFamily: 'var(--font-mono)' }} placeholder="0 = כבוי" />
{regs.map(r => { const selected = keys.includes(r.key); return ( ); })}
{keys.length === 0 && בחר לפחות סוג אחד}
); } window.TeamsView = TeamsView; window.BunkerView = BunkerView; window.ConfigView = ConfigView; window.AmmoGroupsPanel = AmmoGroupsPanel; })();