Implements #17, #18, #19, #20, #21 - Add CSS custom properties for density-dependent spacing (--d-* vars) in theme.css with comfortable (default) and compact modes - Create useDensity hook with localStorage persistence and DOM attribute sync - Add FOUC prevention in main.tsx (sync density before first paint) - Create shared PageFooter component merging stats + pagination - Refactor AppShell to flex layout with density toggle button (COM/CMP) - Consolidate inline pagination from ItemsPage/AuditPage into PageFooter - Delete FooterStats.tsx (replaced by PageFooter) - Replace all hardcoded padding/font/gap values in ItemTable, AuditTable, ItemsToolbar, and AuditToolbar with var(--d-*) references Comfortable mode is already tighter than the previous hardcoded values. Compact mode reduces further for power-user density.
171 lines
4.5 KiB
TypeScript
171 lines
4.5 KiB
TypeScript
import type { AuditItemResult } from "../../api/types";
|
|
|
|
const tierColors: Record<string, string> = {
|
|
critical: "var(--ctp-red)",
|
|
low: "var(--ctp-peach)",
|
|
partial: "var(--ctp-yellow)",
|
|
good: "var(--ctp-green)",
|
|
complete: "var(--ctp-teal)",
|
|
};
|
|
|
|
interface AuditTableProps {
|
|
items: AuditItemResult[];
|
|
loading: boolean;
|
|
selectedPN: string | null;
|
|
onSelect: (pn: string) => void;
|
|
}
|
|
|
|
export function AuditTable({
|
|
items,
|
|
loading,
|
|
selectedPN,
|
|
onSelect,
|
|
}: AuditTableProps) {
|
|
if (loading) {
|
|
return (
|
|
<div
|
|
style={{
|
|
padding: "2rem",
|
|
color: "var(--ctp-subtext0)",
|
|
textAlign: "center",
|
|
}}
|
|
>
|
|
Loading audit data...
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (items.length === 0) {
|
|
return (
|
|
<div
|
|
style={{
|
|
padding: "2rem",
|
|
color: "var(--ctp-subtext0)",
|
|
textAlign: "center",
|
|
}}
|
|
>
|
|
No items found
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div style={{ overflow: "auto", flex: 1 }}>
|
|
<table
|
|
style={{
|
|
width: "100%",
|
|
borderCollapse: "collapse",
|
|
fontSize: "0.8rem",
|
|
}}
|
|
>
|
|
<thead>
|
|
<tr>
|
|
{[
|
|
"Score",
|
|
"Part Number",
|
|
"Description",
|
|
"Category",
|
|
"Sourcing",
|
|
"Missing",
|
|
].map((h) => (
|
|
<th key={h} style={thStyle}>
|
|
{h}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{items.map((item) => {
|
|
const color = tierColors[item.tier] ?? "var(--ctp-subtext0)";
|
|
const isSelected = selectedPN === item.part_number;
|
|
return (
|
|
<tr
|
|
key={item.part_number}
|
|
onClick={() => onSelect(item.part_number)}
|
|
style={{
|
|
cursor: "pointer",
|
|
backgroundColor: isSelected
|
|
? "var(--ctp-surface1)"
|
|
: "transparent",
|
|
transition: "background-color 0.15s",
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
if (!isSelected)
|
|
e.currentTarget.style.backgroundColor =
|
|
"var(--ctp-surface0)";
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
if (!isSelected)
|
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
}}
|
|
>
|
|
<td style={tdStyle}>
|
|
<span
|
|
style={{
|
|
display: "inline-block",
|
|
padding: "0.15rem 0.5rem",
|
|
borderRadius: "1rem",
|
|
fontSize: "0.75rem",
|
|
fontWeight: 600,
|
|
backgroundColor: color,
|
|
opacity: 0.9,
|
|
color: "var(--ctp-crust)",
|
|
}}
|
|
>
|
|
{(item.score * 100).toFixed(0)}%
|
|
</span>
|
|
</td>
|
|
<td
|
|
style={{
|
|
...tdStyle,
|
|
fontFamily: "'JetBrains Mono', monospace",
|
|
color: "var(--ctp-peach)",
|
|
}}
|
|
>
|
|
{item.part_number}
|
|
</td>
|
|
<td
|
|
style={{
|
|
...tdStyle,
|
|
maxWidth: 300,
|
|
overflow: "hidden",
|
|
textOverflow: "ellipsis",
|
|
whiteSpace: "nowrap",
|
|
}}
|
|
>
|
|
{item.description}
|
|
</td>
|
|
<td style={tdStyle}>{item.category_name || item.category}</td>
|
|
<td style={tdStyle}>{item.sourcing_type}</td>
|
|
<td style={{ ...tdStyle, textAlign: "center" }}>
|
|
{item.missing.length}
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const thStyle: React.CSSProperties = {
|
|
textAlign: "left",
|
|
padding: "var(--d-th-py) var(--d-th-px)",
|
|
fontSize: "var(--d-th-font)",
|
|
borderBottom: "1px solid var(--ctp-surface1)",
|
|
color: "var(--ctp-subtext0)",
|
|
fontWeight: 500,
|
|
position: "sticky",
|
|
top: 0,
|
|
backgroundColor: "var(--ctp-base)",
|
|
zIndex: 1,
|
|
};
|
|
|
|
const tdStyle: React.CSSProperties = {
|
|
padding: "var(--d-td-py) var(--d-td-px)",
|
|
fontSize: "var(--d-td-font)",
|
|
borderBottom: "1px solid var(--ctp-surface0)",
|
|
color: "var(--ctp-text)",
|
|
};
|