1+ import { isForbidden } from "@remnacore/shared" ;
12import type { ReactNode } from "react" ;
3+ import { useTranslation } from "react-i18next" ;
24
35export type Column < T > = {
46 key : string ;
@@ -14,6 +16,8 @@ export function DataTable<T>({
1416 rowKey,
1517 onRowClick,
1618 empty = "NO DATA" ,
19+ error,
20+ onRetry,
1721} : {
1822 columns : Column < T > [ ] ;
1923 rows : T [ ] ;
@@ -22,7 +26,12 @@ export function DataTable<T>({
2226 rowKey : ( r : T ) => string ;
2327 onRowClick ?: ( r : T ) => void ;
2428 empty ?: string ;
29+ /** Fetch error, if any. Rendered as a distinct state — NOT the empty state. */
30+ error ?: unknown ;
31+ /** Retry callback (e.g. react-query refetch); shown for non-403 errors. */
32+ onRetry ?: ( ) => void ;
2533} ) {
34+ const { t } = useTranslation ( ) ;
2635 const grid = { gridTemplateColumns : cols , gap : "14px" } as const ;
2736 return (
2837 < section className = "border border-line bg-surface" >
@@ -36,7 +45,28 @@ export function DataTable<T>({
3645 </ span >
3746 ) ) }
3847 </ div >
39- { rows . length === 0 ? (
48+ { error ? (
49+ < div className = "flex flex-col items-center gap-3 px-4 py-10 text-center" >
50+ < span
51+ className = { `text-[11px] uppercase tracking-[1.5px] ${
52+ isForbidden ( error ) ? "text-warn" : "text-danger"
53+ } `}
54+ >
55+ { isForbidden ( error )
56+ ? t ( "common.accessDenied" )
57+ : t ( "common.loadFailed" ) }
58+ </ span >
59+ { ! isForbidden ( error ) && onRetry && (
60+ < button
61+ type = "button"
62+ onClick = { onRetry }
63+ className = "border border-line px-3 py-1.5 text-[10px] uppercase tracking-[1px] text-t3 hover:bg-white/[.03]"
64+ >
65+ { t ( "common.retry" ) }
66+ </ button >
67+ ) }
68+ </ div >
69+ ) : rows . length === 0 ? (
4070 < div className = "px-4 py-10 text-center text-[11px] uppercase tracking-[2px] text-t7" >
4171 { empty }
4272 </ div >
0 commit comments