1"use client";
2
3
17import { useMemo } from "react";
18import { useRouter, usePathname } from "next/navigation";
19import { ExternalLink } from "lucide-react";
20import { cn } from "@/lib/utils";
21import {
22 DatabaseDto,
23 DatabaseView as ViewDto,
24 RecordDto,
25 UseDatabaseRecordsResult,
26 pageUrlFromPath,
27 useDatabase,
28 useDatabaseRecords
29} from "@/hooks/use-database";
30import { TableView } from "@/components/database/table-view";
31import { BoardView } from "@/components/database/board-view";
32import { GalleryView } from "@/components/database/gallery-view";
33import { ListView } from "@/components/database/list-view";
34import { CalendarView } from "@/components/database/calendar-view";
35import { TimelineView } from "@/components/database/timeline-view";
36import { ChartView } from "@/components/database/chart-view";
37import { FormView } from "@/components/form/form-view";
38
39export interface DatabaseViewProps {
40
41 embedded?: boolean;
42 databaseId: string;
43 viewId?: string;
44 className?: string;
45
46 limit?: number;
47}
48
49function ViewBody({
50 database,
51 view,
52 records,
53 onOpenRecord,
54 onOpenRecordPage
55}: {
56 database: DatabaseDto;
57 view: ViewDto;
58 records: UseDatabaseRecordsResult;
59 onOpenRecord: (record: RecordDto) => void;
60 onOpenRecordPage: (record: RecordDto) => void;
61}) {
62 const properties = database.properties;
63
64 switch (view.type) {
65 case "table":
66 return (
67 <TableView
68 databaseId={database.id}
69 view={view}
70 properties={properties}
71 records={records}
72 onOpenRecord={onOpenRecord}
73 onOpenRecordPage={onOpenRecordPage}
74 onSortChange={() => undefined}
75 onFilterChange={() => undefined}
76 />
77 );
78 case "board":
79 return <BoardView databaseId={database.id} view={view} properties={properties} records={records} onOpenRecord={onOpenRecord} />;
80 case "gallery":
81 return <GalleryView databaseId={database.id} view={view} properties={properties} records={records} onOpenRecord={onOpenRecord} />;
82 case "list":
83 return <ListView databaseId={database.id} view={view} properties={properties} records={records} onOpenRecord={onOpenRecord} />;
84 case "calendar":
85 return <CalendarView databaseId={database.id} view={view} properties={properties} records={records} onOpenRecord={onOpenRecord} />;
86 case "timeline":
87 return <TimelineView databaseId={database.id} view={view} properties={properties} records={records} onOpenRecord={onOpenRecord} />;
88 case "chart":
89 return <ChartView databaseId={database.id} view={view} properties={properties} records={records} />;
90 case "form":
91 return (
92 <FormView
93 databaseId={database.id}
94 workspaceId={database.workspaceId}
95 view={view}
96 properties={properties}
97 onOpenRecord={onOpenRecord}
98 onOpenRecordPage={onOpenRecordPage}
99 />
100 );
101 case "dashboard":
102 return <EmptyNote text="Dashboards compose multiple charts — available in a later phase." />;
103 default:
104 return (
105 <TableView
106 databaseId={database.id}
107 view={view}
108 properties={properties}
109 records={records}
110 onOpenRecord={onOpenRecord}
111 onOpenRecordPage={onOpenRecordPage}
112 onSortChange={() => undefined}
113 onFilterChange={() => undefined}
114 />
115 );
116 }
117}
118
119function EmptyNote({ text }: { text: string }) {
120 return (
121 <div className="flex h-full min-h-24 items-center justify-center p-4 text-center text-xs text-muted-foreground">
122 {text}
123 </div>
124 );
125}
126
127export function DatabaseView({ embedded, databaseId, viewId, className, limit = 20 }: DatabaseViewProps) {
128 const router = useRouter();
129 const pathname = usePathname();
130 const { data: database, isLoading, error } = useDatabase(databaseId);
131
132 const view: ViewDto | undefined = useMemo(() => {
133 if (!database) return undefined;
134 if (viewId) return database.views.find((v) => v.id === viewId);
135 return database.views[0];
136 }, [database, viewId]);
137
138 const records = useDatabaseRecords(databaseId, {
139 viewId: view?.id ?? viewId ?? null,
140 filter: view?.filter ?? null,
141 sort: view?.sort ?? null,
142 group: view?.group ?? null,
143 pageSize: limit,
144 enabled: !!view
145 });
146
147
148
149
150
151
152 const pageUrl = (pageId: string): string => pageUrlFromPath(pathname, pageId);
153 const onOpenRecordPage = (record: RecordDto) => {
154 if (!record.pageId) return;
155 router.push(pageUrl(record.pageId));
156 };
157 const onOpenRecord = (record: RecordDto) => onOpenRecordPage(record);
158
159 if (isLoading) {
160 return (
161 <div className={cn("min-h-32 animate-pulse rounded-lg border border-border bg-muted/40", className)} aria-busy="true" />
162 );
163 }
164
165 if (error || !database) {
166 return (
167 <div className={cn("rounded-lg border border-dashed p-4 text-xs text-muted-foreground", className)}>
168 This database is no longer available.
169 </div>
170 );
171 }
172
173 if (!view) {
174 return (
175 <div className={cn("rounded-lg border border-dashed p-4 text-xs text-muted-foreground", className)}>
176 This database has no views yet.
177 </div>
178 );
179 }
180
181 return (
182 <figure className={cn("overflow-hidden rounded-lg border border-border bg-background", className)}>
183 <figcaption className="flex items-center gap-2 border-b border-border bg-muted/40 px-3 py-1.5 text-xs text-muted-foreground">
184 <span className="font-medium text-foreground">{view.name}</span>
185 <span aria-hidden>·</span>
186 <span>{records.data.totalCount ?? records.data.records.length} records</span>
187 <span
188 className="ml-auto inline-flex cursor-pointer items-center gap-1 text-primary hover:underline"
189 role="link"
190 tabIndex={0}
191 title="Open the full database in a new window"
192 onClick={() => {
193
194
195 if (database.pageId) window.open(pageUrl(database.pageId), "_blank", "noopener,noreferrer");
196 }}
197 onKeyDown={(e) => {
198 if (e.key === "Enter" && database.pageId) window.open(pageUrl(database.pageId), "_blank", "noopener,noreferrer");
199 }}
200 >
201 Open
202 <ExternalLink className="size-3" aria-hidden />
203 </span>
204 </figcaption>
205 <div className={cn(embedded && "max-h-105 overflow-y-auto")}>
206 <ViewBody database={database} view={view} records={records} onOpenRecord={onOpenRecord} onOpenRecordPage={onOpenRecordPage} />
207 </div>
208 </figure>
209 );
210}
211