import { useEffect, useRef, useState } from 'react';
import { Link, router, useForm, useHttp, usePage } from '@inertiajs/react';
import { BarChart3, ChevronDown, History, Loader2, Plus, RefreshCw, Save, SlidersHorizontal, SquarePen, X } from 'lucide-react';
import AppLayout from '@/Layouts/AppLayout';
import { Modal } from '@/Components/Proto/UI/Modal';
import { Drawer } from '@/Components/Proto/UI/Drawer';
import { FloatingField } from '@/Components/Proto/UI/FloatingField';
import { StatusBadge } from '@/Components/Proto/UI/StatusBadge';
import { CheckBox } from '@/Components/Proto/UI/CheckBox';
import { useToast } from '@/Components/Toast';
import { ProjectDetailContent } from '@/Pages/MenuProjects/Projects/Detail';
import { CREATE_TARGETS, projectCreateTargets } from '@/lib/projectCreateTargets';

// Server-driven port of Pages/Proto/Projects/Board.jsx — real 6 columns (replacing the
// proto's 5-column mock config), real drag/status-change popover, and an inline-editable
// peek drawer (replacing the proto's full free-edit side-drawer with the same
// save-per-section pattern established by Detail.jsx). The checked-rows "Total Value"
// footer and the kanban-table-list view toggle remain deferred per the design spec.
// The 4 cross-module launch buttons (legacy listproject.php's Sample / Quotation / LWR /
// VisPlan) LANDED 2026-08-05 as the bulk bar's "Create ▾" dropdown — see
// docs/superpowers/specs/2026-08-05-company-project-create-linked-docs-design.md.
// The proto's client-side text search is also dropped: columns are
// server-paginated ("Load More"), so a client-side search over only the loaded cards would
// silently miss anything not yet paged in — the real filter bar below is the replacement.
//
// Editable drawer (2026-07-10): on open, GET details.edit-data fetches {canWrite, header,
// detail, companyCps, applications} into local state `editData` (NOT an Inertia page prop —
// this is a plain useHttp fetch, so a fresh GET after every save is how the drawer's own
// fields pick up server-computed values; router.reload() only refreshes the BOARD COLUMNS).
// pm/sm/mm (canWrite=false) keep the exact pre-existing read-only rendering sourced from
// `peekCard` (the board-column payload already carries everything that view needs). own/
// all/head (canWrite=true) render editable sections that mirror Detail.jsx's components
// almost verbatim — same field lists, same payload shapes, same useHttp().transform()+post()
// idiom — the only differences being: data comes from `editData` instead of Inertia props,
// there is no read-only mode inside these components (Board's read-only path is the OLD
// branch above, not a prop on these), and every `onSaved` callback both reloads the board
// columns AND re-fetches editData (afterSave()) instead of `router.reload({only:['details']})`.

// `statusIds` drives which underlying ItemStatusID values a card can carry while sitting in
// this column (used both to render the sub-status chip and — for Early Stage / Closed — to
// disambiguate a drop). Every column is a valid drop target, including Created: a line can be
// reset to its birth status (1) like any other backward move.
const COLUMNS = [
    { id: 'created', label: 'Created', statusIds: [1] },
    { id: 'earlyStage', label: 'Early Stage', statusIds: [2, 3] },
    { id: 'labTesting', label: 'Lab Testing', statusIds: [4] },
    { id: 'approved', label: 'Approved', statusIds: [5] },
    { id: 'quotation', label: 'Quotation', statusIds: [6] },
    { id: 'closed', label: 'Closed', statusIds: [7, 8] },
];

const STATUS_NAME = {
    1: 'Created', 2: 'Exploring', 3: 'Sample', 4: 'Lab Testing',
    5: 'Approved', 6: 'Quotation', 7: 'Failed', 8: 'Commercialized',
};
// Every selectable item status — feeds the BULK status picker (selection → change status).
const ALL_STATUS_IDS = [1, 2, 3, 4, 5, 6, 7, 8];
// Reverse lookup (StatusName → id) so the history timeline can tone its dot/badge —
// the history rows carry the name, not the id.
const STATUS_ID_BY_NAME = Object.fromEntries(Object.entries(STATUS_NAME).map(([id, name]) => [name, Number(id)]));

// "Ongoing" (in-progress) item statuses: Exploring…Quotation. The terminal Failed(7) /
// Commercialized(8) — and the Created(1) reset — are NOT ongoing. When the status being set
// is ongoing, the status-date picker's `min` is forced to today (you're marking it ongoing
// now, so it can't be back-dated); terminal statuses can record a past date. (Cath, 2026-07-21)
const ONGOING_STATUS_IDS = [2, 3, 4, 5, 6];

// Compact icon-only card action (History / Update / Competitor) — label via tooltip.
const CARD_ICON_BTN = 'grid size-7 place-items-center rounded-md border border-border bg-card text-muted-foreground transition-colors hover:border-primary hover:bg-accent hover:text-primary';

// Competitor data shown on HOVER (no click). Fixed-positioned so the column's overflow can't
// clip it; a top pad bridges the button→popover gap. `rect` is client-only (mouse-enter), so
// SSR never touches `window`. Reads the server card's competitor shape.
function CompetitorHover({ competitors = [] }) {
    const [rect, setRect] = useState(null);
    const ref = useRef(null);
    const show = () => { const r = ref.current?.getBoundingClientRect(); if (r) setRect(r); };
    const n = competitors.length;
    return (
        <div className="relative" onMouseEnter={show} onMouseLeave={() => setRect(null)} onClick={(e) => e.stopPropagation()}>
            <button ref={ref} type="button" title={`Competitor${n > 0 ? ` (${n})` : ''}`} aria-label="Competitor data"
                className="relative grid size-7 place-items-center rounded-md border border-border bg-card text-muted-foreground transition-colors hover:border-primary hover:bg-accent hover:text-primary">
                <BarChart3 className="size-3.5" aria-hidden="true" />
                {n > 0 && <span className="absolute -right-1 -top-1 grid min-w-[14px] place-items-center rounded-full bg-primary px-1 text-[9px] font-bold leading-[14px] text-white">{n}</span>}
            </button>
            {rect && (
                <div className="fixed z-[60] w-64 pt-1.5" style={{ left: Math.max(8, Math.min(rect.left, window.innerWidth - 272)), top: rect.bottom }}>
                    <div className="rounded-xl border border-border bg-card p-3 text-left shadow-modal">
                        <p className="m-0 mb-2 text-[10px] font-bold uppercase tracking-wide text-muted-foreground">Competitor</p>
                        {n === 0 ? (
                            <p className="m-0 text-[12px] text-muted-foreground">No competitor data.</p>
                        ) : (
                            <div className="flex flex-col gap-2">
                                {competitors.map((c, i) => (
                                    <div key={i} className="border-b border-border/60 pb-2 text-[12px] last:border-b-0 last:pb-0">
                                        <div className="flex items-center justify-between gap-2">
                                            <span className="font-bold text-foreground">{c.competitorName || '—'}</span>
                                            <span className="font-semibold tabular-nums text-primary">{formatNumber(c.value)}</span>
                                        </div>
                                        <div className="mt-0.5 text-muted-foreground">{[c.principal, c.product].filter(Boolean).join(' · ') || '—'}</div>
                                        <div className="mt-0.5 tabular-nums text-muted-foreground">{formatNumber(c.price)} × {formatNumber(c.volume)} {c.satuan ?? ''}</div>
                                    </div>
                                ))}
                            </div>
                        )}
                    </div>
                </div>
            )}
        </div>
    );
}

// StatusBadge tone per real ItemStatusID (proto's GUIDE_COLOR palette, mapped onto the
// project's 5 semantic tones instead of proto's bespoke per-status hex values).
const STATUS_TONE = {
    1: 'neutral', 2: 'neutral', 3: 'warning', 4: 'warning',
    5: 'success', 6: 'primary', 7: 'danger', 8: 'success',
};

// Raw accent color per status (matches proto's statusAccent() palette) — used for the
// header Status field's colored left border, same visual language proto used.
const STATUS_ACCENT = {
    1: 'var(--muted-foreground)', 2: 'var(--muted-foreground)', 3: 'var(--warning)',
    4: '#f97316', 5: 'var(--success)', 6: 'var(--primary)', 7: 'var(--danger)', 8: '#16a34a',
};

// Header "Status Guide" legend (proto parity) — one row per ItemStatusID.
const STATUS_GUIDE = [
    { id: 1, desc: 'Just created — no progress yet' },
    { id: 2, desc: 'Received / gathered information' },
    { id: 3, desc: 'Sample(s) were sent' },
    { id: 4, desc: 'Under lab testing' },
    { id: 5, desc: 'Approved after testing' },
    { id: 6, desc: 'Already quoted to the customer' },
    { id: 7, desc: 'Failed — explain why (price, quality, payment, …)' },
    { id: 8, desc: 'Commercialized — routine order started' },
];

// Two-letter initials for the card owner/sales avatar.
const getInitials = (name) => (name || '').trim().split(/\s+/).slice(0, 2).map((w) => w[0]?.toUpperCase() || '').join('') || '?';

const EMPTY_FILTERS = {
    id: '', productId: '', dateFrom: '',
    // Multi-select filters — arrays (matched with whereIn on the server).
    companyId: [], divisionId: [], industryId: [], principalId: [], applicationId: [],
    salesId: [], creatorId: [], priorityId: [], statusIds: [],
};

const routeForScope = (scope) => `company-projects.${scope === 'own' ? 'index' : scope}`;

// Proto-style filter pill: a rounded button that opens its option dropdown on click
// (single-select applies + closes; multi-select toggles). Self-manages open + click-outside.
function FilterPill({ label, options, value, values = [], multi = false, onPick }) {
    const [open, setOpen] = useState(false);
    const ref = useRef(null);
    useEffect(() => {
        if (!open) return undefined;
        const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
        document.addEventListener('mousedown', onDoc);
        return () => document.removeEventListener('mousedown', onDoc);
    }, [open]);
    const active = multi ? values.length > 0 : Boolean(value);
    const shown = multi
        ? (values.length ? `${label} (${values.length})` : label)
        : (value ? (options.find((o) => String(o.value) === String(value))?.label ?? label) : label);
    return (
        <div ref={ref} className="relative">
            <button type="button" onClick={() => setOpen((o) => !o)}
                className={`inline-flex items-center gap-1.5 h-8 px-3 border rounded-full text-[0.78rem] font-semibold whitespace-nowrap transition-colors ${active ? 'border-border-soft-strong bg-primary-light text-primary-hover' : 'border-border-strong bg-surface text-foreground hover:border-primary hover:text-primary'}`}>
                <span className="max-w-[160px] truncate">{shown}</span>
                {active && !multi ? (
                    <span role="button" aria-label="Clear" onClick={(e) => { e.stopPropagation(); onPick(''); setOpen(false); }}
                        className="inline-flex items-center justify-center w-4 h-4 rounded-full text-[0.75rem] leading-none [background:color-mix(in_srgb,var(--primary)_18%,transparent)] hover:bg-danger hover:text-white">×</span>
                ) : (
                    <ChevronDown className="size-3 shrink-0" aria-hidden="true" />
                )}
            </button>
            {open && (
                <div className="absolute left-0 z-50 mt-1.5 min-w-[190px] max-h-[260px] overflow-y-auto rounded-xl border border-border bg-surface p-1 shadow-modal">
                    {options.map((o) => {
                        const isSel = multi ? values.includes(o.value) : String(o.value) === String(value);
                        return (
                            <button key={o.value} type="button"
                                onClick={() => { onPick(o.value); if (!multi) setOpen(false); }}
                                className={`flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-xs font-medium transition-colors ${isSel ? 'bg-accent text-primary' : 'text-foreground hover:bg-secondary'}`}>
                                {multi && <span className={`inline-block size-3 shrink-0 rounded border ${isSel ? 'border-primary bg-primary' : 'border-border-strong'}`} />}
                                <span className="truncate">{o.label}</span>
                            </button>
                        );
                    })}
                    {options.length === 0 && <p className="m-0 px-3 py-2 text-xs text-muted-foreground">No options</p>}
                </div>
            )}
        </div>
    );
}

const formatNumber = (n) => (Number.isFinite(Number(n)) ? Number(n).toLocaleString('en-US', { maximumFractionDigits: 2 }) : n);
// Show a number as-is (0 stays 0, matching the detail page) — dash only when truly missing.
const numOrDash = (v) => (v === null || v === undefined || v === '' ? '—' : formatNumber(v));

/**
 * "New Project" — a single link straight to the create form (user decision 2026-08-19).
 * It used to be a split button whose caret opened an origin menu (Blank / From Quotation /
 * LWR / Sample Order / Visit Plan) that preset the initial line status via `?from=`. The
 * origins were dropped from the UI; the server still honours `?from=` (see
 * CompanyProjectController::SOURCE_ITEM_STATUS) but nothing links to it any more.
 */
function NewProjectButton() {
    return (
        <Link
            href={route('company-projects.create')}
            className="inline-flex h-9 items-center justify-center gap-1.5 rounded-lg bg-linear-to-br from-violet-500 to-primary px-4 text-xs font-bold text-white no-underline shadow-sm transition-[filter] hover:brightness-105"
        >
            <Plus className="size-3.5" aria-hidden="true" /> New Project
        </Link>
    );
}

/** Header "Status Guide" — help popover listing every status with its dot + description. */
function StatusGuide() {
    const [open, setOpen] = useState(false);
    return (
        <div className="relative">
            <button type="button" onClick={() => setOpen((v) => !v)} title="Status Guide" aria-label="Status Guide" aria-expanded={open}
                className="grid size-9 place-items-center rounded-full border border-border-strong bg-surface text-muted-foreground transition-colors hover:border-primary hover:text-primary">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" /><line x1="12" y1="17" x2="12.01" y2="17" /></svg>
            </button>
            {open && (
                <>
                    <div className="fixed inset-0 z-40" onClick={() => setOpen(false)} />
                    <div className="absolute right-0 z-50 mt-1.5 w-[340px] rounded-2xl border border-border bg-card p-3.5 shadow-modal">
                        <p className="m-0 mb-2.5 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">Status Guide</p>
                        <ul className="m-0 flex list-none flex-col gap-2 p-0">
                            {STATUS_GUIDE.map((s) => (
                                <li key={s.id} className="flex items-start gap-2.5">
                                    <span className="mt-[5px] size-2 shrink-0 rounded-full" style={{ background: STATUS_ACCENT[s.id] }} />
                                    <span className="min-w-0">
                                        <span className="block text-[12.5px] font-bold text-foreground">{STATUS_NAME[s.id]}</span>
                                        <span className="block text-[11px] leading-snug text-muted-foreground">{s.desc}</span>
                                    </span>
                                </li>
                            ))}
                        </ul>
                    </div>
                </>
            )}
        </div>
    );
}

// Expandable row for the peek VIEW sidebar — HOVER version (user 2026-07-30): hovering the
// row reveals the detail children; a click PINS it open (so it survives mouse-leave and works
// on touch, where hover doesn't exist). Module-scoped so state survives parent re-renders.
// Stacked label / value cell for an expanded detail — reads like the old detail-page
// hover tooltip (muted caption on top, value below). Works in 1- or 2-column grids.
function KV({ label, children }) {
    return (
        <div className="min-w-0 py-0.5">
            <div className="text-[10px] font-bold uppercase tracking-wide text-muted-foreground">{label}</div>
            <div className="min-w-0 wrap-break-word text-[12px] font-medium text-foreground">{(children ?? '') === '' ? '—' : children}</div>
        </div>
    );
}

// ── Peek VIEW mode — clean read-first detail sidebar, a port of the proto board's
// renderDetail (Pages/Proto/Projects/Board.jsx). Card click opens THIS instead of the
// form-style accordions; the Update button (footer or card) switches the drawer to the
// existing editable accordion form. Status/Priority/Sales stay editable here via
// StripControls when the scope can write — the same saves as edit mode.
function PeekDetailView({ card, scope, editData, editLoading, statuses, priorities, salesUsers, stripStatusId, onOpenStatus, onSaved, onClose, onEdit, onHistory }) {
    const lines = card.lines ?? [];
    const comps = card.competitors ?? [];
    const totalValue = lines.reduce((s, l) => s + (Number(l.value) || 0), 0);

    const Fact = ({ label, span2 = false, children }) => (
        <div className={`min-w-0 ${span2 ? 'col-span-2' : ''}`}>
            <dt className="m-0 text-[10px] font-bold uppercase tracking-wide text-muted-foreground">{label}</dt>
            <dd className={`m-0 mt-0.5 text-[13px] font-semibold text-foreground ${span2 ? 'break-words' : 'truncate'}`}>{children || '—'}</dd>
        </div>
    );
    const SectionTitle = ({ children }) => (
        <h3 className="m-0 mb-2.5 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">{children}</h3>
    );
    // Always-visible number cell inside a product/competitor row.
    const Stat = ({ label, accent = false, children }) => (
        <div className="min-w-0">
            <div className="text-[10px] font-bold uppercase tracking-wide text-muted-foreground">{label}</div>
            <div className={`truncate text-[12px] font-bold tabular-nums ${accent ? 'text-primary' : 'text-foreground'}`}>{children}</div>
        </div>
    );

    return (
        <>
            {/* Header */}
            <div className="border-b border-border shrink-0 flex flex-col gap-3 pt-[18px] px-6 pb-4">
                <div className="flex items-start justify-between gap-3">
                    <div className="min-w-0">
                        <p className="m-0 text-[11px] font-bold uppercase tracking-wide text-muted-foreground truncate">{card.company}</p>
                        <h2 id="peekTitle" className="m-0 mt-0.5 text-xl font-bold text-foreground leading-tight break-words">{lines[0]?.product || card.projectTitle || card.company}</h2>
                    </div>
                    <button className="inline-grid w-[30px] h-[30px] shrink-0 place-items-center border-0 rounded-full bg-transparent text-muted-foreground hover:bg-surface-tint hover:text-foreground" type="button" onClick={onClose} aria-label="Close">
                        <X className="size-4" aria-hidden="true" />
                    </button>
                </div>

                {/* Status / Priority / Sales — editable when the scope can write (proto parity) */}
                {editData && editData.canWrite ? (
                    <StripControls scope={scope} header={editData.header} statuses={statuses}
                        priorities={priorities} salesUsers={salesUsers}
                        currentStatusId={stripStatusId} onOpenStatus={onOpenStatus} onSaved={onSaved} />
                ) : (
                    <div className="grid grid-cols-3 gap-3">
                        <FloatingField as="select" label="Status" value={card.derivedStatusId ?? ''} disabled
                            style={{ borderLeftColor: STATUS_ACCENT[card.derivedStatusId], borderLeftWidth: '3px' }}>
                            <option value={card.derivedStatusId ?? ''}>{STATUS_NAME[card.derivedStatusId] ?? '—'}</option>
                        </FloatingField>
                        <FloatingField as="select" label="Priority" value={card.priority?.name ?? ''} disabled
                            style={{ borderLeftColor: card.priority?.bgColor, borderLeftWidth: '3px' }}>
                            <option value={card.priority?.name ?? ''}>{card.priority?.name || '—'}</option>
                        </FloatingField>
                        <FloatingField as="select" label="Sales" value={card.sales ?? ''} disabled>
                            <option value={card.sales ?? ''}>{card.sales || '—'}</option>
                        </FloatingField>
                    </div>
                )}
            </div>

            {/* Body — compact rows; click to expand the details (no hover) */}
            <div className="flex min-h-0 flex-1 flex-col gap-5 overflow-y-auto py-5 px-6">
                {/* Quick facts always visible */}
                <dl className="grid grid-cols-2 gap-x-4 gap-y-3.5 m-0">
                    <Fact label="Application">{card.application}</Fact>
                    <Fact label="Target Date">{card.targetDate?.trim() ? card.targetDate : ''}</Fact>
                    <Fact label="Target Value">{card.targetValue ? formatNumber(card.targetValue) : ''}</Fact>
                    <Fact label="Sales">{card.sales}</Fact>
                </dl>

                {/* Project — title / description / comment always shown (no hover) */}
                <section>
                    <SectionTitle>Project</SectionTitle>
                    <div className="rounded-xl border border-border bg-surface px-3 py-2.5">
                        <dl className="m-0 flex flex-col gap-2">
                            <KV label="Project Title">{card.projectTitle}</KV>
                            <KV label="Description">{card.projectDescription}</KV>
                            <KV label="Comment">{card.comment}</KV>
                        </dl>
                    </div>
                </section>

                {/* Products (Colorindo) — name + meta subline + numbers, all always visible */}
                <section>
                    <SectionTitle>Products (Colorindo)</SectionTitle>
                    <div className="flex flex-col gap-2">
                        {lines.map((l) => (
                            <div key={l.id} className="rounded-xl border border-border bg-surface px-3 py-2.5">
                                <div className="flex items-start justify-between gap-2">
                                    <div className="min-w-0">
                                        <div className="truncate text-[13px] font-bold text-foreground">{l.product || '—'}</div>
                                        <div className="mt-0.5 text-[11px] text-muted-foreground">{[l.principal, l.priceType, l.quantityType, l.satuan].filter(Boolean).join(' · ') || '—'}</div>
                                    </div>
                                    <span className="flex shrink-0 items-center gap-1.5">
                                        <StatusBadge tone={STATUS_TONE[l.itemStatusId] ?? 'neutral'}>{STATUS_NAME[l.itemStatusId]}</StatusBadge>
                                        <button type="button" title="Status history" onClick={() => onHistory(l.id)}
                                            className="grid size-6 shrink-0 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-primary">
                                            <History className="size-3.5" aria-hidden="true" />
                                        </button>
                                    </span>
                                </div>
                                <dl className="m-0 mt-2 grid grid-cols-3 gap-2">
                                    <Stat label="Price">{numOrDash(l.price)}</Stat>
                                    <Stat label="Qty / Yr">{numOrDash(l.volume)}</Stat>
                                    <Stat label="Value" accent>{numOrDash(l.value)}</Stat>
                                </dl>
                                {l.reason && <p className="m-0 mt-2 text-[11px] text-muted-foreground"><span className="font-bold uppercase tracking-wide">Reason:</span> {l.reason}</p>}
                            </div>
                        ))}
                        {lines.length === 0 && (
                            <p className="m-0 rounded-xl border border-dashed border-border/70 bg-muted/20 px-3 py-2.5 text-center text-[12px] text-muted-foreground">No product lines.</p>
                        )}
                        <div className="flex items-center justify-between rounded-xl border border-border bg-surface-tint px-3 py-2.5">
                            <span className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">Total Value / Year</span>
                            <strong className="text-[15px] font-extrabold text-foreground tabular-nums">{formatNumber(totalValue)}</strong>
                        </div>
                    </div>
                </section>

                {/* Competitors — numbers always shown; hover the name for supplier / producer / type / satuan */}
                <section>
                    <SectionTitle>Competitors {comps.length > 0 && <span className="text-muted-foreground/70">({comps.length})</span>}</SectionTitle>
                    {comps.length === 0 ? (
                        <p className="m-0 rounded-xl border border-dashed border-border/70 bg-muted/20 px-3 py-2.5 text-center text-[12px] text-muted-foreground">No competitor data.</p>
                    ) : (
                        <div className="flex flex-col gap-2">
                            {comps.map((c, i) => (
                                <div key={i} className="rounded-xl border border-border bg-surface px-3 py-2.5">
                                    <div className="min-w-0">
                                        <div className="truncate text-[13px] font-bold text-foreground">{c.product || c.competitorName || '—'}</div>
                                        <div className="mt-0.5 text-[11px] text-muted-foreground">{[c.competitorName && `Supplier: ${c.competitorName}`, c.principal, c.priceType, c.quantityType, c.satuan].filter(Boolean).join(' · ') || '—'}</div>
                                    </div>
                                    <dl className="m-0 mt-2 grid grid-cols-3 gap-2">
                                        <Stat label="Price">{numOrDash(c.price)}</Stat>
                                        <Stat label="Qty / Yr">{numOrDash(c.volume)}</Stat>
                                        <Stat label="Value" accent>{numOrDash(c.value)}</Stat>
                                    </dl>
                                </div>
                            ))}
                        </div>
                    )}
                </section>
            </div>

            {/* Footer */}
            <div className="border-t border-border flex gap-2.5 items-center justify-end shrink-0 bg-surface py-4 px-6">
                <button className="inline-flex h-9 items-center justify-center gap-1.5 rounded-lg border border-input bg-card px-4 text-xs font-bold text-foreground transition-colors hover:border-primary hover:text-primary" type="button" onClick={onClose}>Close</button>
                {(editLoading || !editData || editData.canWrite) && (
                    <button className="inline-flex h-9 items-center justify-center gap-1.5 rounded-lg bg-linear-to-br from-violet-500 to-primary px-4 text-xs font-bold text-white shadow-sm transition-[filter] hover:brightness-105 disabled:opacity-60" type="button"
                        disabled={editLoading || !editData} onClick={onEdit}>
                        <SquarePen className="size-3.5" aria-hidden="true" /> Update
                    </button>
                )}
            </div>
        </>
    );
}

// ── Editable drawer sections (own/all/head only — pm/sm/mm keep the read-only branch
// below unchanged) ──────────────────────────────────────────────────────────────────
// Field lists, payload shapes and save handlers mirror Detail.jsx's HeaderCard /
// TargetFieldsForm / ColorindoLineRow / CompetitorLineRow / AddColorindoLine /
// AddCompetitorLine exactly; the only structural difference is every `onSaved()` call
// here runs the caller's `afterSave` (reload board columns + re-fetch editData) instead
// of `router.reload({ only: ['details'] })`.

// Full header-update payload, shared by both header save paths (Project section here and
// the top-strip Priority/Sales in StripControls). The endpoint (UpdateCompanyProjectHeaderRequest)
// wants ProjectTitle/ProjectDescription/ProjectPriority/UserIDSales together; CommentProject and
// CompanyCP are `sometimes|required`, so they're only INCLUDED WHEN NON-EMPTY — sending an empty
// string/0 would trip the `required`/`exists` rules and fail a save that only meant to change
// something else. Each caller supplies its own edited fields + reads the rest, unchanged, from
// the latest `editData.header`, so the two sections stay decoupled yet always post a complete row.
function headerPayload(scope, { ProjectTitle, ProjectDescription, ProjectPriority, UserIDSales, CommentProject, CompanyCP }) {
    const payload = { scope, ProjectTitle, ProjectDescription, ProjectPriority, UserIDSales };
    if (CommentProject) payload.CommentProject = CommentProject;
    if (CompanyCP) payload.CompanyCP = CompanyCP;
    return payload;
}

// FIX 2 + 3: the top "Control Panel Mini" strip, editable when canWrite. Status opens the
// EXISTING drag/status modal (backend needs a remark, so no bare-select write) pre-filled with
// all of the card's Colorindo line ids + the chosen status; the parent's onOpenStatus wires
// that up and submitStatusChange finishes it. Priority + Sales are a small form that posts the
// FULL header payload (its own fields + the rest read unchanged from `header`); on save the
// parent's onSaved (reload columns + refetch editData) also refreshes this strip and the card,
// fixing the stale-chip issue. Same keep-current-if-missing fallback as elsewhere.
function StripControls({ scope, header, statuses, priorities, salesUsers, currentStatusId, onOpenStatus, onSaved }) {
    const { show } = useToast();
    const form = useForm({ ProjectPriority: header.priorityId, UserIDSales: header.salesId });
    const http = useHttp({});

    const save = async () => {
        http.transform(() => headerPayload(scope, {
            ProjectTitle: header.projectTitle, ProjectDescription: header.projectDescription,
            CommentProject: header.comment, CompanyCP: header.companyCpId,
            ProjectPriority: form.data.ProjectPriority, UserIDSales: form.data.UserIDSales,
        }));
        try {
            await http.post(route('company-projects.header.update', header.id));
            show('Project berhasil disimpan', 'success');
            onSaved();
        } catch {
            show('Gagal menyimpan project', 'error');
        }
    };

    return (
        <div className="flex flex-col gap-3">
            <div className="grid grid-cols-3 gap-3">
                {/* Status: a trigger, not a bound write — onChange opens the modal. The current
                    status shows so the field isn't blank; selectable options exclude only the
                    current value (Created included — it's a normal target now). */}
                <FloatingField as="select" label="Status" value={currentStatusId ?? ''}
                    onChange={(e) => onOpenStatus(e.target.value)}
                    style={{ borderLeftColor: STATUS_ACCENT[currentStatusId], borderLeftWidth: '3px' }}>
                    {currentStatusId
                        ? <option value={currentStatusId}>{STATUS_NAME[currentStatusId] ?? '—'}</option>
                        : <option value="">—</option>}
                    {statuses.filter((s) => s.ID !== Number(currentStatusId))
                        .map((s) => <option key={s.ID} value={s.ID}>{s.StatusName}</option>)}
                </FloatingField>
                <FloatingField as="select" label="Priority" value={form.data.ProjectPriority ?? ''}
                    onChange={(e) => form.setData('ProjectPriority', e.target.value)}>
                    {!priorities.some((p) => p.ID === header.priorityId) && header.priorityId
                        ? <option value={header.priorityId}>Priority #{header.priorityId}</option> : null}
                    {priorities.map((p) => <option key={p.ID} value={p.ID}>{p.PriorityName}</option>)}
                </FloatingField>
                <FloatingField as="select" label="Sales" value={form.data.UserIDSales ?? ''}
                    onChange={(e) => form.setData('UserIDSales', e.target.value)}>
                    {!salesUsers.some((u) => u.ID === header.salesId) && header.salesId
                        ? <option value={header.salesId}>User #{header.salesId}</option> : null}
                    {salesUsers.map((u) => <option key={u.ID} value={u.ID}>{u.Nama}</option>)}
                </FloatingField>
            </div>
            {/* Always visible (like the other sections' Save buttons) — the POST goes through a
                separate useHttp, so useForm.isDirty wouldn't reset after a save and would linger. */}
            <button type="button" onClick={save} disabled={http.processing}
                className="inline-flex h-9 items-center justify-center gap-1.5 self-start rounded-lg bg-linear-to-br from-violet-500 to-primary px-4 text-xs font-bold text-white shadow-sm transition-[filter] hover:brightness-105 disabled:opacity-50">
                {http.processing ? <Loader2 className="size-3.5 animate-spin" aria-hidden="true" /> : <SquarePen className="size-3.5" aria-hidden="true" />}
                Save
            </button>
        </div>
    );
}

export default function CompanyProjectBoard({
    scope, canCreate = false, canCreateDocs = {}, filters = {}, filterOptions = {}, statuses = [], priorities = [],
    created, earlyStage, labTesting, approved, quotation, closed,
}) {
    const { show: showToast } = useToast();
    // Picks the self vs on-behalf create route per company (see projectCreateTargets).
    const currentUserId = Number(usePage().props.auth?.user?.ID ?? 0);
    const optionsHttp = useHttp({});
    // Dedicated instance for the status-change write. `updateLineStatus()` (Task 6) returns a
    // plain JSON response, not an Inertia response — router.post() would reject it client-side
    // ("must receive a valid Inertia response") since Inertia's Response.process() checks for
    // the X-Inertia header before ever calling onSuccess. useHttp's XHR client has no such
    // check, so it's the correct transport for this non-navigational, JSON-returning write
    // (per CLAUDE.md: useHttp for non-navigation actions; router/useForm for redirect-backed
    // full-page visits). Backed by an empty {} — .transform() supplies the real payload at
    // submit time (same idiom Create.jsx uses for its own POST).
    const statusHttp = useHttp({});
    const columnData = { created, earlyStage, labTesting, approved, quotation, closed };

    const [createMenuOpen, setCreateMenuOpen] = useState(false);
    const [peekCard, setPeekCard] = useState(null);
    const [peekLayout, setPeekLayout] = useState('side'); // 'side' = card click · 'center' = Update button (popup)
    // Save for the edit popup lives in the modal HEADER, next to the title — the body below is
    // ProjectDetailContent, which hands its saveAll up through registerSaveAll.
    const popupSaveRef = useRef(null);
    const [popupSaving, setPopupSaving] = useState(false);
    const [peekMode, setPeekMode] = useState('view'); // 'view' = clean detail sidebar (card click) · 'edit' = accordion form (Update)
    const [dragCard, setDragCard] = useState(null);
    const [dropTarget, setDropTarget] = useState(null); // { column, card }
    const [filtersOpen, setFiltersOpen] = useState(false);
    const [ongoing, setOngoing] = useState(false); // "Ongoing" preset toggle (in-progress statuses + date min = today)

    // Editable drawer: fetched fresh on every peekCard open (and after every save) —
    // see the file-header comment for why this can't just be an Inertia page prop.
    const [editData, setEditData] = useState(null);
    const [editLoading, setEditLoading] = useState(false);
    const editHttp = useHttp({});

    useEffect(() => {
        if (!peekCard) {
            setEditData(null);
            return;
        }
        let cancelled = false;
        setEditLoading(true);
        editHttp.get(route('company-projects.details.edit-data', { detail: peekCard.detailId, scope }))
            .then((data) => { if (!cancelled) setEditData(data); })
            .catch(() => { if (!cancelled) showToast('Gagal memuat data project', 'error'); })
            .finally(() => { if (!cancelled) setEditLoading(false); });
        return () => { cancelled = true; };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [peekCard?.detailId]);

    // Silent background refresh (no loading flag) after a section save succeeds — keeps
    // the drawer's own fields in sync with what the server just persisted/computed.
    const refreshEditData = () => {
        if (!peekCard) return;
        editHttp.get(route('company-projects.details.edit-data', { detail: peekCard.detailId, scope }))
            .then((data) => setEditData(data))
            .catch(() => {});
    };

    // Full-project data for the edit popup — same payload Detail.jsx (/company-projects/:id)
    // consumes, so the popup renders the exact same ProjectDetailContent for the WHOLE project.
    const [projectData, setProjectData] = useState(null);
    const [projectLoading, setProjectLoading] = useState(false);
    const projectHttp = useHttp({});

    useEffect(() => {
        if (!peekCard || peekMode !== 'edit') {
            setProjectData(null);
            return;
        }
        let cancelled = false;
        setProjectLoading(true);
        projectHttp.get(route('company-projects.project-edit-data', { project: peekCard.projectId, scope }))
            .then((data) => { if (!cancelled) setProjectData(data); })
            .catch(() => { if (!cancelled) showToast('Gagal memuat data project', 'error'); })
            .finally(() => { if (!cancelled) setProjectLoading(false); });
        return () => { cancelled = true; };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [peekCard?.projectId, peekMode]);

    const refreshProjectData = () => {
        if (!peekCard) return;
        projectHttp.get(route('company-projects.project-edit-data', { project: peekCard.projectId, scope }))
            .then((data) => setProjectData(data))
            .catch(() => {});
    };

    // After a save inside the edit popup: reload the board columns (status/priority/title changes
    // move cards between columns) and re-fetch the popup's project data.
    const afterProjectSave = () => {
        router.reload({ only: Object.keys(columnData) });
        refreshProjectData();
    };

    // Every editable section's `onSaved` runs this: reload the board columns (a save can
    // change the card's derived status/priority/title, moving it between columns — same
    // reasoning as submitStatusChange's reload below) and re-fetch this card's edit data.
    const afterSave = () => {
        router.reload({ only: Object.keys(columnData) });
        refreshEditData();
    };

    const [statusForm, setStatusForm] = useState(null); // { lineIds, itemStatusId, itemReasonId, remark }
    const [reasonOptions, setReasonOptions] = useState([]);
    const [busy, setBusy] = useState(false);
    const [historyRows, setHistoryRows] = useState(null); // null = closed, [] = loaded-empty

    // ── Multi-select (checkbox per product-card) → bulk actions ───────────────
    // Keyed by line.id (companyprojectdetailcc id, globally unique). Value keeps the
    // bits the bulk bar needs: product name, company (+id), current status, project.
    const [selected, setSelected] = useState(() => new Map());
    const clearSel = () => setSelected(new Map());
    const toggleSel = (line, card) => setSelected((m) => {
        const n = new Map(m);
        if (n.has(line.id)) n.delete(line.id);
        else n.set(line.id, {
            lineId: line.id, product: line.product, company: card.company,
            companyId: card.companyId ?? null, companySalesId: card.companySalesId ?? 0,
            itemStatusId: line.itemStatusId, projectId: card.projectId,
        });
        return n;
    });
    const selLines = [...selected.values()];
    const selCompanyIds = new Set(selLines.map((s) => s.companyId).filter((v) => v != null));
    const selCompanyNames = new Set(selLines.map((s) => s.company));
    // Create Project needs ONE company (a project = one company). Prefer id; fall back to name.
    const sameCompany = selLines.length > 0 && (selCompanyIds.size <= 1) && (selCompanyNames.size === 1);

    // Bulk Change Status: reuse the drag/status modal (statusForm + submitStatusChange),
    // seeded with EVERY selected line id and a full 8-status picker.
    const openBulkStatus = () => {
        const lines = selLines.map((s) => ({ id: s.lineId, product: s.product, itemStatusId: s.itemStatusId }));
        setDropTarget({ column: { statusIds: ALL_STATUS_IDS }, card: { lines } });
        setStatusForm({ lineIds: lines.map((l) => l.id), itemStatusId: null, itemReasonId: null, remark: '', tanggal: '' });
        setReasonOptions([]);
    };

    // Change Status directly from a single product card — same modal/flow as bulk/drag, seeded
    // with just this one line and a full 8-status picker.
    const openStatusForLine = (line) => {
        setDropTarget({ column: { statusIds: ALL_STATUS_IDS }, card: { lines: [line] } });
        setStatusForm({ lineIds: [line.id], itemStatusId: null, itemReasonId: null, remark: '', tanggal: '' });
        setReasonOptions([]);
    };

    // Create Project from the selection → carry the picked line ids (+company) to Create.
    const createFromSelection = () => {
        if (!sameCompany) return;
        const companyId = selLines[0].companyId;
        router.get(route('company-projects.create'), {
            prefillLines: selLines.map((s) => s.lineId).join(','),
            ...(companyId ? { company: companyId } : {}),
        });
    };

    // "Create <doc> from the checked lines" — legacy listproject.php opens ONE TAB PER
    // COMPANY (user decision 2026-08-05), so this deliberately does not navigate.
    //
    // Browsers only grant one window.open per user gesture unless popups are allowed for
    // the site, so tabs 2..n are commonly blocked. window.open returns null when that
    // happens — say so rather than letting the user believe the other companies had no
    // lines. Client-side toast is correct here: no Inertia visit happens, so there is no
    // server flash channel (.claude/rules/notifications.md).
    const openCreateTargets = (target) => {
        const targets = projectCreateTargets(selLines, target, currentUserId, route);
        setCreateMenuOpen(false);
        if (targets.length === 0) return;

        const blocked = targets.filter((t) => !window.open(t.url, '_blank')).length;
        if (blocked > 0) {
            showToast(
                `${blocked} of ${targets.length} tab(s) were blocked. Allow pop-ups for this site, then try again.`,
                'warning',
            );
        }
    };

    // Free-text filters (Project ID / Product ID) are local until committed (Enter/blur) so
    // every keystroke doesn't fire a round trip; re-sync if the server value changes under us
    // (e.g. after a Reset).
    const [idText, setIdText] = useState(filters.id ?? '');
    const [productText, setProductText] = useState(filters.productId ?? '');
    useEffect(() => setIdText(filters.id ?? ''), [filters.id]);
    useEffect(() => setProductText(filters.productId ?? ''), [filters.productId]);

    const applyFilters = (patch) => {
        router.get(route(routeForScope(scope)), { ...filters, ...patch }, {
            preserveState: true, preserveScroll: true, only: ['filters', ...Object.keys(columnData)],
        });
    };

    const loadMore = (columnId) => {
        const nextCount = (columnData[columnId]?.cards.length ?? 0) + 24;
        router.get(route(routeForScope(scope)), { ...filters, [`${columnId}Count`]: nextCount }, {
            preserveState: true, preserveScroll: true, only: [columnId],
        });
    };

    // Toggle one value in a multi-select (array) filter.
    const toggleMulti = (key, value) => {
        const cur = filters[key] ?? [];
        const has = cur.map(String).includes(String(value));
        applyFilters({ [key]: has ? cur.filter((x) => String(x) !== String(value)) : [...cur, value] });
    };

    const resetFilters = () => { setOngoing(false); applyFilters(EMPTY_FILTERS); };

    const activeFilterCount = Object.keys(EMPTY_FILTERS).filter((key) => {
        const v = filters[key];
        return Array.isArray(v) ? v.length > 0 : Boolean(v);
    }).length;
    // The keys tucked behind "More filters" (Stage/Priority/Principal are surfaced as pills).
    const MORE_KEYS = ['companyId', 'divisionId', 'industryId', 'applicationId', 'salesId', 'creatorId', 'id', 'productId'];
    const moreActiveCount = MORE_KEYS.filter((k) => {
        const v = filters[k];
        return Array.isArray(v) ? v.length > 0 : Boolean(v);
    }).length;

    // Today as YYYY-MM-DD in LOCAL time (en-CA yields ISO date order) for the status-date `min`.
    const todayStr = new Date().toLocaleDateString('en-CA');

    const onDrop = (column, card) => {
        if (!dragCard) return;
        const candidateStatuses = column.statusIds;
        setDropTarget({ column, card });
        setStatusForm({
            lineIds: card.lines.map((l) => l.id), // pre-check every line; user can uncheck in the popover
            itemStatusId: candidateStatuses.length > 1 ? null : candidateStatuses[0], // null => ask which sub-status
            itemReasonId: null,
            remark: '',
            tanggal: '', // UI-only for now (not posted) — see the status-date field in the modal
        });
        setDragCard(null);
    };

    const onPickSubStatus = async (statusId) => {
        setStatusForm((f) => ({ ...f, itemStatusId: statusId, itemReasonId: null }));
        if ([7, 8].includes(statusId)) {
            const reasons = await optionsHttp.get(route('company-projects.options.status-reasons', statusId));
            setReasonOptions(reasons || []);
        } else {
            setReasonOptions([]);
        }
    };

    // FIX 2: current derived status shown in the drawer's top-strip Status select — from
    // editData's live line statuses when loaded (so it reflects a just-saved change), else the
    // board card's derived value. MIN() mirrors the server's column-derivation rule.
    const stripStatusId = editData?.detail?.colorindoLines?.length
        ? Math.min(...editData.detail.colorindoLines.map((l) => Number(l.itemStatusId)))
        : (peekCard?.derivedStatusId ?? '');

    // FIX 2: changing the top-strip Status opens the SAME status-change modal the drag flow
    // uses (setDropTarget + setStatusForm), pre-filled with every Colorindo line id + the chosen
    // status, then finished via submitStatusChange — the backend requires a remark, so this must
    // go through the modal, not a bare select. Reasons are fetched for Failed(7)/Commercialized(8),
    // exactly like onPickSubStatus. Selecting the current status is a no-op.
    const openStatusFromStrip = async (value) => {
        const statusId = Number(value);
        if (!statusId || statusId === Number(stripStatusId)) return;
        const lines = editData?.detail?.colorindoLines?.length
            ? editData.detail.colorindoLines.map((l) => ({ id: l.id, product: l.productName, itemStatusId: l.itemStatusId }))
            : (peekCard?.lines ?? []);
        // A single-status column so the modal skips its sub-status picker; card.lines feeds the
        // multi-line "which line(s)?" checkboxes, which submitStatusChange already handles.
        setDropTarget({ column: { statusIds: [statusId] }, card: { lines } });
        setStatusForm({ lineIds: lines.map((l) => l.id), itemStatusId: statusId, itemReasonId: null, remark: '', tanggal: '' });
        if ([7, 8].includes(statusId)) {
            const reasons = await optionsHttp.get(route('company-projects.options.status-reasons', statusId));
            setReasonOptions(reasons || []);
        } else {
            setReasonOptions([]);
        }
    };

    const submitStatusChange = () => {
        if (!statusForm?.itemStatusId || !statusForm.remark || statusForm.lineIds.length === 0) return;
        if ([7, 8].includes(statusForm.itemStatusId) && !statusForm.itemReasonId) return;
        setBusy(true);
        // .transform() overrides the submitted body regardless of the hook's bound `data`
        // (which stays `{}` the whole time) — the callback's own argument is ignored on
        // purpose so this always sends the LATEST statusForm/scope read at submit time.
        statusHttp.transform(() => ({
            scope, lineIds: statusForm.lineIds, itemStatusId: statusForm.itemStatusId,
            itemReasonId: statusForm.itemReasonId, remark: statusForm.remark,
        }));
        statusHttp.post(route('company-projects.lines.update-status'), {
            onSuccess: () => {
                showToast('Status berhasil diubah', 'success');
                setStatusForm(null);
                setDropTarget(null);
                clearSel(); // a bulk change consumes the selection

                // A plain useHttp POST doesn't refresh Inertia's page props the way a router
                // visit does — a status change can move a card between any two of the 6
                // columns, so reload all of them rather than guessing which two changed.
                router.reload({ only: Object.keys(columnData) });
                // FIX 2: when the status change was launched from the OPEN drawer's top-strip
                // Status select, refresh its edit data too so the strip + line statuses reflect
                // it. No-ops during the drag flow (drawer closed → peekCard null).
                refreshEditData();
            },
            // useHttp splits failures into 3 callbacks (unlike router.post's single onError):
            // onError = 422 validation, onHttpException = other HTTP errors (403/500/…),
            // onNetworkError = offline/connection failure. Cover all three with the same
            // toast so any failure mode surfaces the same as before.
            onError: () => showToast('Please check the form and try again.', 'error'),
            onHttpException: () => showToast('Ada kesalahan, periksa kembali', 'error'),
            onNetworkError: () => showToast('Ada kesalahan, periksa kembali', 'error'),
            onFinish: () => setBusy(false),
        }).catch(() => {}); // submit() rethrows after invoking the callbacks above; already handled.
    };

    const loadHistory = async (lineId) => {
        const rows = await optionsHttp.get(route('company-projects.lines.history', { detailCc: lineId, scope }));
        setHistoryRows(rows || []);
    };

    // Real option lists, sourced from the server (replacing the proto's hardcoded arrays).
    const companyOpts = (filterOptions.companies ?? []).map((c) => ({ value: c.ID, label: c.CompanyName }));
    const divisionOpts = (filterOptions.divisions ?? []).map((d) => ({ value: d.ID, label: d.DivisionName }));
    const industryOpts = (filterOptions.industries ?? []).map((i) => ({ value: i.ID, label: i.IndustryName }));
    const principalOpts = (filterOptions.principals ?? []).map((p) => ({ value: p.ID, label: p.PrincipalName }));
    const applicationOpts = (filterOptions.applications ?? []).map((a) => ({ value: a.ID, label: a.ApplicationName }));
    const salesOpts = (filterOptions.salesUsers ?? []).map((u) => ({ value: u.ID, label: u.Nama }));
    const creatorOpts = (filterOptions.creatorUsers ?? []).map((u) => ({ value: u.ID, label: u.Nama }));
    const priorityOpts = priorities.map((p) => ({ value: p.ID, label: p.PriorityName }));

    return (
        <section className="flex min-w-0 flex-col gap-4">
            <header className="flex items-center justify-between gap-4">
                <div>
                    <p className="m-0 mb-1.5 flex items-center gap-2 text-xs font-semibold text-muted-foreground">
                        <span>Projects</span>
                        <span aria-hidden="true">›</span>
                        <span className="text-foreground">Company Project Board</span>
                    </p>
                    <h1 className="m-0 text-xl font-bold leading-tight text-card-foreground">Company Project Board</h1>
                </div>
                <div className="flex items-center gap-2">
                    <StatusGuide />
                    {canCreate && <NewProjectButton />}
                </div>
            </header>

            {/* Proto-style filter toolbar: quick pills open their dropdown on click; the rest
                sit behind "More filters". Wiring is unchanged — pills call the same server-driven
                applyFilters()/toggleMulti() the old panel used. */}
            <div className="flex flex-wrap items-center gap-2 pb-1">
                <FilterPill label="Stage" multi values={filters.statusIds ?? []}
                    options={statuses.map((s) => ({ value: s.ID, label: s.StatusName }))}
                    onPick={(v) => toggleMulti('statusIds', v)} />
                <FilterPill label="Priority" multi values={filters.priorityId ?? []} options={priorityOpts}
                    onPick={(v) => toggleMulti('priorityId', v)} />
                <FilterPill label="Principal" multi values={filters.principalId ?? []} options={principalOpts}
                    onPick={(v) => toggleMulti('principalId', v)} />

                {/* Ongoing preset — in-progress statuses (Exploring…Quotation) + date min = today */}
                <button type="button"
                    onClick={() => {
                        const on = !ongoing;
                        setOngoing(on);
                        applyFilters({ statusIds: on ? ONGOING_STATUS_IDS : [] });
                    }}
                    className={`inline-flex items-center gap-1.5 h-8 px-3 border rounded-full text-[0.78rem] font-semibold whitespace-nowrap transition-colors ${ongoing ? 'border-border-soft-strong bg-primary-light text-primary-hover' : 'border-border-strong bg-surface text-foreground hover:border-primary hover:text-primary'}`}>
                    <span className={`w-1.5 h-1.5 rounded-full shrink-0 ${ongoing ? 'bg-primary' : 'bg-muted-foreground'}`} />
                    Ongoing
                </button>

                {/* Target-date "From" filter — its min flips to today while Ongoing is on */}
                <label className={`inline-flex items-center gap-1.5 h-8 px-3 border rounded-full bg-surface text-[0.78rem] font-semibold transition-colors focus-within:border-primary ${filters.dateFrom ? 'border-border-soft-strong bg-primary-light text-primary-hover' : 'border-border-strong text-foreground'}`}>
                    <span className="text-muted-foreground">From</span>
                    <input type="date" value={filters.dateFrom ?? ''} min={ongoing ? todayStr : undefined}
                        onChange={(e) => applyFilters({ dateFrom: e.target.value })}
                        className="min-w-[112px] border-none bg-transparent p-0 text-[0.78rem] text-foreground outline-none" />
                    {filters.dateFrom && (
                        <span role="button" aria-label="Clear date" onClick={() => applyFilters({ dateFrom: '' })}
                            className="inline-flex items-center justify-center w-4 h-4 rounded-full text-[0.75rem] leading-none cursor-pointer [background:color-mix(in_srgb,var(--primary)_18%,transparent)] hover:bg-danger hover:text-white">×</span>
                    )}
                </label>

                {/* More filters — the remaining fields in a dropdown panel */}
                <div className="relative">
                    <button type="button" onClick={() => setFiltersOpen((v) => !v)}
                        className={`inline-flex items-center gap-1.5 h-8 px-3 border rounded-full text-[0.78rem] font-semibold whitespace-nowrap transition-colors ${moreActiveCount > 0 ? 'border-border-soft-strong bg-primary-light text-primary-hover' : 'border-border-strong bg-surface text-foreground hover:border-primary hover:text-primary'}`}>
                        <SlidersHorizontal className="size-3.5" aria-hidden="true" />
                        More filters
                        {moreActiveCount > 0 && <span className="inline-flex min-w-[18px] items-center justify-center rounded-full bg-primary px-1 text-[0.62rem] font-extrabold text-primary-foreground">{moreActiveCount}</span>}
                    </button>
                    {filtersOpen && (
                        <>
                            <div className="fixed inset-0 z-40" onClick={() => setFiltersOpen(false)} />
                            <div className="absolute left-0 z-50 mt-1.5 w-[440px] max-w-[92vw] rounded-2xl border border-border bg-surface p-4 shadow-modal">
                                <div className="grid grid-cols-2 gap-3.5">
                                    <FloatingField as="input" label="Project ID" value={idText}
                                        onChange={(e) => setIdText(e.target.value)}
                                        onBlur={() => applyFilters({ id: idText })}
                                        onKeyDown={(e) => e.key === 'Enter' && applyFilters({ id: idText })} />
                                    <FloatingField as="input" label="Product ID" value={productText}
                                        onChange={(e) => setProductText(e.target.value)}
                                        onBlur={() => applyFilters({ productId: productText })}
                                        onKeyDown={(e) => e.key === 'Enter' && applyFilters({ productId: productText })} />
                                </div>
                                {/* All multi-select — pick several per filter */}
                                <div className="mt-3 flex flex-wrap gap-2">
                                    <FilterPill label="Company" multi values={filters.companyId ?? []} options={companyOpts} onPick={(v) => toggleMulti('companyId', v)} />
                                    <FilterPill label="Division" multi values={filters.divisionId ?? []} options={divisionOpts} onPick={(v) => toggleMulti('divisionId', v)} />
                                    <FilterPill label="Industry" multi values={filters.industryId ?? []} options={industryOpts} onPick={(v) => toggleMulti('industryId', v)} />
                                    <FilterPill label="Application" multi values={filters.applicationId ?? []} options={applicationOpts} onPick={(v) => toggleMulti('applicationId', v)} />
                                    <FilterPill label="Sales" multi values={filters.salesId ?? []} options={salesOpts} onPick={(v) => toggleMulti('salesId', v)} />
                                    <FilterPill label="Creator" multi values={filters.creatorId ?? []} options={creatorOpts} onPick={(v) => toggleMulti('creatorId', v)} />
                                </div>
                            </div>
                        </>
                    )}
                </div>

                {activeFilterCount > 0 && (
                    <button type="button" onClick={resetFilters}
                        className="inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-full px-2.5 text-[12.5px] font-semibold text-muted-foreground transition-colors hover:bg-danger/10 hover:text-danger-text">
                        Reset all ({activeFilterCount})
                    </button>
                )}
            </div>

            <div className="flex gap-5 overflow-x-auto overflow-y-hidden items-start pt-2.5 px-0 pb-6 h-[calc(100vh-80px)]">
                {COLUMNS.map((col) => {
                    const data = columnData[col.id] ?? { cards: [], hasMore: false };
                    return (
                        <div key={col.id}
                            className="bg-surface-soft rounded-2xl py-4 px-3 min-w-[320px] max-w-[320px] flex flex-col shrink-0 max-h-full"
                            onDragOver={(e) => e.preventDefault()}
                            onDrop={() => onDrop(col, dragCard)}
                        >
                            <div className="flex justify-between items-center px-1 pb-3">
                                <span className="font-bold text-[0.8rem] uppercase text-foreground tracking-[0.05em]">{col.label}</span>
                                <span className="inline-grid place-items-center min-w-[22px] h-[22px] rounded-full bg-surface border border-border-strong text-[0.75rem] font-bold">{data.cards.reduce((n, c) => n + c.lines.length, 0)}</span>
                            </div>
                            {/* Thin, token-coloured scrollbar. The browser default is a ~15px grey
                                gutter — inside a 320px column that is a wide slab of chrome next to
                                every card. Written as arbitrary variants, not bespoke CSS in
                                app.css (design-system rule: app.css carries no bespoke rules). */}
                            <div className="flex flex-col gap-4 overflow-y-auto flex-1 pt-1 pr-1 pb-4 pl-0
                                [scrollbar-color:var(--color-border-strong)_transparent] [scrollbar-width:thin]
                                [&::-webkit-scrollbar]:w-1.5
                                [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border-strong
                                [&::-webkit-scrollbar-thumb:hover]:bg-muted-foreground
                                [&::-webkit-scrollbar-track]:bg-transparent">
                                {/* One card per PRODUCT (companyprojectdetailcc line). `productCard` carries
                                    just this line so a drag moves only this product's status. Competitors moved
                                    off the card face into the hover button. Placement is still per-detail (server
                                    buckets a whole detail by its lowest line status), so a product's badge can
                                    differ from its column — fixing that needs a server change. */}
                                {data.cards.flatMap((card) => card.lines.map((line) => {
                                    const productCard = { ...card, lines: [line] };
                                    const isSel = selected.has(line.id);
                                    return (
                                        <div key={`${card.detailId}-${line.id}`}
                                            className={`group/card relative flex cursor-grab flex-col gap-2 rounded-xl border p-3.5 transition-colors ${isSel ? 'border-primary bg-accent/40 shadow-sm' : 'border-border/50 bg-surface shadow-sm hover:border-border-strong'}`}
                                            draggable
                                            onDragStart={() => setDragCard(productCard)}
                                            onDragEnd={() => setDragCard(null)}
                                            onClick={() => { if (!dragCard) { setPeekLayout('side'); setPeekMode('view'); setPeekCard(card); } }}
                                        >
                                            <div className="flex items-center gap-2">
                                                {/* Select checkbox — design-system CheckBox (same as Approval PM).
                                                    Its wrapper stops click-propagation; the span blocks drag start. */}
                                                <span className="shrink-0" draggable={false} onDragStart={(e) => { e.preventDefault(); e.stopPropagation(); }}>
                                                    <CheckBox checked={isSel} onChange={() => toggleSel(line, card)} ariaLabel={`Select ${line.product || 'product'}`} />
                                                </span>
                                                <h3 className="m-0 min-w-0 flex-1 truncate text-sm font-semibold leading-tight text-foreground" title={card.company}>{card.company}</h3>
                                                {card.sales && (
                                                    <span title={card.sales} className="inline-flex size-[22px] shrink-0 items-center justify-center rounded-full bg-primary/10 text-[0.58rem] font-extrabold text-primary">{getInitials(card.sales)}</span>
                                                )}
                                                {card.priority?.name && (
                                                    <span className="inline-flex shrink-0 items-center gap-[5px] whitespace-nowrap rounded-full border border-border bg-transparent px-2 py-0.5 text-[0.68rem] font-semibold text-foreground">
                                                        <span className="size-1.5 shrink-0 rounded-full" style={{ background: card.priority.bgColor }} />
                                                        {card.priority.name}
                                                    </span>
                                                )}
                                            </div>

                                            {/* Product is the card's subject now — name + its own status */}
                                            <div className="flex items-center gap-2 pl-6">
                                                <span className="size-2 shrink-0 rounded-full bg-primary" />
                                                <span className="min-w-0 flex-1 truncate text-[0.82rem] font-semibold text-foreground">{line.product || '—'}</span>
                                                <StatusBadge tone={STATUS_TONE[line.itemStatusId] ?? 'neutral'}>
                                                    {[7, 8].includes(line.itemStatusId) && (line.itemStatusId === 8 ? '✓ ' : '✕ ')}
                                                    {STATUS_NAME[line.itemStatusId]}
                                                </StatusBadge>
                                            </div>

                                            <p className="m-0 pl-6 text-[0.72rem] text-muted-foreground">
                                                <Link
                                                    href={route(scope === 'own' ? 'company-projects.show' : `company-projects.show-${scope}`, card.projectId)}
                                                    onClick={(e) => e.stopPropagation()}
                                                    draggable={false}
                                                    className="font-semibold text-primary hover:underline"
                                                >
                                                    #{card.projectId}
                                                </Link>
                                                {card.targetDate?.trim() ? ` · ${card.targetDate}` : ''}
                                            </p>

                                            {/* Actions — compact icon buttons (label via tooltip); stopPropagation
                                                so a button never also opens the peek drawer. */}
                                            <div className="mt-0.5 flex items-center gap-1 pl-6" onClick={(e) => e.stopPropagation()}>
                                                <button type="button" onClick={() => openStatusForLine({ id: line.id, product: line.product, itemStatusId: line.itemStatusId })} title="Change Status" aria-label="Change Status" className={CARD_ICON_BTN}>
                                                    <RefreshCw className="size-3.5" aria-hidden="true" />
                                                </button>
                                                <button type="button" onClick={() => loadHistory(line.id)} title="History" aria-label="History" className={CARD_ICON_BTN}>
                                                    <History className="size-3.5" aria-hidden="true" />
                                                </button>
                                                <button type="button" onClick={() => { setPeekLayout('center'); setPeekMode('edit'); setPeekCard(card); }} title="Update" aria-label="Update" className={CARD_ICON_BTN}>
                                                    <SquarePen className="size-3.5" aria-hidden="true" />
                                                </button>
                                                <CompetitorHover competitors={card.competitors} />
                                            </div>
                                        </div>
                                    );
                                }))}
                                {data.cards.length === 0 && (
                                    <p className="m-0 px-1 text-[0.75rem] text-muted-foreground">No projects.</p>
                                )}
                                {data.hasMore && (
                                    <button type="button" className="mt-1 w-full py-2.5 text-center text-[0.8rem] font-semibold text-primary transition-opacity hover:opacity-70" onClick={() => loadMore(col.id)}>
                                        Load More
                                    </button>
                                )}
                            </div>
                        </div>
                    );
                })}
            </div>

            {/* Bulk action bar — floating pill, shown once ≥1 product-card is checked. */}
            {selLines.length > 0 && (
                <div className="pointer-events-none fixed inset-x-0 bottom-6 z-50 flex justify-center px-4">
                    <div className="pointer-events-auto flex flex-wrap items-center gap-2 rounded-full border border-border bg-card px-3 py-2 shadow-modal">
                        <span className="pl-2 pr-1 text-[12.5px] font-semibold text-muted-foreground">
                            <span className="font-extrabold text-foreground">{selLines.length}</span> selected
                        </span>
                        <button type="button" onClick={openBulkStatus}
                            className="inline-flex h-9 items-center gap-1.5 rounded-lg bg-linear-to-br from-violet-500 to-primary px-4 text-xs font-bold text-white shadow-sm transition-[filter] hover:brightness-105">
                            <RefreshCw className="size-3.5" /> Change Status
                        </button>
                        <button type="button" onClick={createFromSelection} disabled={!sameCompany}
                            title={sameCompany ? 'Create a project from the selected products' : 'Select products from ONE company to create a project'}
                            className="inline-flex h-9 items-center gap-1.5 rounded-lg border border-input bg-card px-4 text-xs font-bold text-foreground transition-colors hover:border-primary hover:text-primary disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input disabled:hover:text-foreground">
                            <Plus className="size-3.5" /> Create Project
                        </button>
                        {CREATE_TARGETS.some((t) => canCreateDocs[t.key]) && (
                            <div className="relative">
                                <button type="button" onClick={() => setCreateMenuOpen((v) => !v)}
                                    aria-haspopup="menu" aria-expanded={createMenuOpen}
                                    title="Create a document from the selected products"
                                    className="inline-flex h-9 items-center gap-1.5 rounded-lg border border-input bg-card px-4 text-xs font-bold text-foreground transition-colors hover:border-primary hover:text-primary">
                                    <Plus className="size-3.5" /> Create
                                    <ChevronDown className={`size-3.5 transition-transform ${createMenuOpen ? 'rotate-180' : ''}`} aria-hidden="true" />
                                </button>
                                {createMenuOpen && (
                                    <>
                                        <button type="button" tabIndex={-1} aria-hidden="true" className="fixed inset-0 z-40 cursor-default" onClick={() => setCreateMenuOpen(false)} />
                                        {/* Opens UPWARD (bottom-full/mb): the bulk bar is pinned to the
                                            bottom of the viewport, unlike NewProjectMenu in the header. */}
                                        <div role="menu" className="absolute bottom-full right-0 z-50 mb-1.5 w-48 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-modal">
                                            {CREATE_TARGETS.filter((t) => canCreateDocs[t.key]).map((t) => (
                                                <button key={t.key} type="button" role="menuitem"
                                                    onClick={() => openCreateTargets(t)}
                                                    className="block w-full rounded-lg px-3 py-2 text-left text-xs font-semibold text-foreground transition-colors hover:bg-secondary">
                                                    {t.label}
                                                </button>
                                            ))}
                                        </div>
                                    </>
                                )}
                            </div>
                        )}
                        <button type="button" onClick={clearSel}
                            className="inline-flex h-9 items-center gap-1.5 rounded-lg px-3 text-xs font-bold text-muted-foreground transition-colors hover:text-danger-text">
                            <X className="size-3.5" /> Clear
                        </button>
                    </div>
                </div>
            )}

            {/* Drag/status-change popover */}
            <Modal open={!!statusForm} onClose={() => setStatusForm(null)} size="max-w-md" labelledBy="statusPopoverTitle">
                <div className="flex flex-col gap-4 p-5">
                    <div className="flex items-center gap-2.5">
                        <span className="inline-grid size-7 place-items-center rounded-lg bg-accent text-primary" aria-hidden="true">
                            <RefreshCw className="size-4" />
                        </span>
                        <div>
                            <h2 id="statusPopoverTitle" className="m-0 text-sm font-extrabold leading-[1.2] text-card-foreground">Update Status</h2>
                            <small className="block text-[11px] font-medium text-muted-foreground">
                                {statusForm?.itemStatusId
                                    ? <>Moving <span className="font-bold text-foreground">{statusForm.lineIds.length}</span> line(s) → <StatusBadge tone={STATUS_TONE[statusForm.itemStatusId] ?? 'neutral'}>{STATUS_NAME[statusForm.itemStatusId]}</StatusBadge></>
                                    : 'Pick the target status'}
                            </small>
                        </div>
                    </div>

                    {dropTarget?.column.statusIds.length > 1 && !statusForm?.itemStatusId && (
                        <div className="flex flex-col gap-1.5">
                            <span className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">Move to status</span>
                            <div className="flex flex-wrap gap-2">
                                {dropTarget.column.statusIds.map((sid) => (
                                    <button key={sid} type="button" className="inline-flex items-center gap-1.5 rounded-full border border-input px-3 py-1.5 text-xs font-bold text-foreground transition-colors hover:border-primary hover:bg-accent hover:text-primary" onClick={() => onPickSubStatus(sid)}>
                                        <span className="size-2 shrink-0 rounded-full" style={{ background: STATUS_ACCENT[sid] || 'var(--color-muted-foreground)' }} />
                                        {STATUS_NAME[sid]}
                                    </button>
                                ))}
                            </div>
                        </div>
                    )}
                    {statusForm?.itemStatusId && dropTarget?.card.lines.length > 1 && (
                        <div className="flex flex-col gap-1.5">
                            <span className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">Which line(s) are moving?</span>
                            <div className="flex flex-col gap-0.5 rounded-lg border border-border p-1.5">
                                {dropTarget.card.lines.map((l) => (
                                    <label key={l.id} className="flex cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 transition-colors hover:bg-secondary/50">
                                        <CheckBox size="sm" checked={statusForm.lineIds.includes(l.id)}
                                            onChange={(e) => setStatusForm((f) => ({ ...f, lineIds: e.target.checked ? [...f.lineIds, l.id] : f.lineIds.filter((id) => id !== l.id) }))} />
                                        <span className="min-w-0 flex-1 truncate text-[13px] font-medium text-foreground">{l.product}</span>
                                        <StatusBadge tone={STATUS_TONE[l.itemStatusId] ?? 'neutral'}>{STATUS_NAME[l.itemStatusId]}</StatusBadge>
                                    </label>
                                ))}
                            </div>
                        </div>
                    )}
                    {statusForm?.itemStatusId && [7, 8].includes(statusForm.itemStatusId) && (
                        <FloatingField as="select" label="Reason" value={statusForm.itemReasonId ?? ''} onChange={(e) => setStatusForm((f) => ({ ...f, itemReasonId: Number(e.target.value) || null }))}>
                            <option value="">Select Reason</option>
                            {reasonOptions.map((r) => <option key={r.ID} value={r.ID}>{r.ReasonName}</option>)}
                        </FloatingField>
                    )}
                    {statusForm?.itemStatusId && (() => {
                        // Ongoing status → date can't be earlier than today; terminal/reset status → any date.
                        const ongoing = ONGOING_STATUS_IDS.includes(Number(statusForm.itemStatusId));
                        return (
                            <div>
                                <FloatingField
                                    type="date"
                                    label="Tanggal"
                                    value={statusForm.tanggal}
                                    min={ongoing ? todayStr : undefined}
                                    onChange={(e) => setStatusForm((f) => ({ ...f, tanggal: e.target.value }))}
                                />
                                {ongoing && (
                                    <p className="mt-1 text-[11px] font-medium text-muted-foreground">
                                        Status ongoing — tanggal minimal hari ini ({todayStr}).
                                    </p>
                                )}
                            </div>
                        );
                    })()}
                    {statusForm?.itemStatusId && (
                        <FloatingField as="textarea" label="Remark *" rows={2} value={statusForm.remark} onChange={(e) => setStatusForm((f) => ({ ...f, remark: e.target.value }))} />
                    )}
                    <div className="flex justify-end gap-2 pt-2">
                        <button type="button" className="rounded-lg border border-input px-3.5 py-1.5 text-xs font-bold" onClick={() => setStatusForm(null)}>Cancel</button>
                        <button type="button" disabled={busy || !statusForm?.itemStatusId || !statusForm?.remark || statusForm?.lineIds.length === 0 || ([7, 8].includes(statusForm?.itemStatusId) && !statusForm?.itemReasonId)}
                            className="rounded-lg bg-linear-to-br from-violet-500 to-primary text-white px-3.5 py-1.5 text-xs font-bold shadow-sm transition-[filter] hover:brightness-105 disabled:opacity-50" onClick={submitStatusChange}>
                            {busy ? <Loader2 className="size-3.5 animate-spin" /> : 'Confirm'}
                        </button>
                    </div>
                </div>
            </Modal>

            {/* Peek/edit drawer — right-side panel, a literal port of proto's drawer chrome
                (Pages/Proto/Projects/Board.jsx): same "Control Panel Mini" header with
                Status/Priority/Sales fields + Target strip, same 3 accordion sections. When
                canWrite (own/all/head) the strip becomes EDITABLE (StripControls, FIX 2+3):
                Status opens the status-change modal, Priority/Sales save the header. When
                loading or read-only (pm/sm/mm) the strip stays the pre-existing disabled selects
                from `peekCard`. The accordion BODY below branches on `editData.canWrite` (fetched
                on open, see the effect above): pm/sm/mm render read-only fields from `peekCard`;
                own/all/head render the editable sections (EditHeaderForm etc.) from `editData`. */}
            <Drawer open={!!peekCard} onClose={() => setPeekCard(null)} labelledBy="peekTitle" variant={peekLayout === 'center' ? 'center' : 'side'}>
                {peekCard && peekMode === 'view' && (
                    <PeekDetailView
                        card={peekCard}
                        scope={scope}
                        editData={editData}
                        editLoading={editLoading}
                        statuses={statuses}
                        priorities={priorities}
                        salesUsers={filterOptions.salesUsers ?? []}
                        stripStatusId={stripStatusId}
                        onOpenStatus={openStatusFromStrip}
                        onSaved={afterSave}
                        onClose={() => setPeekCard(null)}
                        onEdit={() => { setPeekLayout('center'); setPeekMode('edit'); }}
                        onHistory={loadHistory}
                    />
                )}
                {peekCard && peekMode === 'edit' && (
                    (projectLoading || !projectData) ? (
                        <div className="flex flex-1 items-center justify-center gap-2 py-16 text-sm text-muted-foreground">
                            <Loader2 className="size-4 animate-spin" aria-hidden="true" /> Loading…
                        </div>
                    ) : (
                        <>
                            {/* Header — the popup chrome; the body below is the exact /company-projects/:id
                                content (ProjectDetailContent), embedded (no breadcrumb/tabs/activity). */}
                            <div className="flex shrink-0 items-center justify-between gap-3 border-b border-border px-6 py-4">
                                <div className="min-w-0">
                                    <p className="m-0 truncate text-[11px] font-bold uppercase tracking-wide text-muted-foreground">Edit Project</p>
                                    <h2 id="peekTitle" className="m-0 truncate text-lg font-bold leading-tight text-foreground" title={projectData.project.company}>{projectData.project.company} · #{projectData.project.id}</h2>
                                </div>
                                <div className="flex shrink-0 items-center gap-2">
                                    {projectData.canWrite && (
                                        <button
                                            type="button"
                                            disabled={popupSaving}
                                            onClick={async () => {
                                                setPopupSaving(true);
                                                try { await popupSaveRef.current?.(); } finally { setPopupSaving(false); }
                                            }}
                                            className="inline-flex h-8 items-center justify-center gap-1.5 rounded-lg bg-linear-to-br from-violet-500 to-primary px-3.5 text-xs font-bold text-white shadow-sm transition-[filter] hover:brightness-105 disabled:opacity-60"
                                        >
                                            <Save className="size-3.5" /> {popupSaving ? 'Saving All...' : 'Save All Changes'}
                                        </button>
                                    )}
                                    <button className="inline-grid size-[30px] shrink-0 place-items-center rounded-full border-0 bg-transparent text-muted-foreground hover:bg-surface-tint hover:text-foreground" type="button" onClick={() => setPeekCard(null)} aria-label="Close">
                                        <X className="size-4" aria-hidden="true" />
                                    </button>
                                </div>
                            </div>
                            <div className="min-h-0 flex-1 overflow-y-auto px-6 py-5">
                                <ProjectDetailContent
                                    scope={projectData.scope ?? scope}
                                    canWrite={projectData.canWrite}
                                    project={projectData.project}
                                    details={projectData.details}
                                    opportunityGroups={projectData.opportunityGroups}
                                    principals={projectData.principals}
                                    priceTypes={projectData.priceTypes}
                                    quantityTypes={projectData.quantityTypes}
                                    satuans={projectData.satuans}
                                    statuses={projectData.statuses}
                                    applications={projectData.applications}
                                    onRefresh={afterProjectSave}
                                    showBreadcrumb={false}
                                    showTabs={false}
                                    showActivity={false}
                                    showTopActions={false}
                                    registerSaveAll={(fn) => { popupSaveRef.current = fn; }}
                                />
                            </div>
                        </>
                    )
                )}
            </Drawer>

            {/* Per-line status history */}
            <Modal open={historyRows !== null} onClose={() => setHistoryRows(null)} size="max-w-md" labelledBy="historyTitle">
                <div className="flex flex-col gap-3 p-5">
                    <div className="flex items-center gap-2.5">
                        <span className="inline-grid size-7 place-items-center rounded-lg bg-accent text-primary" aria-hidden="true">
                            <History className="size-4" />
                        </span>
                        <div>
                            <h2 id="historyTitle" className="m-0 text-sm font-extrabold leading-[1.2] text-card-foreground">Status History</h2>
                            <small className="block text-[11px] font-medium text-muted-foreground">Status changes on this product</small>
                        </div>
                    </div>

                    {(historyRows ?? []).length === 0 ? (
                        <p className="m-0 py-6 text-center text-[13px] italic text-muted-foreground">No history yet.</p>
                    ) : (
                        <ol className="m-0 flex max-h-[60vh] list-none flex-col overflow-y-auto p-0">
                            {(historyRows ?? []).map((row) => {
                                const sid = STATUS_ID_BY_NAME[row.StatusName];
                                return (
                                    <li key={row.ID} className="border-b border-border/60 py-2.5 first:pt-0 last:border-b-0">
                                        <div className="flex items-center justify-between gap-3">
                                            <span className="flex min-w-0 items-center gap-2">
                                                <span className="size-2 shrink-0 rounded-full" style={{ background: STATUS_ACCENT[sid] || 'var(--color-muted-foreground)' }} />
                                                <span className="truncate text-[13px] font-bold text-foreground">{row.StatusName || '—'}</span>
                                                {row.ReasonName && (
                                                    <span className="shrink-0 rounded-full bg-secondary px-2 py-0.5 text-[10px] font-semibold text-muted-foreground">{row.ReasonName}</span>
                                                )}
                                            </span>
                                            <span className="shrink-0 text-[11px] font-medium tabular-nums text-muted-foreground">{row.Tanggal}</span>
                                        </div>
                                        {row.Remark && row.Remark !== '' && (
                                            <p className="m-0 mt-1 pl-4 text-[11.5px] leading-relaxed text-muted-foreground">{row.Remark}</p>
                                        )}
                                    </li>
                                );
                            })}
                        </ol>
                    )}
                </div>
            </Modal>
        </section>
    );
}

CompanyProjectBoard.layout = [AppLayout];
