This commit is contained in:
Jamie Curnow
2025-09-02 23:56:00 +10:00
parent 330993f028
commit fadec9751e
355 changed files with 9308 additions and 17813 deletions

View File

@@ -0,0 +1,16 @@
import type { Table as ReactTable } from "@tanstack/react-table";
interface Props {
tableInstance: ReactTable<any>;
}
function EmptyRow({ tableInstance }: Props) {
return (
<tr>
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
<p className="text-center">There are no items</p>
</td>
</tr>
);
}
export { EmptyRow };

View File

@@ -0,0 +1,13 @@
import type { Certificate } from "src/api/backend";
import { intl } from "src/locale";
interface Props {
certificate?: Certificate;
}
export function CertificateFormatter({ certificate }: Props) {
if (certificate) {
return intl.formatMessage({ id: "lets-encrypt" });
}
return intl.formatMessage({ id: "http-only" });
}

View File

@@ -0,0 +1,25 @@
import { intlFormat, parseISO } from "date-fns";
import { intl } from "src/locale";
interface Props {
domains: string[];
createdOn?: string;
}
export function DomainsFormatter({ domains, createdOn }: Props) {
return (
<div className="flex-fill">
<div className="font-weight-medium">
{domains.map((domain: string) => (
<span key={domain} className="badge badge-lg domain-name">
{domain}
</span>
))}
</div>
{createdOn ? (
<div className="text-secondary mt-1">
{intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })}
</div>
) : null}
</div>
);
}

View File

@@ -0,0 +1,17 @@
interface Props {
url: string;
name?: string;
}
export function GravatarFormatter({ url, name }: Props) {
return (
<div className="d-flex py-1 align-items-center">
<span
title={name}
className="avatar avatar-2 me-2"
style={{
backgroundImage: `url(${url})`,
}}
/>
</div>
);
}

View File

@@ -0,0 +1,11 @@
import { intl } from "src/locale";
interface Props {
enabled: boolean;
}
export function StatusFormatter({ enabled }: Props) {
if (enabled) {
return <span className="badge bg-lime-lt">{intl.formatMessage({ id: "online" })}</span>;
}
return <span className="badge bg-red-lt">{intl.formatMessage({ id: "offline" })}</span>;
}

View File

@@ -0,0 +1,21 @@
import { intlFormat, parseISO } from "date-fns";
import { intl } from "src/locale";
interface Props {
value: string;
createdOn?: string;
}
export function ValueWithDateFormatter({ value, createdOn }: Props) {
return (
<div className="flex-fill">
<div className="font-weight-medium">
<div className="font-weight-medium">{value}</div>
</div>
{createdOn ? (
<div className="text-secondary mt-1">
{intl.formatMessage({ id: "created-on" }, { date: intlFormat(parseISO(createdOn)) })}
</div>
) : null}
</div>
);
}

View File

@@ -0,0 +1,5 @@
export * from "./CertificateFormatter";
export * from "./DomainsFormatter";
export * from "./GravatarFormatter";
export * from "./StatusFormatter";
export * from "./ValueWithDateFormatter";

View File

@@ -0,0 +1,39 @@
import { flexRender } from "@tanstack/react-table";
import type { TableLayoutProps } from "src/components";
import { EmptyRow } from "./EmptyRow";
function TableBody<T>(props: TableLayoutProps<T>) {
const { tableInstance, extraStyles, emptyState } = props;
const rows = tableInstance.getRowModel().rows;
if (rows.length === 0) {
return emptyState ? (
emptyState
) : (
<tbody className="table-tbody">
<EmptyRow tableInstance={tableInstance} />
</tbody>
);
}
return (
<tbody className="table-tbody">
{rows.map((row: any) => {
return (
<tr key={row.id} {...extraStyles?.row(row.original)}>
{row.getVisibleCells().map((cell: any) => {
const { className } = (cell.column.columnDef.meta as any) ?? {};
return (
<td key={cell.id} className={className}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
);
})}
</tr>
);
})}
</tbody>
);
}
export { TableBody };

View File

@@ -0,0 +1,26 @@
import type { TableLayoutProps } from "src/components";
function TableHeader<T>(props: TableLayoutProps<T>) {
const { tableInstance } = props;
const headerGroups = tableInstance.getHeaderGroups();
return (
<thead>
{headerGroups.map((headerGroup: any) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header: any) => {
const { column } = header;
const { className } = (column.columnDef.meta as any) ?? {};
return (
<th key={header.id} className={className}>
{typeof column.columnDef.header === "string" ? `${column.columnDef.header}` : null}
</th>
);
})}
</tr>
))}
</thead>
);
}
export { TableHeader };

View File

@@ -0,0 +1,64 @@
export interface TablePagination {
limit: number;
offset: number;
total: number;
}
export interface TableSortBy {
id: string;
desc: boolean;
}
export interface TableFilter {
id: string;
value: any;
}
const tableEvents = {
FILTERS_CHANGED: "FILTERS_CHANGED",
PAGE_CHANGED: "PAGE_CHANGED",
PAGE_SIZE_CHANGED: "PAGE_SIZE_CHANGED",
TOTAL_COUNT_CHANGED: "TOTAL_COUNT_CHANGED",
SORT_CHANGED: "SORT_CHANGED",
};
const tableEventReducer = (state: any, { type, payload }: any) => {
let offset = state.offset;
switch (type) {
case tableEvents.PAGE_CHANGED:
return {
...state,
offset: payload * state.limit,
};
case tableEvents.PAGE_SIZE_CHANGED:
return {
...state,
limit: payload,
};
case tableEvents.TOTAL_COUNT_CHANGED:
return {
...state,
total: payload,
};
case tableEvents.SORT_CHANGED:
return {
...state,
sortBy: payload,
};
case tableEvents.FILTERS_CHANGED:
if (state.filters !== payload) {
// this actually was a legit change
// sets to page 1 when filter is modified
offset = 0;
}
return {
...state,
filters: payload,
offset,
};
default:
throw new Error(`Unhandled action type: ${type}`);
}
};
export { tableEvents, tableEventReducer };

View File

@@ -0,0 +1,22 @@
import type { Table as ReactTable } from "@tanstack/react-table";
import { TableBody } from "./TableBody";
import { TableHeader } from "./TableHeader";
interface TableLayoutProps<TFields> {
tableInstance: ReactTable<TFields>;
emptyState?: React.ReactNode;
extraStyles?: {
row: (rowData: TFields) => any | undefined;
};
}
function TableLayout<TFields>(props: TableLayoutProps<TFields>) {
const hasRows = props.tableInstance.getRowModel().rows.length > 0;
return (
<table className="table table-vcenter table-selectable mb-0">
{hasRows ? <TableHeader tableInstance={props.tableInstance} /> : null}
<TableBody {...props} />
</table>
);
}
export { TableLayout, type TableLayoutProps };

View File

@@ -0,0 +1,4 @@
export * from "./Formatter";
export * from "./TableHeader";
export * from "./TableHelpers";
export * from "./TableLayout";