mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-11-13 13:55:14 +00:00
React
This commit is contained in:
16
frontend/src/components/Table/EmptyRow.tsx
Normal file
16
frontend/src/components/Table/EmptyRow.tsx
Normal 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 };
|
||||
@@ -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" });
|
||||
}
|
||||
25
frontend/src/components/Table/Formatter/DomainsFormatter.tsx
Normal file
25
frontend/src/components/Table/Formatter/DomainsFormatter.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
11
frontend/src/components/Table/Formatter/StatusFormatter.tsx
Normal file
11
frontend/src/components/Table/Formatter/StatusFormatter.tsx
Normal 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>;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
5
frontend/src/components/Table/Formatter/index.ts
Normal file
5
frontend/src/components/Table/Formatter/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from "./CertificateFormatter";
|
||||
export * from "./DomainsFormatter";
|
||||
export * from "./GravatarFormatter";
|
||||
export * from "./StatusFormatter";
|
||||
export * from "./ValueWithDateFormatter";
|
||||
39
frontend/src/components/Table/TableBody.tsx
Normal file
39
frontend/src/components/Table/TableBody.tsx
Normal 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 };
|
||||
26
frontend/src/components/Table/TableHeader.tsx
Normal file
26
frontend/src/components/Table/TableHeader.tsx
Normal 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 };
|
||||
64
frontend/src/components/Table/TableHelpers.ts
Normal file
64
frontend/src/components/Table/TableHelpers.ts
Normal 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 };
|
||||
22
frontend/src/components/Table/TableLayout.tsx
Normal file
22
frontend/src/components/Table/TableLayout.tsx
Normal 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 };
|
||||
4
frontend/src/components/Table/index.ts
Normal file
4
frontend/src/components/Table/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./Formatter";
|
||||
export * from "./TableHeader";
|
||||
export * from "./TableHelpers";
|
||||
export * from "./TableLayout";
|
||||
Reference in New Issue
Block a user