Skip to content

Commit 8c5def0

Browse files
committed
feat: add reusable datalist component
1 parent 13e5e9e commit 8c5def0

File tree

8 files changed

+825
-454
lines changed

8 files changed

+825
-454
lines changed

packages/blocks/invoice-list/src/frontend/InvoiceList.client.tsx

Lines changed: 68 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,22 @@ import React, { useState, useTransition } from 'react';
55

66
import { Mappings, Utils } from '@o2s/utils.frontend';
77

8-
import { cn } from '@o2s/ui/lib/utils';
9-
108
import { toast } from '@o2s/ui/hooks/use-toast';
119

1210
import { useGlobalContext } from '@o2s/ui/providers/GlobalProvider';
1311

12+
import { DataList } from '@o2s/ui/components/DataList';
13+
import type { DataListColumnConfig } from '@o2s/ui/components/DataList';
1414
import { FiltersSection } from '@o2s/ui/components/Filters';
1515
import { NoResults } from '@o2s/ui/components/NoResults';
1616
import { Pagination } from '@o2s/ui/components/Pagination';
17-
import { Price } from '@o2s/ui/components/Price';
1817

19-
import { Badge } from '@o2s/ui/elements/badge';
2018
import { Button } from '@o2s/ui/elements/button';
2119
import { Link } from '@o2s/ui/elements/link';
2220
import { LoadingOverlay } from '@o2s/ui/elements/loading-overlay';
2321
import { Separator } from '@o2s/ui/elements/separator';
24-
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@o2s/ui/elements/table';
2522

26-
import { Request } from '../api-harmonization/invoice-list.client';
23+
import { Model, Request } from '../api-harmonization/invoice-list.client';
2724
import { sdk } from '../sdk';
2825

2926
import { InvoiceListPureProps } from './InvoiceList.types';
@@ -74,6 +71,65 @@ export const InvoiceListPure: React.FC<InvoiceListPureProps> = ({ locale, access
7471
}
7572
};
7673

74+
// Define columns configuration outside JSX for better readability
75+
const columns = data.table.data.columns.map((column) => {
76+
switch (column.id) {
77+
case 'type':
78+
return {
79+
...column,
80+
type: 'text',
81+
cellClassName: 'max-w-[100px] md:max-w-sm',
82+
};
83+
case 'paymentStatus':
84+
return {
85+
...column,
86+
type: 'badge',
87+
variant: (value: string) =>
88+
Mappings.InvoiceBadge.invoiceBadgePaymentStatusVariants[
89+
value as keyof typeof Mappings.InvoiceBadge.invoiceBadgePaymentStatusVariants
90+
],
91+
};
92+
case 'paymentDueDate':
93+
return {
94+
...column,
95+
type: 'date',
96+
};
97+
case 'totalAmountDue':
98+
case 'totalNetAmountDue':
99+
return {
100+
...column,
101+
type: 'price',
102+
headerClassName: 'text-right',
103+
cellClassName: 'text-right',
104+
config: { currencyKey: 'currency' },
105+
};
106+
default:
107+
return {
108+
...column,
109+
type: 'text',
110+
};
111+
}
112+
}) as DataListColumnConfig<Model.Invoice>[];
113+
114+
const actions = data.table.data.actions
115+
? {
116+
...data.table.data.actions,
117+
render: (invoice: Model.Invoice) => (
118+
<Link asChild>
119+
<Button
120+
variant="link"
121+
className="flex items-center justify-end gap-2"
122+
onClick={() => handleDownload(invoice.id)}
123+
aria-description={data.downloadButtonAriaDescription?.replace('{id}', invoice.id)}
124+
>
125+
<Download className="h-4 w-4" />
126+
{data.table.data.actions!.label}
127+
</Button>
128+
</Link>
129+
),
130+
}
131+
: undefined;
132+
77133
return (
78134
<div className="w-full">
79135
{initialData.length > 0 ? (
@@ -91,121 +147,12 @@ export const InvoiceListPure: React.FC<InvoiceListPureProps> = ({ locale, access
91147
<LoadingOverlay isActive={isPending}>
92148
{data.invoices.data.length ? (
93149
<div className="flex flex-col gap-6">
94-
<Table>
95-
<TableHeader>
96-
<TableRow>
97-
{data.table.data.columns.map((column) => (
98-
<TableHead
99-
key={column.id}
100-
className={cn(
101-
'py-3 px-4 text-sm text-muted-foreground md:text-nowrap',
102-
column.id === 'totalAmountDue' && 'text-right',
103-
column.id === 'totalNetAmountDue' && 'text-right',
104-
)}
105-
>
106-
{column.title}
107-
</TableHead>
108-
))}
109-
{data.table.data.actions && (
110-
<TableHead className="py-3 px-4 text-sm text-muted-foreground md:text-nowrap">
111-
{data.table.data.actions.title}
112-
</TableHead>
113-
)}
114-
</TableRow>
115-
</TableHeader>
116-
<TableBody>
117-
{data.invoices.data.map((invoice) => {
118-
return (
119-
<TableRow key={invoice.id}>
120-
{data.table.data.columns.map((column) => {
121-
switch (column.id) {
122-
case 'type':
123-
return (
124-
<TableCell
125-
key={column.id}
126-
className="max-w-[100px] md:max-w-sm truncate whitespace-nowrap"
127-
>
128-
{invoice[column.id].displayValue}
129-
</TableCell>
130-
);
131-
case 'id':
132-
return (
133-
<TableCell
134-
key={column.id}
135-
className="truncate whitespace-nowrap"
136-
>
137-
{invoice[column.id]}
138-
</TableCell>
139-
);
140-
case 'paymentStatus':
141-
return (
142-
<TableCell
143-
key={column.id}
144-
className="whitespace-nowrap"
145-
>
146-
<Badge
147-
variant={
148-
Mappings.InvoiceBadge
149-
.invoiceBadgePaymentStatusVariants[
150-
invoice.paymentStatus.value
151-
]
152-
}
153-
>
154-
{invoice[column.id].displayValue}
155-
</Badge>
156-
</TableCell>
157-
);
158-
case 'paymentDueDate':
159-
return (
160-
<TableCell
161-
key={column.id}
162-
className="whitespace-nowrap truncate"
163-
>
164-
{invoice[column.id].displayValue}
165-
</TableCell>
166-
);
167-
case 'totalAmountDue':
168-
case 'totalNetAmountDue':
169-
return (
170-
<TableCell
171-
key={column.id}
172-
className="whitespace-nowrap text-right truncate"
173-
>
174-
<Price
175-
price={{
176-
value: invoice[column.id].value,
177-
currency: invoice.currency,
178-
}}
179-
/>
180-
</TableCell>
181-
);
182-
default:
183-
return null;
184-
}
185-
})}
186-
{data.table.data.actions && (
187-
<TableCell className="py-0">
188-
<Link asChild>
189-
<Button
190-
variant="link"
191-
className="flex items-center justify-end gap-2"
192-
onClick={() => handleDownload(invoice.id)}
193-
aria-description={data.downloadButtonAriaDescription?.replace(
194-
'{id}',
195-
invoice.id,
196-
)}
197-
>
198-
<Download className="h-4 w-4" />
199-
{data.table.data.actions.label}
200-
</Button>
201-
</Link>
202-
</TableCell>
203-
)}
204-
</TableRow>
205-
);
206-
})}
207-
</TableBody>
208-
</Table>
150+
<DataList
151+
data={data.invoices.data}
152+
getRowKey={(invoice) => invoice.id}
153+
columns={columns}
154+
actions={actions}
155+
/>
209156

210157
{data.pagination && (
211158
<Pagination

0 commit comments

Comments
 (0)