Table UI fix and XLSX library fix

This commit is contained in:
2026-04-01 20:12:19 -05:00
parent 6ee62c2225
commit f47385abdc
3 changed files with 105 additions and 13 deletions

View File

@@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from "react"; import { useState, useRef, useEffect } from "react";
import * as XLSX from "xlsx"; import ExcelJS from "exceljs";
import { chemicalsApi } from "../lib/api"; import { chemicalsApi } from "../lib/api";
import type { ChemicalInventory } from "../shared/types"; import type { ChemicalInventory } from "../shared/types";
import { Card } from "./ui/card"; import { Card } from "./ui/card";
@@ -282,15 +282,19 @@ export function Inventory() {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
function handleDownloadExcel() { async function handleDownloadExcel() {
const wsData = [ const wb = new ExcelJS.Workbook();
TABLE_COLUMNS.map(c => c.label), const ws = wb.addWorksheet("Chemical Inventory");
...filtered.map(item => TABLE_COLUMNS.map(c => item[c.key] ?? "")), ws.addRow(TABLE_COLUMNS.map(c => c.label));
]; filtered.forEach(item => ws.addRow(TABLE_COLUMNS.map(c => item[c.key] ?? "")));
const ws = XLSX.utils.aoa_to_sheet(wsData); const buffer = await wb.xlsx.writeBuffer();
const wb = XLSX.utils.book_new(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
XLSX.utils.book_append_sheet(wb, ws, "Chemical Inventory"); const url = URL.createObjectURL(blob);
XLSX.writeFile(wb, "chemical_inventory.xlsx"); const a = document.createElement("a");
a.href = url;
a.download = "chemical_inventory.xlsx";
a.click();
URL.revokeObjectURL(url);
} }
// ── derived ───────────────────────────────────────────────────────────── // ── derived ─────────────────────────────────────────────────────────────
@@ -622,7 +626,7 @@ export function Inventory() {
</div> </div>
</div> </div>
</DialogHeader> </DialogHeader>
<div className="overflow-auto flex-1"> <div className="table-scroll flex-1">
<table className="text-xs border-collapse min-w-max"> <table className="text-xs border-collapse min-w-max">
<thead className="sticky top-0 z-10 bg-muted"> <thead className="sticky top-0 z-10 bg-muted">
<tr> <tr>

View File

@@ -42,6 +42,7 @@
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"exceljs": "^4.4.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.487.0", "lucide-react": "^0.487.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
@@ -53,8 +54,7 @@
"recharts": "^2.15.2", "recharts": "^2.15.2",
"sonner": "^2.0.3", "sonner": "^2.0.3",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"vaul": "^1.1.2", "vaul": "^1.1.2"
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",

View File

@@ -197,4 +197,92 @@
html { html {
font-size: var(--font-size); font-size: var(--font-size);
}
.table-scroll {
overflow: scroll;
flex: 1;
}
.table-scroll::-webkit-scrollbar {
width: 14px;
height: 14px;
}
.table-scroll::-webkit-scrollbar-track {
background: var(--muted);
border: 1px solid var(--border);
}
.table-scroll::-webkit-scrollbar-thumb {
background: var(--muted-foreground);
border: 1px solid var(--border);
border-radius: 999px;
min-width: 30px;
min-height: 30px;
}
.table-scroll::-webkit-scrollbar-thumb:hover {
background: var(--foreground);
}
/* Hide all buttons by default, then selectively show the correct one per end */
.table-scroll::-webkit-scrollbar-button {
display: none;
}
/* Up arrow — top of vertical bar */
.table-scroll::-webkit-scrollbar-button:vertical:decrement:start {
display: block;
background: var(--muted);
border: 1px solid var(--border);
border-radius: 999px 999px 0 0;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath d='M4 2 L7 6 L1 6 Z' fill='%23666'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: 8px 8px;
}
/* Down arrow — bottom of vertical bar */
.table-scroll::-webkit-scrollbar-button:vertical:increment:end {
display: block;
background: var(--muted);
border: 1px solid var(--border);
border-radius: 0 0 999px 999px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath d='M4 6 L7 2 L1 2 Z' fill='%23666'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: 8px 8px;
}
/* Left arrow — left end of horizontal bar */
.table-scroll::-webkit-scrollbar-button:horizontal:decrement:start {
display: block;
background: var(--muted);
border: 1px solid var(--border);
border-radius: 999px 0 0 999px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath d='M2 4 L6 1 L6 7 Z' fill='%23666'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: 8px 8px;
}
/* Right arrow — right end of horizontal bar */
.table-scroll::-webkit-scrollbar-button:horizontal:increment:end {
display: block;
background: var(--muted);
border: 1px solid var(--border);
border-radius: 0 999px 999px 0;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath d='M6 4 L2 1 L2 7 Z' fill='%23666'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: 8px 8px;
}
.table-scroll::-webkit-scrollbar-button:hover {
background: var(--border);
}
.table-scroll::-webkit-scrollbar-corner {
background: transparent;
} }