pelagia-portal/design_handoff_pelagia_portal/components.jsx
Hardik d769cae71e chore(inventory): remove item detail page; move SiteSelect to shared components
- Delete /inventory/items/[id] — items expand inline in the list
- Move SiteSelect from deleted [id] folder to components/inventory/site-select
- Fix admin product detail page import to use new shared path
- Fix items-table: Fragment key prop, restore Link import, plain text item names
- Fix vendor-items-table: remove broken link to deleted item detail page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 00:07:04 +05:30

104 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Shared components for Pelagia Portal */
const { useState, useEffect, useRef, useMemo } = React;
/* ─────────── Icons (inline SVG, 14×14) ─────────── */
const Icon = ({ name, size = 14 }) => {
const paths = {
home: <><path d="M2 7l6-5 6 5v7a1 1 0 0 1-1 1h-3v-5H6v5H3a1 1 0 0 1-1-1V7z"/></>,
file: <><path d="M3 1.5h6l3.5 3.5v9a1 1 0 0 1-1 1h-8.5a1 1 0 0 1-1-1v-12a1 1 0 0 1 1-1z M9 1.5V5h3.5"/></>,
list: <><path d="M2 4h12 M2 8h12 M2 12h12"/></>,
check: <><path d="M3.5 8L7 11.5L13 4.5"/></>,
cart: <><path d="M2 2h2l2 9h8l1.5-6h-9 M6.5 13.5a1 1 0 1 0 0-.001 M12.5 13.5a1 1 0 1 0 0-.001"/></>,
box: <><path d="M2 4.5l6-2.5 6 2.5v7l-6 2.5-6-2.5v-7z M2 4.5l6 2.5 6-2.5 M8 7v7.5"/></>,
truck: <><path d="M1 3.5h8.5v7.5h-8.5z M9.5 6h3l2 2.5v2.5h-5z M4 12.5a1.25 1.25 0 1 0 0-.01 M11.5 12.5a1.25 1.25 0 1 0 0-.01"/></>,
users: <><path d="M5.5 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4z M1.5 14c0-2 1.5-3.5 4-3.5s4 1.5 4 3.5 M10.5 7.5a1.75 1.75 0 1 0 0-3.5 M14.5 13c0-1.6-1.2-2.8-3-2.8"/></>,
ship: <><path d="M2 11h12l-1 3h-10z M3 11V6l5-2 5 2v5 M8 4v-2 M5.5 8h5"/></>,
map: <><path d="M5.5 1.5l-3.5 1.5v11l3.5-1.5 5 1.5 3.5-1.5v-11l-3.5 1.5-5-1.5z M5.5 1.5v11 M10.5 3v11"/></>,
chart: <><path d="M2 13V3 M2 13h12 M5 11V8 M8 11V5 M11 11V7"/></>,
user: <><path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6z M2 14c0-3 2.5-5 6-5s6 2 6 5"/></>,
settings: <><path d="M8 5.5a2.5 2.5 0 1 0 0 5a2.5 2.5 0 0 0 0-5z M8 1v1.5 M8 13.5V15 M2.5 5L4 6 M12 10l1.5 1 M2.5 11L4 10 M12 6l1.5-1 M1 8h1.5 M13.5 8H15"/></>,
plus: <><path d="M8 3v10 M3 8h10"/></>,
download: <><path d="M8 2v9 M4 7l4 4 4-4 M2.5 13.5h11"/></>,
upload: <><path d="M8 11V2 M4 6l4-4 4 4 M2.5 13.5h11"/></>,
search: <><circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5L14 14"/></>,
chevron: <><path d="M5 3l5 5-5 5"/></>,
star: <><path d="M8 1.5l2 4.5 5 .5-3.7 3.4 1 5-4.3-2.5-4.3 2.5 1-5L1 6.5l5-.5z"/></>,
edit: <><path d="M11 2l3 3-8.5 8.5h-3v-3z M9.5 3.5l3 3"/></>,
trash: <><path d="M3 4h10 M5 4V2.5h6V4 M4.5 4l.5 9.5a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1l.5-9.5"/></>,
bell: <><path d="M8 1.5a4 4 0 0 1 4 4v3l1.5 2h-11l1.5-2v-3a4 4 0 0 1 4-4z M6 11.5a2 2 0 0 0 4 0"/></>,
eye: <><path d="M1 8s2.5-4.5 7-4.5S15 8 15 8s-2.5 4.5-7 4.5S1 8 1 8z"/><circle cx="8" cy="8" r="2"/></>,
refresh: <><path d="M13.5 8a5.5 5.5 0 0 1-9.5 3.5 M13.5 4v3.5h-3.5 M2.5 8a5.5 5.5 0 0 1 9.5-3.5 M2.5 12V8.5H6"/></>,
paperclip: <><path d="M12 7l-5 5a3 3 0 1 1-4.2-4.2L9 1.5a2 2 0 1 1 3 3L5.5 11a1 1 0 1 1-1.5-1.5L10 3.5"/></>,
arrowRight: <><path d="M3 8h10 M9 4l4 4-4 4"/></>,
pkg: <><path d="M2 4.5L8 2l6 2.5V11L8 13.5 2 11z M2 4.5L8 7l6-2.5 M8 7v6.5"/></>,
};
return (
<svg className="nav-icon" width={size} height={size} viewBox="0 0 16 16"
fill="none" stroke="currentColor" strokeWidth="1.4"
strokeLinecap="round" strokeLinejoin="round">
{paths[name] || null}
</svg>
);
};
/* ─────────── Status Badge ─────────── */
const STATUS_LABELS = {
DRAFT: ["draft", "Draft"],
SUBMITTED: ["submitted", "Submitted"],
MGR_REVIEW: ["review", "Under Review"],
VENDOR_ID_PENDING: ["vendor", "Vendor Needed"],
EDITS_REQUESTED: ["edits", "Edits Requested"],
MGR_APPROVED: ["approved", "Approved"],
SENT_FOR_PAYMENT: ["sent", "Payment Sent"],
PAID_DELIVERED: ["paid", "Paid · Awaiting Receipt"],
CLOSED: ["closed", "Closed"],
REJECTED: ["rejected", "Rejected"],
};
const Badge = ({ status, children, className = "", noDot }) => {
if (status) {
const [cls, label] = STATUS_LABELS[status] || ["draft", status];
return <span className={`badge ${cls} ${noDot ? "no-dot" : ""} ${className}`}>{label}</span>;
}
return <span className={`badge ${className} ${noDot ? "no-dot" : ""}`}>{children}</span>;
};
/* ─────────── Format helpers ─────────── */
const inr = (n) => "₹" + Number(n).toLocaleString("en-IN", { maximumFractionDigits: 0 });
const inrFull = (n) => "₹" + Number(n).toLocaleString("en-IN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
/* ─────────── Card ─────────── */
const Card = ({ title, action, children, flush, className = "" }) => (
<div className={`card ${className}`}>
{(title || action) && (
<div className="card-head">
<h3 className="card-title">{title}</h3>
{action}
</div>
)}
<div className={`card-body ${flush ? "flush" : ""}`}>{children}</div>
</div>
);
/* ─────────── Stat tile ─────────── */
const Stat = ({ label, value, sub, onClick }) => (
<div className={`stat ${onClick ? "clickable" : ""}`} onClick={onClick}>
<div className="stat-label">{label}</div>
<div className="stat-value">{value}</div>
{sub && <div className="stat-sub">{sub}</div>}
</div>
);
/* ─────────── Crumbs ─────────── */
const Crumbs = ({ items }) => (
<div className="crumbs">
{items.map((it, i) => (
<React.Fragment key={i}>
{i > 0 && <span className="sep">/</span>}
<span style={{ color: i === items.length - 1 ? "var(--ink-2)" : undefined }}>{it}</span>
</React.Fragment>
))}
</div>
);
Object.assign(window, { Icon, Badge, Card, Stat, Crumbs, inr, inrFull, STATUS_LABELS });