/* ═══════════════════════════════════════════════════════════ FireMissionModal + EditGunStockModal Full combo/validation logic preserved; colors tokenized. ═══════════════════════════════════════════════════════════ */ (function () { const { useState: useStateF } = React; const { GUNS: GUNS_F, CHARGE_BEHAVIORS: CB_F, CHARGE_TYPE_NAMES: CTN_F, parseKey: parseKeyF, chargeInvDeduction: cidF, genId: genIdF, genMissionId: gmiF, makeShellKey: mskF, makeFuzeKey: mfkF, makeChargeKey: mckF, keyLabel: keyLabelF, ammoQtyBadge: badgeF, Modal: ModalF, PasswordGate: PasswordGateF, } = window; function emptyCombo() { return { id: genIdF(), shellKey: '', chargeKey: '', chargeSubOpt: '', fuzeKey: '', shellQty: '' }; } function legacyCombos(ammoItems) { const shells = ammoItems.filter(i => parseKeyF(i.key)?.category === 'shell'); const charges = ammoItems.filter(i => parseKeyF(i.key)?.category === 'charge'); const fuzes = ammoItems.filter(i => parseKeyF(i.key)?.category === 'fuze'); const count = Math.max(shells.length, charges.length, fuzes.length, 1); return Array.from({ length: count }, (_, i) => ({ id: genIdF(), shellKey: shells[i]?.key || '', chargeKey: charges[i]?.key || '', chargeSubOpt: charges[i]?.subOpt || '', fuzeKey: fuzes[i]?.key || '', shellQty: String(shells[i]?.qty || charges[i]?.qty || fuzes[i]?.qty || ''), })); } function FireMissionModal({ state, dispatch, onClose, editRow }) { const isEdit = !!editRow; const today = new Date().toISOString().split('T')[0]; const nowT = new Date().toTimeString().slice(0, 5); const CHECKLIST = [ { id: 'c1', label: 'שם משימה ונ.צ' }, { id: 'c2', label: 'הזנת נתונים' }, { id: 'c3', label: 'מברק' }, { id: 'c4', label: 'תיקון / נקייה' }, { id: 'c5', label: 'תחמושת' }, { id: 'c6', label: 'השוואת קו טווח' }, { id: 'c7', label: 'זווית נפילה' }, { id: 'c8', label: "טמפ' מטען" }, { id: 'c9', label: 'בדיקה בלתי תלויה' }, ]; function newTab() { return { id: genIdF(), date: today, time: nowT, unit: '', target: '', selGuns: [], gunCombos: {}, notes: '', localGuns: JSON.parse(JSON.stringify(state.guns)), borrowSrc: '', borrowDst: '', borrowQtys: {}, checks: {}, firstFiredAt: null, }; } const [tabs, setTabs] = useStateF(() => { if (isEdit) { const combos = editRow.combos?.length ? editRow.combos : legacyCombos(editRow.ammoItems || []); return [{ id: genIdF(), date: editRow.date, time: editRow.time, unit: editRow.unit, target: editRow.target, selGuns: [editRow.gun], gunCombos: { [editRow.gun]: combos }, notes: editRow.notes || '', localGuns: JSON.parse(JSON.stringify(state.guns)), borrowSrc: '', borrowDst: '', borrowQtys: {}, checks: {}, firstFiredAt: null, }]; } return [newTab()]; }); const [activeIdx, setActiveIdx] = useStateF(0); const updateTab = (idx, fn) => setTabs(prev => prev.map((t, i) => i === idx ? fn(t) : t)); const updateActive = fn => updateTab(activeIdx, fn); const m = tabs[activeIdx] || tabs[0]; const setDate = v => updateActive(t => ({ ...t, date: v })); const setTime = v => updateActive(t => ({ ...t, time: v })); const setUnit = v => updateActive(t => ({ ...t, unit: v })); const setTarget = v => updateActive(t => ({ ...t, target: v })); const setNotes = v => updateActive(t => ({ ...t, notes: v })); const toggleCheck = id => updateActive(t => ({ ...t, checks: { ...t.checks, [id]: !t.checks[id] } })); const checkAll = () => updateActive(t => ({ ...t, checks: Object.fromEntries(CHECKLIST.map(c => [c.id, true])) })); const clearAll = () => updateActive(t => ({ ...t, checks: {} })); const markFirstFired = () => updateActive(t => ({ ...t, time: new Date().toTimeString().slice(0, 5), firstFiredAt: new Date().toTimeString().slice(0, 5) })); const toggleGun = g => updateActive(t => { const selGuns = t.selGuns.includes(g) ? t.selGuns.filter(x => x !== g) : [...t.selGuns, g]; const gunCombos = t.gunCombos[g] ? { ...t.gunCombos } : { ...t.gunCombos, [g]: [emptyCombo()] }; return { ...t, selGuns, gunCombos }; }); const addCombo = g => updateActive(t => ({ ...t, gunCombos: { ...t.gunCombos, [g]: [...(t.gunCombos[g] || []), emptyCombo()] } })); const rmCombo = (g, id) => updateActive(t => ({ ...t, gunCombos: { ...t.gunCombos, [g]: (t.gunCombos[g] || []).filter(c => c.id !== id) } })); const setC = (g, id, field, val) => updateActive(t => { const combos = (t.gunCombos[g] || []).map(c => { if (c.id !== id) return c; const u = { ...c, [field]: val }; if (field === 'chargeKey') u.chargeSubOpt = ''; return u; }); return { ...t, gunCombos: { ...t.gunCombos, [g]: combos } }; }); function comboChargeBeh(c) { const p = parseKeyF(c.chargeKey); if (!p || p.category !== 'charge') return null; return CB_F[p.chargeType] || null; } function comboChargeInvDed(c) { const qty = parseInt(c.shellQty) || 0; if (!qty || !c.chargeKey) return 0; const p = parseKeyF(c.chargeKey); if (!p || p.category !== 'charge') return qty; return cidF(p.chargeType, c.chargeSubOpt, qty); } function avail(mi, g, key, excludeId) { const base = mi.localGuns[g]?.[key] || 0; const used = (mi.gunCombos[g] || []).filter(c => c.id !== excludeId).reduce((s, c) => { const qty = parseInt(c.shellQty) || 0; if (!qty) return s; if (c.shellKey === key || c.fuzeKey === key) return s + qty; if (c.chargeKey === key) return s + comboChargeInvDed(c); return s; }, 0); return Math.max(0, base - used); } function comboMaxQty(mi, g, c) { if (!c.shellKey && !c.chargeKey && !c.fuzeKey) return 9999; const beh = comboChargeBeh(c); const shellAv = c.shellKey ? avail(mi, g, c.shellKey, c.id) : Infinity; const fuzeAv = c.fuzeKey ? avail(mi, g, c.fuzeKey, c.id) : Infinity; let chargeAv = c.chargeKey ? avail(mi, g, c.chargeKey, c.id) : Infinity; if (beh?.kind === 'modular' && c.chargeSubOpt === 'M231/2') chargeAv = Math.floor(chargeAv / 2); return Math.min(shellAv, fuzeAv, chargeAv); } function comboIsValid(mi, g, c) { const qty = parseInt(c.shellQty) || 0; if (!c.shellKey || !c.chargeKey || !c.fuzeKey || qty <= 0) return false; const beh = comboChargeBeh(c); if (beh && (beh.kind === 'separate' || beh.kind === 'modular') && !c.chargeSubOpt) return false; const shellOk = qty <= avail(mi, g, c.shellKey, c.id); const fuzeOk = qty <= avail(mi, g, c.fuzeKey, c.id); const ded = comboChargeInvDed(c); const chargeOk = ded <= avail(mi, g, c.chargeKey, c.id); return shellOk && fuzeOk && chargeOk; } const ammoValidForTab = mi => { if (!mi.unit || !mi.target || !mi.date || !mi.time || mi.selGuns.length === 0) return false; for (const g of mi.selGuns) { const combos = mi.gunCombos[g] || []; if (!combos.length) return false; if (combos.some(c => !comboIsValid(mi, g, c))) return false; } return true; }; const checksComplete = mi => CHECKLIST.every(c => mi.checks[c.id]); const canSubmitTab = mi => ammoValidForTab(mi) && checksComplete(mi); const buildAmmoItems = (mi, g) => (mi.gunCombos[g] || []).flatMap(c => { const qty = parseInt(c.shellQty) || 0; if (!qty) return []; const items = []; if (c.shellKey) items.push({ key: c.shellKey, qty, subOpt: null, invDeduction: qty }); if (c.fuzeKey) items.push({ key: c.fuzeKey, qty, subOpt: null, invDeduction: qty }); if (c.chargeKey) items.push({ key: c.chargeKey, qty, subOpt: c.chargeSubOpt || null, invDeduction: comboChargeInvDed(c) }); return items; }); const submitEdit = () => { const mi = tabs[0]; dispatch({ type: 'EDIT_MISSION_ROW', rowId: editRow.id, gun: editRow.gun, oldAmmoItems: editRow.ammoItems, newAmmoItems: buildAmmoItems(mi, editRow.gun), newCombos: mi.gunCombos[editRow.gun] || [], newNotes: mi.notes, newDate: mi.date, newTime: mi.time }); onClose(); }; const submitTab = idx => { const mi = tabs[idx]; const missionId = gmiF(mi.date, state.nextMissionSeq || 1); const entries = mi.selGuns.map(g => ({ gun: g, ammoItems: buildAmmoItems(mi, g), combos: mi.gunCombos[g] || [] })); dispatch({ type: 'FIRE_MISSION', entries, meta: { date: mi.date, time: mi.time, unit: mi.unit, target: mi.target, missionId, notes: mi.notes } }); const nt = tabs.filter((_, j) => j !== idx); if (nt.length === 0) { onClose(); return; } setTabs(nt); setActiveIdx(Math.min(idx, nt.length - 1)); }; const borrowSrcAmmo = m.borrowSrc ? m.localGuns[m.borrowSrc] || {} : {}; const borrowAvailRegs = state.registeredTypes.filter(r => (borrowSrcAmmo[r.key] || 0) > 0); const setBQ = (key, v) => updateActive(t => ({ ...t, borrowQtys: { ...t.borrowQtys, [key]: Math.max(0, Math.min(t.localGuns[t.borrowSrc]?.[key] || 0, parseInt(v) || 0)) } })); const setBorrowSrc = v => updateActive(t => ({ ...t, borrowSrc: v, borrowQtys: {} })); const setBorrowDst = v => updateActive(t => ({ ...t, borrowDst: v })); const applyBorrow = () => { if (!m.borrowSrc || !m.borrowDst || m.borrowSrc === m.borrowDst) return; const items = borrowAvailRegs.map(r => ({ key: r.key, qty: m.borrowQtys[r.key] || 0 })).filter(i => i.qty > 0); if (!items.length) return; updateActive(t => { const n = JSON.parse(JSON.stringify(t.localGuns)); items.forEach(({ key, qty }) => { n[t.borrowSrc][key] = Math.max(0, (n[t.borrowSrc][key] || 0) - qty); n[t.borrowDst][key] = (n[t.borrowDst][key] || 0) + qty; }); return { ...t, localGuns: n, borrowSrc: '', borrowDst: '', borrowQtys: {} }; }); dispatch({ type: 'TRANSFER_GUN_TO_GUN', srcGun: m.borrowSrc, dstGun: m.borrowDst, items }); }; const tabLabel = (t, i) => t.target ? t.target : `משימה ${i + 1}`; const addTab = () => { setTabs(p => [...p, newTab()]); setActiveIdx(tabs.length); }; const closeTab = i => { const nt = tabs.filter((_, j) => j !== i); if (nt.length === 0) { onClose(); return; } setTabs(nt); setActiveIdx(Math.min(activeIdx, nt.length - 1)); }; const allChecked = checksComplete(m); const ammoReady = ammoValidForTab(m); return ( {!isEdit && (
{tabs.map((t, i) => (
{tabs.length > 1 && ( )}
))} {tabs.length > 1 && {tabs.filter(canSubmitTab).length}/{tabs.length} מוכנות}
)} {isEdit && (
⏱ עריכת בדיעבד: שנה תאריך ושעה כדי לעדכן את מיקום הרשומה בציר הזמן. המיון מתבצע אוטומטית עם השמירה.
)}
setDate(e.target.value)} />
{ if (!m.firstFiredAt || isEdit) setTime(e.target.value); }} readOnly={!!m.firstFiredAt && !isEdit} style={{ color: m.firstFiredAt && !isEdit ? 'var(--info)' : isEdit ? 'var(--ok)' : undefined, fontWeight: (m.firstFiredAt && !isEdit) || isEdit ? 700 : undefined }} />
{!isEdit && (
)}
setUnit(e.target.value)} />
setTarget(e.target.value)} />
{!isEdit && <>
בחר צוותים מתנערים:
{GUNS_F.map(g => )}
} {state.registeredTypes.length > 0 && (
📋 מלאי צוותים — חי (מתעדכן בעת השאלה)
{GUNS_F.map(g => )} {[...state.registeredTypes.filter(r => r.category === 'shell'), ...state.registeredTypes.filter(r => r.category === 'charge'), ...state.registeredTypes.filter(r => r.category === 'fuze')].map(r => ( {GUNS_F.map(g => )} ))}
תחמושתצוות {g}{m.selGuns.includes(g) ? ' ✓' : ''}
{r.label}{badgeF(m.localGuns[g]?.[r.key] || 0)}
)} {m.selGuns.map(g => { const combos = m.gunCombos[g] || []; const gShells = state.registeredTypes.filter(r => r.category === 'shell' && (m.localGuns[g]?.[r.key] || 0) > 0); const gCharges = state.registeredTypes.filter(r => r.category === 'charge' && (m.localGuns[g]?.[r.key] || 0) > 0); const gFuzes = state.registeredTypes.filter(r => r.category === 'fuze' && (m.localGuns[g]?.[r.key] || 0) > 0); return (
צוות {g} — תצורות ירי
{['פגז', 'חנ"ה', 'מטען', 'מרעום', 'כמות', ''].map((h, i) =>
{h}
)}
{combos.map(c => { const qty = parseInt(c.shellQty) || 0; const beh = comboChargeBeh(c); const needSub = beh && (beh.kind === 'separate' || beh.kind === 'modular'); const ded = comboChargeInvDed(c); const maxQ = comboMaxQty(m, g, c); const shellAv = c.shellKey ? avail(m, g, c.shellKey, c.id) : 0; const fuzeAv = c.fuzeKey ? avail(m, g, c.fuzeKey, c.id) : 0; const chargeAv = c.chargeKey ? avail(m, g, c.chargeKey, c.id) : 0; const shellOk = !c.shellKey || qty <= shellAv; const fuzeOk = !c.fuzeKey || qty <= fuzeAv; const chargeOk = !c.chargeKey || ded <= chargeAv; const allOk = shellOk && fuzeOk && chargeOk; const isValid = comboIsValid(m, g, c); return (
{needSub ? ( ) :
}
setC(g, c.id, 'shellQty', e.target.value)} style={{ textAlign: 'center' }} disabled={needSub && !c.chargeSubOpt} /> {combos.length > 1 ? :
}
{qty > 0 && c.shellKey && c.chargeKey && c.fuzeKey && (
🔵 פגז ×{qty} (זמין:{shellAv}) 🟡 מרעום ×{qty} (זמין:{fuzeAv}) ⚡ חנ"ה {beh?.kind === 'modular' ? `${ded} מודולים (${c.chargeSubOpt || '?'} × ${qty})` : `×${ded}`} (זמין:{chargeAv})
)} {qty > 0 && (!allOk || (!isValid && (c.shellKey || c.chargeKey || c.fuzeKey))) && (
⚠{!shellOk ? ` פגז חסר ${qty - shellAv}` : ''}{!fuzeOk ? ` | מרעום חסר ${qty - fuzeAv}` : ''}{!chargeOk ? ` | חנ"ה חסר ${ded - chargeAv}` : ''}{needSub && !c.chargeSubOpt ? ' | נדרש לבחור אזור חנ"ה' : ''}
)}
); })}
); })} {!isEdit && m.selGuns.length > 0 && (
↕ משוך חוסר מצוות אחר (השאלה)
{m.borrowSrc && borrowAvailRegs.length > 0 && (
{borrowAvailRegs.map(r => (
{r.label} זמין: {borrowSrcAmmo[r.key]} setBQ(r.key, e.target.value)} style={{ width: '60px', textAlign: 'center' }} />
))}
)} מעדכן מלאי ורושם בהיסטוריה מיד
)}