OverviewFilesCommitsIssuesDocsReleasesPeers

electrical-review / design / tabs.jsx

316 lines · jsx ref:
/* ============================================================================
   PRSM SOLAR — TAB VIEWS
   ========================================================================== */
const { useState: useStateT } = React;

const FLAG_LABEL = {
  pass: "Verified",
  price_corrected: "Price corrected",
  unverifiable: "Unverifiable",
  out_of_stock: "Out of stock",
  open_quote: "Open quote",
  owned: "Owned ($0)",
  estimate: "Estimate only",
};

function SectionHead({ n, kicker, title, sub }) {
  return (
    <div className="sec-head">
      <div className="sec-num mono">{n}</div>
      <div>
        {kicker && <div className="eyebrow" style={{ marginBottom: 4 }}>{kicker}</div>}
        <h2 className="sec-title">{title}</h2>
        {sub && <p className="sec-sub">{sub}</p>}
      </div>
    </div>
  );
}

const Flag = ({ s }) => (
  <span className={"flag " + s} title={FLAG_LABEL[s]}><i />{FLAG_LABEL[s]}</span>
);

/* ----------------------------- OVERVIEW ------------------------------- */
function PhaseCard({ d, tone }) {
  const stats = [
    ["PV active", d.pvActive, d.pvActiveNote],
    ["Battery", d.battery, d.batteryNote],
    ["Inverter", d.inverter, d.inverterNote],
    ["Production", d.production, d.productionNote],
  ];
  return (
    <div className={"phase-card " + tone}>
      <div className="phase-card-top">
        <span className={"stamp " + (tone === "p1" ? "estimate" : "computed")}>{d.tag}</span>
        <span className="phase-landed mono">{d.landed}<em>{d.landedNote}</em></span>
      </div>
      <h3 className="phase-name">{d.name}</h3>
      <p className="phase-blurb">{d.blurb}</p>
      <div className="stat-grid">
        {stats.map(([k, v, n]) => (
          <div className="stat" key={k}>
            <div className="stat-k tag">{k}</div>
            <div className="stat-v">{v}</div>
            <div className="stat-n mono">{n}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

function Overview() {
  const P = window.PRSM;
  return (
    <div className="tab-wrap">
      <div className="ov-hero">
        <div>
          <div className="eyebrow">Build concept</div>
          <h2 className="ov-title">Two phases, one island.<br />Shore-assisted today, fully off-grid tomorrow.</h2>
          <p className="ov-lede">
            Phase 1 stands up an 8 kW array, a 6 kW inverter and a 16 kWh pack — with shore power topping the
            battery through a UL-listed DC charger during the build-out. Phase 2 drops the grid: the array doubles,
            a second inverter parallels in (N+1), and a second pack lands on a ≥350 A DC bank.
          </p>
        </div>
        <div className="ov-combined sheet">
          <div className="eyebrow">Both phases · landed</div>
          <div className="ov-big mono">{P.summary.combined.landed}</div>
          <div className="ov-combined-rows">
            <div><span className="tag">Goods</span><b className="mono">{P.summary.combined.goods}</b></div>
            <div><span className="tag">Shipping</span><b className="mono">{P.summary.combined.shipping}</b></div>
          </div>
          <p className="ov-combined-foot mono">incl. ~6% VT use tax unless S-3E filed</p>
        </div>
      </div>

      <div className="phase-flow">
        <PhaseCard d={P.summary.phase1} tone="p1" />
        <div className="flow-arrow"><span className="mono">drop&nbsp;grid</span><div className="arrow-line" /><span className="arrow-head">▶</span></div>
        <PhaseCard d={P.summary.phase2} tone="p2" />
      </div>

      <div className="ov-cols">
        <div className="sheet pad">
          <SectionHead n="01" title="System specs" sub="Script-validated electrical quantities (NREL-grade Python)." />
          <div className="spec-grid">
            {P.specs.map((s) => (
              <div className="spec" key={s.label}>
                <div className="spec-top"><span className="spec-label">{s.label}</span><span className="spec-code mono">{s.code}</span></div>
                <div className="spec-val mono">{s.value}</div>
                <div className="spec-note">{s.note}</div>
              </div>
            ))}
          </div>
          <div className="legend"><span className="stamp computed">Computed</span><span className="legend-txt">specs are script-traced; dollar figures elsewhere are dated estimates.</span></div>
        </div>

        <div className="ov-side">
          <div className="sheet pad">
            <SectionHead n="02" title="Locked decisions" />
            <ul className="dec-list">
              {P.decisions.map((d) => (
                <li key={d.k}><span className="dec-k mono">{d.k}</span><span className="dec-v">{d.v}</span></li>
              ))}
            </ul>
          </div>
          <div className="sheet pad oos">
            <SectionHead n="03" title="Out of scope" sub="Separate budgets — not in the BOM totals." />
            <ul className="oos-list">
              {P.outOfScope.map((o, i) => <li key={i}><span className="mono oos-x">×</span>{o}</li>)}
            </ul>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ------------------------------- BOM ---------------------------------- */
function BomTable({ phase }) {
  const data = window.PRSM.bom[phase];
  return (
    <div>
      <p className="basis-note">{data.basis}</p>
      {data.groups.map((g) => (
        <div className="bom-group" key={g.name}>
          <div className="bom-group-head"><span className="mono">{g.name}</span><span className="hairline-grow" /></div>
          <div className="bom-rows">
            <div className="bom-row bom-head">
              <div>Item</div><div>Spec</div><div>Qty</div><div>Vendor · SKU</div><div className="r">Goods</div><div className="r">Ship</div><div>Status</div>
            </div>
            {g.items.map((it, i) => (
              <div className="bom-row" key={i}>
                <div className="bom-item">{it.item}</div>
                <div className="bom-spec">{it.spec}</div>
                <div className="bom-qty mono">{it.qty}</div>
                <div className="bom-vendor">{it.vendor}{it.sku && it.sku !== "—" && <span className="bom-sku mono">{it.sku}</span>}</div>
                <div className="bom-goods mono r">{it.goods}</div>
                <div className="bom-ship mono r">{it.ship}</div>
                <div className="bom-status"><Flag s={it.status} />{it.note && <span className="bom-note">{it.note}</span>}</div>
              </div>
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}

function BOM() {
  const [phase, setPhase] = useStateT("phase1");
  return (
    <div className="tab-wrap">
      <div className="tab-toolbar">
        <SectionHead n="04" kicker="Bill of materials" title={window.PRSM.bom[phase].title} />
        <div className="seg">
          <button className={phase === "phase1" ? "on" : ""} onClick={() => setPhase("phase1")}>Phase 1</button>
          <button className={phase === "phase2" ? "on" : ""} onClick={() => setPhase("phase2")}>Phase 2</button>
        </div>
      </div>
      <div className="flag-legend">
        {Object.keys(FLAG_LABEL).map((k) => <Flag key={k} s={k} />)}
      </div>
      <BomTable phase={phase} />
      <p className="fine mono">Quantities & specs are computed/script-traced. Dollar, freight & tax figures are dated 2026-06-03 web-researched estimates — confirm at checkout or via RFQ before ordering.</p>
    </div>
  );
}

/* ------------------------------ COSTS --------------------------------- */
function RollList({ rows }) {
  return (
    <div className="roll">
      {rows.map((r, i) => (
        <div className={"roll-row" + (r.strong ? " strong" : "")} key={i}>
          <span className="roll-k">{r.k}{r.flag && <span className="stamp shore" style={{ marginLeft: 8 }}>{r.flag}</span>}</span>
          <span className="roll-v mono">{r.v}</span>
        </div>
      ))}
    </div>
  );
}

function Costs() {
  const P = window.PRSM;
  const t = P.transition;
  return (
    <div className="tab-wrap">
      <div className="est-banner">
        <span className="stamp estimate">Estimate</span>
        <p>Every dollar here is a <b>dated 2026-06-03 web-researched estimate</b> — prices move with copper, tariffs and shipping. Quantities and electrical specs are computed; the money is not. Confirm at checkout / via RFQ before ordering.</p>
      </div>

      <div className="cost-cols">
        <div className="sheet pad">
          <div className="cost-head"><span className="stamp estimate">Phase 1</span><span className="cost-total mono">$7,800–8,500</span></div>
          <h3 className="cost-sub">Goods roll-up</h3>
          <RollList rows={P.bom.phase1.rollup} />
          <hr className="dotrule" style={{ margin: "16px 0" }} />
          <h3 className="cost-sub">Landed</h3>
          <RollList rows={P.bom.phase1.landed} />
        </div>
        <div className="sheet pad">
          <div className="cost-head"><span className="stamp computed">Phase 2</span><span className="cost-total mono">$6,300–6,700</span></div>
          <h3 className="cost-sub">Goods roll-up (Δ)</h3>
          <RollList rows={P.bom.phase2.rollup} />
          <hr className="dotrule" style={{ margin: "16px 0" }} />
          <h3 className="cost-sub">Landed delta</h3>
          <RollList rows={P.bom.phase2.landed} />
        </div>
      </div>

      <div className="sheet pad two-phase">
        <SectionHead n="05" title="Two-phase total + racking" sub="Electrical landed (computed BoS, dated estimates) with the Phase-1 wood-rack materials folded in." />
        <div className="tp-grid">
          <div className="tp-row tp-head"><div>Stage</div><div className="r">Goods</div><div className="r">Shipping</div><div className="r">Landed</div></div>
          <div className="tp-row"><div>Phase 1 · electrical</div><div className="r mono">$6,745–6,955</div><div className="r mono">$590–1,040</div><div className="r mono">$7,800–8,500</div></div>
          <div className="tp-row"><div>Phase 2 (Δ) · electrical</div><div className="r mono">$5,650–5,850</div><div className="r mono">$320–470</div><div className="r mono">$6,300–6,700</div></div>
          <div className="tp-row"><div>Both phases · electrical</div><div className="r mono">{P.summary.combined.goods}</div><div className="r mono">{P.summary.combined.shipping}</div><div className="r mono">{P.summary.combined.landed}</div></div>
          <div className="tp-row"><div>+ Racking · Ph-1 wood rack <span className="stamp estimate" style={{ marginLeft: 6 }}>materials est</span></div><div className="r mono">$1,400–2,800</div><div className="r mono">local</div><div className="r mono">$1,500–2,950</div></div>
          <div className="tp-row tp-total"><div>Both phases + racking</div><div className="r mono">$13,800–15,600</div><div className="r mono">$910–1,510</div><div className="r mono">$15,600–18,150</div></div>
        </div>
        <p className="fine mono">Racking is the Phase-1 wood-rack materials band from the structural handoff (landed = +6% VT use tax) — refine it live in the <b>Racking</b> tab calculator (default ≈ $2,508). Phase 2's added bank uses the identical design; structural labor, foundations & trenching remain owner-fabricated (out of scope).</p>
      </div>

      <div className="sheet pad trans">
        <div className="trans-head">
          <div>
            <span className="stamp shore">Optional</span>
            <h3 className="cost-sub" style={{ marginTop: 8 }}>{t.title}</h3>
            <p className="trans-sub mono">{t.subtitle}</p>
          </div>
          <span className="cost-total mono">{t.total}</span>
        </div>
        <p className="trans-blurb">{t.blurb}</p>
        <div className="trans-items">
          {t.items.map((it, i) => (
            <div className="trans-item" key={i}>
              <div className="trans-item-top"><span>{it.item}</span><b className="mono">{it.goods}</b></div>
              <div className="trans-item-spec mono">{it.spec}</div>
            </div>
          ))}
        </div>
        <p className="trans-rule"><span className="mono">RULE</span>{t.rule}</p>
      </div>
    </div>
  );
}

/* --------------------------- SCHEMATICS ------------------------------- */
function Schematics() {
  const [phase, setPhase] = useStateT("phase1");
  const [idx, setIdx] = useStateT(0);
  const list = window.SCHEMATICS[phase];
  const cur = list[Math.min(idx, list.length - 1)];
  const Comp = cur.Comp;
  const setP = (p) => { setPhase(p); setIdx(0); };
  return (
    <div className="tab-wrap">
      <div className="tab-toolbar">
        <SectionHead n="06" kicker="Schematics" title={phase === "phase1" ? "Phase 1 — Hybrid" : "Phase 2 — Off-Grid"} sub="Redrawn from the validated SLD topology & computed values. Drag to pan, scroll to zoom." />
        <div className="seg">
          <button className={phase === "phase1" ? "on" : ""} onClick={() => setP("phase1")}>Phase 1</button>
          <button className={phase === "phase2" ? "on" : ""} onClick={() => setP("phase2")}>Phase 2</button>
        </div>
      </div>
      <div className="seg seg-line">
        {list.map((s, i) => (
          <button key={s.id} className={i === idx ? "on" : ""} onClick={() => setIdx(i)}>{s.label}</button>
        ))}
      </div>
      <Comp key={cur.id} />
      <div className="sch-notes">
        <div className="stamp computed">Computed</div>
        <p className="mono">Topology, gauges (8 AWG PV · 2/0 Cu battery), OCPD (175 A Class-T · ≥350 A bank), cold Voc 430.1 V and string sizing are script-traced. Symbols are schematic, not to scale; confirm trench/run lengths before buying wire.</p>
      </div>
    </div>
  );
}

/* ----------------------------- FLYOVER -------------------------------- */
function Flyover() {
  return (
    <div className="tab-wrap">
      <SectionHead n="07" kicker="3D rendering" title="Site flyover" sub="Placeholder slot — drop a rendered video or stills of the installed ground-mount here." />
      <div className="fly-slot">
        <div className="fly-inner">
          <div className="fly-play">▶</div>
          <div className="fly-title mono">3D RENDER / FLYOVER</div>
          <div className="fly-sub">Drop a video file or render sequence of the installed array, inverter wall & battery bank.</div>
          <div className="fly-specs">
            <span className="tag">15-acre ground-mount</span>
            <span className="tag">55° tilt (as-built)</span>
            <span className="tag">4×7S array</span>
            <span className="tag">battery shed (Ph 2)</span>
          </div>
        </div>
        <div className="fly-corner tl" /><div className="fly-corner tr" /><div className="fly-corner bl" /><div className="fly-corner br" />
      </div>
      <p className="fine mono">When you have a render, hand me the file and I'll wire it in here with playback controls.</p>
    </div>
  );
}

Object.assign(window, { Overview, BOM, Costs, Schematics, Flyover, SectionHead, Flag });