mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
ready to test
Testing Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -1,3 +1,28 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name : table.util.jsx
|
||||
* Type : Utility
|
||||
* Description : Shared helpers for the generic admin DataTable system.
|
||||
* Provides:
|
||||
* - pageSizes constant for the page-size selector
|
||||
* - BADGE_STYLES + EnumBadge for enum-type column rendering
|
||||
* - formatDate (date-only, "MMM d, yyyy") used as the default
|
||||
* renderer for "date"-type attributes app-wide
|
||||
* - renderCell, the default per-type cell renderer dispatched
|
||||
* by buildColumns when no cellOverride is supplied
|
||||
* - ColumnFilter, the per-column filter UI (enum/date/text)
|
||||
* - SortIcon, the header sort indicator
|
||||
* - longTextCell, a reusable cellOverride factory for long
|
||||
* values (UUIDs, free text notes, etc.) — truncates the
|
||||
* value and shows an "Eye · View" trigger that opens a
|
||||
* shadcn Dialog with the full value
|
||||
* - buildColumns, which turns server-provided attribute
|
||||
* metadata into TanStack column definitions
|
||||
*
|
||||
* Author: rgrgogu
|
||||
* Modified by: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: May 6, 2026
|
||||
* Date Modified: Jun 17, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
@@ -6,7 +31,10 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select";
|
||||
import { Search } from "lucide-react";
|
||||
import {
|
||||
Dialog, DialogContent, DialogHeader, DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Search, ArrowUp, ArrowDown, ArrowUpDown, Eye } from "lucide-react";
|
||||
|
||||
export const pageSizes = [10, 25, 50, 75, 100, 250, 500, 750, 1000]
|
||||
|
||||
@@ -106,7 +134,7 @@ export function ColumnFilter({ column, attr }) {
|
||||
onValueChange={(v) => {
|
||||
const value = v === "__all__" ? "" : v;
|
||||
setInputValue(value);
|
||||
column.setFilterValue(value); // ✅ auto apply
|
||||
column.setFilterValue(value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs w-full">
|
||||
@@ -254,6 +282,64 @@ export function SortIcon({ column }) {
|
||||
return <ArrowUpDown className="ml-1 h-3 w-3 text-muted-foreground/40 shrink-0" />;
|
||||
}
|
||||
|
||||
// ── Long text cell — truncate + "Eye · View" dialog trigger ────────────────────
|
||||
//
|
||||
// Generic, reusable across any table/column. Any value longer than the
|
||||
// threshold renders truncated with a small "View" indicator; clicking opens
|
||||
// a shadcn Dialog showing the full value. Values at or under the threshold
|
||||
// render plainly, same as the default text cell.
|
||||
//
|
||||
// Usage in any columns.config.jsx:
|
||||
// import { longTextCell } from "@/utils/table.util";
|
||||
// const cellOverrides = {
|
||||
// completion_id: longTextCell("Completion ID"),
|
||||
// note: longTextCell("Note"),
|
||||
// };
|
||||
|
||||
const LONG_TEXT_THRESHOLD = 24; // characters — like UUIDs are 36, short notes may be under this
|
||||
|
||||
function LongTextCell({ value, label = "Full value" }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return <span className="text-muted-foreground/40">-</span>;
|
||||
}
|
||||
|
||||
const str = String(value);
|
||||
if (str.length <= LONG_TEXT_THRESHOLD) {
|
||||
return <span className="text-sm">{str}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setOpen(true)}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
>
|
||||
<div className="truncate max-w-[160px]">{str}</div>
|
||||
<div className="inline-flex items-center gap-1 text-xs text-muted-foreground shrink-0">
|
||||
<Eye className="size-3.5" /> View
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{label}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<p className="text-sm break-all bg-muted rounded-md p-3">{str}</p>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function longTextCell(label) {
|
||||
return (info) => <LongTextCell value={info.getValue()} label={label} />;
|
||||
}
|
||||
|
||||
// ── Build columns dynamically from attributes ──────────────────────────────────
|
||||
const columnHelper = createColumnHelper();
|
||||
|
||||
@@ -276,5 +362,4 @@ export function buildColumns(attributes, { cellOverrides = {} } = {}) {
|
||||
meta: { attr },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,25 @@ export function getTimestamp() {
|
||||
return `${YYYY}${MM}${DD}_${HH}${mm}${SS}`;
|
||||
}
|
||||
|
||||
export function timeAgo(ts) {
|
||||
if (!ts) return "—";
|
||||
const diff = Math.floor((Date.now() - new Date(ts).getTime()) / 1000);
|
||||
if (diff < 5) return "just now";
|
||||
if (diff < 60) return `${diff} sec ago`;
|
||||
const mins = Math.floor(diff / 60);
|
||||
if (mins < 60) return `${mins} min${mins !== 1 ? "s" : ""} ago`;
|
||||
const hrs = Math.floor(mins / 60);
|
||||
if (hrs < 24) return `${hrs} hr${hrs !== 1 ? "s" : ""} ago`;
|
||||
const days = Math.floor(hrs / 24);
|
||||
if (days < 7) return `${days} day${days !== 1 ? "s" : ""} ago`;
|
||||
const weeks = Math.floor(days / 7);
|
||||
if (weeks < 5) return `${weeks} week${weeks !== 1 ? "s" : ""} ago`;
|
||||
const months = Math.floor(days / 30);
|
||||
if (months < 12) return `${months} month${months !== 1 ? "s" : ""} ago`;
|
||||
const years = Math.floor(days / 365);
|
||||
return `${years} year${years !== 1 ? "s" : ""} ago`;
|
||||
}
|
||||
|
||||
export function formatDuration (secs) {
|
||||
if (!secs) return "0 min";
|
||||
const h = Math.floor(secs / 3600);
|
||||
|
||||
Reference in New Issue
Block a user