@dyrected/admin 2.5.36 → 2.5.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import { HashRouter, Link, MemoryRouter, Route, Routes, useLocation, useNavigate
5
5
  import { QueryClient, QueryClientProvider, useInfiniteQuery, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
6
6
  import { createClient } from "@dyrected/sdk";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
- import { AlertCircle, AlignCenter, AlignLeft, AlignRight, Archive, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, ArrowUpRight, Bold, BookOpen, Braces, Calendar, Check, CheckCircle, CheckCircle2, ChevronDown, ChevronDownIcon, ChevronLeft, ChevronLeftIcon, ChevronRight, ChevronRightIcon, ChevronUp, ChevronsUpDown, Circle, Clock, Clock3, Code, Code2, Compass, Copy, Database, ExternalLink, Eye, EyeOff, FileDown, FileIcon, FileText, Filter, Globe, Grid, GripVertical, Heading1, Heading2, Image as Image$1, Info, Italic, KeyRound, Layers, LayoutDashboard, Library, Link as Link$1, List, ListOrdered, Loader2, Lock, LogOut, Mail, Menu, Monitor, MoreHorizontal, PanelLeftClose, PanelLeftOpen, Pencil, Plus, Quote, RotateCcw, Save, Scissors, Search, Settings, Settings2, Share2, Shield, Smartphone, Sparkles, Strikethrough, Table, Trash2, Underline, Undo2, Upload, UploadCloud, Users, Video, Volume2, X, XCircle, icons } from "lucide-react";
8
+ import { AlertCircle, AlertTriangle, AlignCenter, AlignLeft, AlignRight, Archive, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, ArrowUpRight, Bold, BookOpen, Braces, Calendar, Check, CheckCircle, CheckCircle2, ChevronDown, ChevronDownIcon, ChevronLeft, ChevronLeftIcon, ChevronRight, ChevronRightIcon, ChevronUp, ChevronsUpDown, Circle, Clock, Clock3, Code, Code2, Compass, Copy, Database, Download, ExternalLink, Eye, EyeOff, FileDown, FileIcon, FileText, FileUp, Filter, Globe, GripVertical, Heading1, Heading2, Image as Image$1, Info, Italic, KeyRound, Layers, LayoutDashboard, Library, Link as Link$1, List, ListOrdered, Loader2, Lock, LogOut, Mail, Menu, Monitor, MoreHorizontal, PanelLeftClose, PanelLeftOpen, Pencil, Play, Plus, Quote, RotateCcw, Save, Scissors, Search, Settings, Settings2, Share2, Shield, Smartphone, Sparkles, Strikethrough, Table, Trash2, Underline, Undo2, Upload, UploadCloud, Users, Video, Volume2, X, XCircle, icons } from "lucide-react";
9
9
  import { clsx } from "clsx";
10
10
  import { extendTailwindMerge } from "tailwind-merge";
11
11
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
@@ -16,10 +16,16 @@ import * as PopoverPrimitive from "@radix-ui/react-popover";
16
16
  import * as SelectPrimitive from "@radix-ui/react-select";
17
17
  import { flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
18
18
  import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
19
+ import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
20
+ import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
21
+ import { CSS } from "@dnd-kit/utilities";
22
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
23
+ import Papa from "papaparse";
24
+ import * as ProgressPrimitive from "@radix-ui/react-progress";
25
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
19
26
  import jexl from "jexl";
20
27
  import { DataSheetGrid, checkboxColumn, keyColumn, textColumn } from "react-datasheet-grid";
21
28
  import "react-datasheet-grid/dist/style.css";
22
- import * as SheetPrimitive from "@radix-ui/react-dialog";
23
29
  import { EditorContent, useEditor } from "@tiptap/react";
24
30
  import StarterKit from "@tiptap/starter-kit";
25
31
  import TextAlign from "@tiptap/extension-text-align";
@@ -29,7 +35,6 @@ import Underline$1 from "@tiptap/extension-underline";
29
35
  import { TableKit } from "@tiptap/extension-table";
30
36
  import * as TogglePrimitive from "@radix-ui/react-toggle";
31
37
  import * as TabsPrimitive from "@radix-ui/react-tabs";
32
- import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
33
38
  import ReactCrop, { centerCrop, makeAspectCrop } from "react-image-crop";
34
39
  import "react-image-crop/dist/ReactCrop.css";
35
40
  import { useDropzone } from "react-dropzone";
@@ -42,11 +47,7 @@ import * as z from "zod";
42
47
  import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
43
48
  import { format } from "date-fns";
44
49
  import { DayPicker, getDefaultClassNames } from "react-day-picker";
45
- import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
46
- import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
47
- import { CSS } from "@dnd-kit/utilities";
48
50
  import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
49
- import * as ProgressPrimitive from "@radix-ui/react-progress";
50
51
  import * as SeparatorPrimitive from "@radix-ui/react-separator";
51
52
  import { Blurhash } from "react-blurhash";
52
53
  //#region src/providers/dyrected-provider.tsx
@@ -1796,7 +1797,7 @@ function usePreferences(key, defaultValue) {
1796
1797
  //#endregion
1797
1798
  //#region src/components/ui/data-table.tsx
1798
1799
  var EMPTY_VISIBILITY = {};
1799
- function DataTable({ columns, data, searchKey, rowSelection: externalRowSelection, onRowSelectionChange, bulkActions, toolbarActions, persistenceKey, initialColumnVisibility }) {
1800
+ function DataTable({ columns, data, searchKey, rowSelection: externalRowSelection, onRowSelectionChange, bulkActions, toolbarActions, persistenceKey, initialColumnVisibility, hideViewButton = false }) {
1800
1801
  const [sorting, setSorting] = React$1.useState([]);
1801
1802
  const [columnFilters, setColumnFilters] = React$1.useState([]);
1802
1803
  const [columnVisibility, setColumnVisibility] = usePreferences(persistenceKey ? `visibility_${persistenceKey}` : "temp_visibility", initialColumnVisibility ?? EMPTY_VISIBILITY);
@@ -1828,8 +1829,8 @@ function DataTable({ columns, data, searchKey, rowSelection: externalRowSelectio
1828
1829
  className: "dy-flex dy-flex-col dy-gap-3 sm:dy-flex-row sm:dy-items-center sm:dy-gap-4",
1829
1830
  children: [
1830
1831
  /* @__PURE__ */ jsxs("div", {
1831
- className: `dy-order-1 dy-grid dy-w-full dy-gap-2 sm:dy-order-3 sm:dy-ml-auto sm:dy-flex sm:dy-w-auto sm:dy-items-center ${toolbarActions ? "dy-grid-cols-3" : "dy-grid-cols-1"}`,
1832
- children: [toolbarActions, /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
1832
+ className: `dy-order-1 dy-grid dy-w-full dy-gap-2 sm:dy-order-3 sm:dy-ml-auto sm:dy-flex sm:dy-w-auto sm:dy-items-center ${toolbarActions ? hideViewButton ? "dy-grid-cols-2" : "dy-grid-cols-3" : hideViewButton ? "dy-grid-cols-1" : "dy-grid-cols-2"}`,
1833
+ children: [toolbarActions, !hideViewButton && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
1833
1834
  asChild: true,
1834
1835
  children: /* @__PURE__ */ jsxs(Button, {
1835
1836
  variant: "outline",
@@ -1943,6 +1944,621 @@ var Checkbox = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__
1943
1944
  }));
1944
1945
  Checkbox.displayName = CheckboxPrimitive.Root.displayName;
1945
1946
  //#endregion
1947
+ //#region src/components/ui/dialog.tsx
1948
+ var Dialog = DialogPrimitive.Root;
1949
+ var DialogTrigger = DialogPrimitive.Trigger;
1950
+ var DialogPortal = DialogPrimitive.Portal;
1951
+ var DialogOverlay = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Overlay, {
1952
+ ref,
1953
+ className: cn("dy-fixed dy-inset-0 dy-z-50 dy-bg-black/40 dy-backdrop-blur-[2px] data-[state=open]:dy-animate-in data-[state=closed]:dy-animate-out data-[state=closed]:dy-fade-out-0 data-[state=open]:dy-fade-in-0", className),
1954
+ ...props
1955
+ }));
1956
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
1957
+ var DialogContent = React$1.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsx(DialogPortal, { children: /* @__PURE__ */ jsxs("div", {
1958
+ className: "dy-admin-ui",
1959
+ children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
1960
+ ref,
1961
+ className: cn("dy-fixed dy-left-[50%] dy-top-[50%] dy-z-50 dy-grid dy-w-full dy-max-w-lg dy-translate-x-[-50%] dy-translate-y-[-50%] dy-gap-4 dy-border dy-bg-background dy-p-6 dy-shadow-2xl dy-duration-200 data-[state=open]:dy-animate-in data-[state=closed]:dy-animate-out data-[state=closed]:dy-fade-out-0 data-[state=open]:dy-fade-in-0 data-[state=closed]:dy-zoom-out-95 data-[state=open]:dy-zoom-in-95 data-[state=closed]:dy-slide-out-to-left-1/2 data-[state=closed]:dy-slide-out-to-top-[48%] data-[state=open]:dy-slide-in-from-left-1/2 data-[state=open]:dy-slide-in-from-top-[48%] sm:dy-rounded-xl", className),
1962
+ ...props,
1963
+ children: [children, /* @__PURE__ */ jsxs(DialogPrimitive.Close, {
1964
+ className: "dy-absolute dy-right-4 dy-top-4 dy-rounded-sm dy-opacity-70 dy-ring-offset-background dy-transition-opacity hover:dy-opacity-100 focus:dy-outline-none focus:dy-ring-2 focus:dy-ring-ring focus:dy-ring-offset-2 disabled:dy-pointer-events-none data-[state=open]:dy-bg-accent data-[state=open]:dy-text-muted-foreground",
1965
+ children: [/* @__PURE__ */ jsx(X, { className: "dy-h-4 dy-w-4" }), /* @__PURE__ */ jsx("span", {
1966
+ className: "dy-sr-only",
1967
+ children: "Close"
1968
+ })]
1969
+ })]
1970
+ })]
1971
+ }) }));
1972
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
1973
+ var DialogHeader = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
1974
+ className: cn("dy-flex dy-flex-col dy-space-y-1.5 dy-text-center sm:dy-text-left", className),
1975
+ ...props
1976
+ });
1977
+ DialogHeader.displayName = "DialogHeader";
1978
+ var DialogFooter = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
1979
+ className: cn("dy-flex dy-flex-col-reverse sm:dy-flex-row sm:dy-justify-end sm:dy-space-x-2", className),
1980
+ ...props
1981
+ });
1982
+ DialogFooter.displayName = "DialogFooter";
1983
+ var DialogTitle = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Title, {
1984
+ ref,
1985
+ className: cn("dy-text-lg dy-font-semibold dy-leading-none dy-tracking-tight", className),
1986
+ ...props
1987
+ }));
1988
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
1989
+ var DialogDescription = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Description, {
1990
+ ref,
1991
+ className: cn("dy-text-sm dy-text-muted-foreground", className),
1992
+ ...props
1993
+ }));
1994
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
1995
+ //#endregion
1996
+ //#region src/components/ui/progress.tsx
1997
+ var Progress = React$1.forwardRef(({ className, value, ...props }, ref) => /* @__PURE__ */ jsx(ProgressPrimitive.Root, {
1998
+ ref,
1999
+ className: cn("dy-relative dy-h-4 dy-w-full dy-overflow-hidden dy-rounded-full dy-bg-secondary", className),
2000
+ ...props,
2001
+ children: /* @__PURE__ */ jsx(ProgressPrimitive.Indicator, {
2002
+ className: "dy-h-full dy-w-full dy-flex-1 dy-bg-primary dy-transition-all",
2003
+ style: { transform: `translateX(-${100 - (value || 0)}%)` }
2004
+ })
2005
+ }));
2006
+ Progress.displayName = ProgressPrimitive.Root.displayName;
2007
+ //#endregion
2008
+ //#region src/components/ui/scroll-area.tsx
2009
+ var ScrollArea = React$1.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(ScrollAreaPrimitive.Root, {
2010
+ ref,
2011
+ className: cn("dy-relative dy-overflow-hidden", className),
2012
+ ...props,
2013
+ children: [
2014
+ /* @__PURE__ */ jsx(ScrollAreaPrimitive.Viewport, {
2015
+ className: "dy-h-full dy-w-full dy-rounded-[inherit]",
2016
+ children
2017
+ }),
2018
+ /* @__PURE__ */ jsx(ScrollBar, {}),
2019
+ /* @__PURE__ */ jsx(ScrollAreaPrimitive.Corner, {})
2020
+ ]
2021
+ }));
2022
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
2023
+ var ScrollBar = React$1.forwardRef(({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsx(ScrollAreaPrimitive.ScrollAreaScrollbar, {
2024
+ ref,
2025
+ orientation,
2026
+ className: cn("dy-flex dy-touch-none dy-select-none dy-transition-colors", orientation === "vertical" && "dy-h-full dy-w-2.5 dy-border-l dy-border-l-transparent dy-p-[1px]", orientation === "horizontal" && "dy-h-2.5 dy-flex-col dy-border-t dy-border-t-transparent dy-p-[1px]", className),
2027
+ ...props,
2028
+ children: /* @__PURE__ */ jsx(ScrollAreaPrimitive.ScrollAreaThumb, { className: "dy-relative dy-flex-1 dy-rounded-full dy-bg-border" })
2029
+ }));
2030
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
2031
+ //#endregion
2032
+ //#region src/components/ui/csv-importer.tsx
2033
+ function CsvImporter({ slug, schema, onClose }) {
2034
+ const { client } = useDyrected();
2035
+ const queryClient = useQueryClient();
2036
+ const [step, setStep] = React$1.useState("upload");
2037
+ const [csvHeaders, setCsvHeaders] = React$1.useState([]);
2038
+ const [parsedRows, setParsedRows] = React$1.useState([]);
2039
+ const [mapping, setMapping] = React$1.useState({});
2040
+ const [validatedData, setValidatedData] = React$1.useState([]);
2041
+ const [totalRows, setTotalRows] = React$1.useState(0);
2042
+ const [processedCount, setProcessedCount] = React$1.useState(0);
2043
+ const [successCount, setSuccessCount] = React$1.useState(0);
2044
+ const [failedRows, setFailedRows] = React$1.useState([]);
2045
+ const importableFields = React$1.useMemo(() => {
2046
+ return schema.fields.filter((f) => f.name !== "id" && f.name !== "createdAt" && f.name !== "updatedAt" && f.type !== "row" && f.type !== "join" && !f.admin?.hidden);
2047
+ }, [schema]);
2048
+ const requiredFields = React$1.useMemo(() => {
2049
+ return importableFields.filter((f) => f.required);
2050
+ }, [importableFields]);
2051
+ const handleFileChange = (e) => {
2052
+ const selectedFile = e.target.files?.[0];
2053
+ if (!selectedFile) return;
2054
+ Papa.parse(selectedFile, {
2055
+ header: true,
2056
+ skipEmptyLines: "greedy",
2057
+ complete: (results) => {
2058
+ const headers = results.meta.fields || [];
2059
+ setCsvHeaders(headers);
2060
+ setParsedRows(results.data);
2061
+ const initialMapping = {};
2062
+ headers.forEach((header) => {
2063
+ const match = importableFields.find((f) => f.name.toLowerCase() === header.toLowerCase() || f.label?.toLowerCase() === header.toLowerCase());
2064
+ initialMapping[header] = match ? match.name : "__ignore__";
2065
+ });
2066
+ setMapping(initialMapping);
2067
+ setStep("map");
2068
+ },
2069
+ error: (error) => {
2070
+ console.error("PapaParse error:", error);
2071
+ }
2072
+ });
2073
+ };
2074
+ const handleMapChange = (header, fieldName) => {
2075
+ setMapping((prev) => ({
2076
+ ...prev,
2077
+ [header]: fieldName
2078
+ }));
2079
+ };
2080
+ const isMapValid = React$1.useMemo(() => {
2081
+ const mappedFields = Object.values(mapping);
2082
+ return requiredFields.every((f) => mappedFields.includes(f.name));
2083
+ }, [requiredFields, mapping]);
2084
+ const coerceAndValidate = (val, field) => {
2085
+ if (val === void 0 || val === null || String(val).trim() === "") {
2086
+ if (field.required) return {
2087
+ value: null,
2088
+ error: `${field.label || field.name} is required`
2089
+ };
2090
+ return { value: null };
2091
+ }
2092
+ const strVal = String(val).trim();
2093
+ switch (field.type) {
2094
+ case "number": {
2095
+ const num = Number(strVal);
2096
+ if (isNaN(num)) return {
2097
+ value: null,
2098
+ error: "Must be a valid number"
2099
+ };
2100
+ return { value: num };
2101
+ }
2102
+ case "boolean": {
2103
+ const lower = strVal.toLowerCase();
2104
+ if (lower === "true" || lower === "yes" || lower === "1") return { value: true };
2105
+ if (lower === "false" || lower === "no" || lower === "0") return { value: false };
2106
+ return {
2107
+ value: null,
2108
+ error: "Must be a boolean (true/false, yes/no, 1/0)"
2109
+ };
2110
+ }
2111
+ case "date":
2112
+ case "datetime": {
2113
+ const date = new Date(strVal);
2114
+ if (isNaN(date.getTime())) return {
2115
+ value: null,
2116
+ error: "Must be a valid date"
2117
+ };
2118
+ return { value: date.toISOString() };
2119
+ }
2120
+ case "select":
2121
+ case "radio":
2122
+ if (field.options && Array.isArray(field.options) && field.options.length > 0) {
2123
+ const found = field.options.find((opt) => {
2124
+ const optVal = typeof opt === "string" ? opt : opt.value;
2125
+ return String(optVal).toLowerCase() === strVal.toLowerCase();
2126
+ });
2127
+ if (!found) return {
2128
+ value: null,
2129
+ error: `Must be one of: ${field.options.map((opt) => typeof opt === "string" ? opt : opt.label || opt.value).join(", ")}`
2130
+ };
2131
+ return { value: typeof found === "string" ? found : found.value };
2132
+ }
2133
+ return { value: strVal };
2134
+ default: return { value: strVal };
2135
+ }
2136
+ };
2137
+ const handleProceedToPreview = () => {
2138
+ setValidatedData(parsedRows.map((row, idx) => {
2139
+ const coercedRow = {};
2140
+ const errors = {};
2141
+ let isValid = true;
2142
+ Object.entries(mapping).forEach(([csvHeader, targetField]) => {
2143
+ if (!targetField || targetField === "__ignore__") return;
2144
+ const fieldSchema = importableFields.find((f) => f.name === targetField);
2145
+ if (!fieldSchema) return;
2146
+ const rawValue = row[csvHeader];
2147
+ const { value, error } = coerceAndValidate(rawValue, fieldSchema);
2148
+ if (error) {
2149
+ errors[targetField] = error;
2150
+ isValid = false;
2151
+ } else coercedRow[targetField] = value;
2152
+ });
2153
+ requiredFields.forEach((field) => {
2154
+ if (!Object.prototype.hasOwnProperty.call(coercedRow, field.name) || coercedRow[field.name] === null) {
2155
+ errors[field.name] = `${field.label || field.name} is required`;
2156
+ isValid = false;
2157
+ }
2158
+ });
2159
+ return {
2160
+ rowNumber: idx + 1,
2161
+ data: coercedRow,
2162
+ errors,
2163
+ isValid
2164
+ };
2165
+ }));
2166
+ setStep("preview");
2167
+ };
2168
+ const startImport = async () => {
2169
+ setStep("importing");
2170
+ setTotalRows(validatedData.length);
2171
+ setProcessedCount(0);
2172
+ setSuccessCount(0);
2173
+ setFailedRows([]);
2174
+ let success = 0;
2175
+ const failures = [];
2176
+ for (let i = 0; i < validatedData.length; i++) {
2177
+ const rowResult = validatedData[i];
2178
+ setProcessedCount(i + 1);
2179
+ if (!rowResult.isValid) {
2180
+ failures.push({
2181
+ row: rowResult.rowNumber,
2182
+ data: rowResult.data,
2183
+ error: `Validation error: ${Object.values(rowResult.errors).join(", ")}`
2184
+ });
2185
+ setFailedRows([...failures]);
2186
+ continue;
2187
+ }
2188
+ try {
2189
+ await client.collection(slug).create(rowResult.data);
2190
+ success++;
2191
+ setSuccessCount(success);
2192
+ } catch (error) {
2193
+ failures.push({
2194
+ row: rowResult.rowNumber,
2195
+ data: rowResult.data,
2196
+ error: error.message || "Failed to create entry"
2197
+ });
2198
+ setFailedRows([...failures]);
2199
+ }
2200
+ }
2201
+ queryClient.invalidateQueries({ queryKey: ["collection", slug] });
2202
+ setStep("complete");
2203
+ };
2204
+ const downloadFailedCsv = () => {
2205
+ if (failedRows.length === 0) return;
2206
+ const headers = [...csvHeaders, "Import Error"];
2207
+ const rows = failedRows.map((f) => {
2208
+ return {
2209
+ ...parsedRows[f.row - 1],
2210
+ "Import Error": f.error
2211
+ };
2212
+ });
2213
+ const csvContent = Papa.unparse({
2214
+ fields: headers,
2215
+ data: rows
2216
+ });
2217
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
2218
+ const url = URL.createObjectURL(blob);
2219
+ const link = document.createElement("a");
2220
+ link.setAttribute("href", url);
2221
+ link.setAttribute("download", `${slug}_failed_imports.csv`);
2222
+ document.body.appendChild(link);
2223
+ link.click();
2224
+ document.body.removeChild(link);
2225
+ };
2226
+ const previewRows = React$1.useMemo(() => {
2227
+ return validatedData.slice(0, 100);
2228
+ }, [validatedData]);
2229
+ const totalErrors = React$1.useMemo(() => {
2230
+ return validatedData.filter((r) => !r.isValid).length;
2231
+ }, [validatedData]);
2232
+ return /* @__PURE__ */ jsxs("div", {
2233
+ className: "dy-space-y-6",
2234
+ children: [
2235
+ step === "upload" && /* @__PURE__ */ jsx("div", {
2236
+ className: "dy-space-y-4",
2237
+ children: /* @__PURE__ */ jsx("div", {
2238
+ className: "dy-text-center dy-py-10 dy-border-2 dy-border-dashed dy-border-border dy-rounded-xl hover:dy-bg-muted/30 dy-transition-colors",
2239
+ children: /* @__PURE__ */ jsxs("label", {
2240
+ className: "dy-cursor-pointer dy-block dy-space-y-4 dy-px-6",
2241
+ children: [
2242
+ /* @__PURE__ */ jsx(Upload, { className: "dy-h-10 dy-w-10 dy-mx-auto dy-text-muted-foreground" }),
2243
+ /* @__PURE__ */ jsxs("div", {
2244
+ className: "dy-space-y-1",
2245
+ children: [/* @__PURE__ */ jsx("p", {
2246
+ className: "dy-text-sm dy-font-semibold",
2247
+ children: "Click to upload or drag & drop"
2248
+ }), /* @__PURE__ */ jsx("p", {
2249
+ className: "dy-text-xs dy-text-muted-foreground",
2250
+ children: "CSV files only"
2251
+ })]
2252
+ }),
2253
+ /* @__PURE__ */ jsx("input", {
2254
+ type: "file",
2255
+ accept: ".csv",
2256
+ className: "dy-hidden",
2257
+ onChange: handleFileChange
2258
+ })
2259
+ ]
2260
+ })
2261
+ })
2262
+ }),
2263
+ step === "map" && /* @__PURE__ */ jsxs("div", {
2264
+ className: "dy-space-y-4",
2265
+ children: [
2266
+ /* @__PURE__ */ jsx("div", {
2267
+ className: "dy-p-4 dy-bg-muted/40 dy-border dy-border-border dy-rounded-xl dy-text-sm dy-text-muted-foreground",
2268
+ children: "Map the columns in your CSV file to the corresponding fields of the collection. All required fields must be mapped to proceed."
2269
+ }),
2270
+ /* @__PURE__ */ jsx(ScrollArea, {
2271
+ className: "dy-h-[300px] dy-pr-4 dy-border dy-border-border dy-rounded-xl",
2272
+ children: /* @__PURE__ */ jsx("div", {
2273
+ className: "dy-divide-y dy-divide-border",
2274
+ children: csvHeaders.map((header) => {
2275
+ const mappedVal = mapping[header] ?? "__ignore__";
2276
+ return /* @__PURE__ */ jsxs("div", {
2277
+ className: "dy-flex dy-items-center dy-justify-between dy-p-3.5",
2278
+ children: [/* @__PURE__ */ jsxs("div", {
2279
+ className: "dy-space-y-1 dy-flex-1 dy-pr-4",
2280
+ children: [/* @__PURE__ */ jsx("p", {
2281
+ className: "dy-text-sm dy-font-semibold dy-text-foreground",
2282
+ children: header
2283
+ }), parsedRows[0] && /* @__PURE__ */ jsxs("p", {
2284
+ className: "dy-text-xs dy-text-muted-foreground dy-truncate",
2285
+ children: ["Sample: ", parsedRows[0][header]]
2286
+ })]
2287
+ }), /* @__PURE__ */ jsx("div", {
2288
+ className: "dy-w-[200px]",
2289
+ children: /* @__PURE__ */ jsxs(Select, {
2290
+ value: mappedVal,
2291
+ onValueChange: (val) => handleMapChange(header, val),
2292
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
2293
+ className: "dy-w-full",
2294
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Ignore field" })
2295
+ }), /* @__PURE__ */ jsxs(SelectContent, { children: [/* @__PURE__ */ jsx(SelectItem, {
2296
+ value: "__ignore__",
2297
+ children: "Ignore field"
2298
+ }), importableFields.map((field) => /* @__PURE__ */ jsxs(SelectItem, {
2299
+ value: field.name,
2300
+ children: [field.required ? "* " : "", field.label || field.name]
2301
+ }, field.name))] })]
2302
+ })
2303
+ })]
2304
+ }, header);
2305
+ })
2306
+ })
2307
+ }),
2308
+ requiredFields.length > 0 && /* @__PURE__ */ jsxs("div", {
2309
+ className: "dy-space-y-2",
2310
+ children: [/* @__PURE__ */ jsx("p", {
2311
+ className: "dy-text-xs dy-font-semibold dy-text-muted-foreground",
2312
+ children: "Required Fields:"
2313
+ }), /* @__PURE__ */ jsx("div", {
2314
+ className: "dy-flex dy-flex-wrap dy-gap-2",
2315
+ children: requiredFields.map((f) => {
2316
+ const isMapped = Object.values(mapping).includes(f.name);
2317
+ return /* @__PURE__ */ jsxs(Badge, {
2318
+ variant: isMapped ? "secondary" : "destructive",
2319
+ className: "dy-rounded-md",
2320
+ children: [isMapped ? "✓ " : "✗ ", f.label || f.name]
2321
+ }, f.name);
2322
+ })
2323
+ })]
2324
+ }),
2325
+ /* @__PURE__ */ jsxs("div", {
2326
+ className: "dy-flex dy-justify-between dy-pt-4",
2327
+ children: [/* @__PURE__ */ jsx(Button, {
2328
+ variant: "outline",
2329
+ onClick: () => setStep("upload"),
2330
+ children: "Back"
2331
+ }), /* @__PURE__ */ jsxs(Button, {
2332
+ onClick: handleProceedToPreview,
2333
+ disabled: !isMapValid,
2334
+ children: ["Preview Validation ", /* @__PURE__ */ jsx(ArrowRight, { className: "dy-h-4 dy-w-4 dy-ml-2" })]
2335
+ })]
2336
+ })
2337
+ ]
2338
+ }),
2339
+ step === "preview" && /* @__PURE__ */ jsxs("div", {
2340
+ className: "dy-space-y-4",
2341
+ children: [
2342
+ /* @__PURE__ */ jsxs("div", {
2343
+ className: "dy-flex dy-items-center dy-justify-between",
2344
+ children: [/* @__PURE__ */ jsxs("div", {
2345
+ className: "dy-space-y-1",
2346
+ children: [/* @__PURE__ */ jsx("h3", {
2347
+ className: "dy-text-sm dy-font-semibold",
2348
+ children: "Validation Preview"
2349
+ }), /* @__PURE__ */ jsxs("p", {
2350
+ className: "dy-text-xs dy-text-muted-foreground",
2351
+ children: ["Showing the first 100 rows. ", totalErrors > 0 ? `${totalErrors} rows have errors and will be skipped.` : "All rows are valid."]
2352
+ })]
2353
+ }), totalErrors > 0 && /* @__PURE__ */ jsxs(Badge, {
2354
+ variant: "destructive",
2355
+ className: "dy-rounded-md dy-gap-1.5",
2356
+ children: [
2357
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "dy-h-3.5 dy-w-3.5" }),
2358
+ " ",
2359
+ totalErrors,
2360
+ " Invalid"
2361
+ ]
2362
+ })]
2363
+ }),
2364
+ /* @__PURE__ */ jsx(ScrollArea, {
2365
+ className: "dy-h-[300px] dy-border dy-border-border dy-rounded-xl",
2366
+ children: /* @__PURE__ */ jsxs("table", {
2367
+ className: "dy-w-full dy-text-left dy-border-collapse",
2368
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
2369
+ className: "dy-border-b dy-border-border dy-bg-muted/40 dy-text-xs dy-font-semibold dy-text-muted-foreground",
2370
+ children: [/* @__PURE__ */ jsx("th", {
2371
+ className: "dy-p-3 dy-w-16",
2372
+ children: "Row"
2373
+ }), importableFields.filter((f) => Object.values(mapping).includes(f.name)).map((f) => /* @__PURE__ */ jsx("th", {
2374
+ className: "dy-p-3",
2375
+ children: f.label || f.name
2376
+ }, f.name))]
2377
+ }) }), /* @__PURE__ */ jsx("tbody", {
2378
+ className: "dy-text-sm",
2379
+ children: previewRows.map((rowResult) => /* @__PURE__ */ jsxs("tr", {
2380
+ className: "dy-border-b dy-border-border hover:dy-bg-muted/20",
2381
+ children: [/* @__PURE__ */ jsx("td", {
2382
+ className: "dy-p-3 dy-font-medium dy-text-muted-foreground",
2383
+ children: rowResult.rowNumber
2384
+ }), importableFields.filter((f) => Object.values(mapping).includes(f.name)).map((f) => {
2385
+ const val = rowResult.data[f.name];
2386
+ const error = rowResult.errors[f.name];
2387
+ return /* @__PURE__ */ jsx("td", {
2388
+ className: "dy-p-3",
2389
+ children: error ? /* @__PURE__ */ jsxs("span", {
2390
+ className: "dy-flex dy-items-center dy-gap-1.5 dy-text-destructive dy-font-medium",
2391
+ children: [/* @__PURE__ */ jsx(AlertCircle, { className: "dy-h-3.5 dy-w-3.5" }), error]
2392
+ }) : String(val ?? "")
2393
+ }, f.name);
2394
+ })]
2395
+ }, rowResult.rowNumber))
2396
+ })]
2397
+ })
2398
+ }),
2399
+ /* @__PURE__ */ jsxs("div", {
2400
+ className: "dy-flex dy-justify-between dy-pt-4",
2401
+ children: [/* @__PURE__ */ jsx(Button, {
2402
+ variant: "outline",
2403
+ onClick: () => setStep("map"),
2404
+ children: "Back"
2405
+ }), /* @__PURE__ */ jsxs(Button, {
2406
+ onClick: startImport,
2407
+ variant: "default",
2408
+ className: "dy-gap-2",
2409
+ children: [
2410
+ /* @__PURE__ */ jsx(Play, { className: "dy-h-4 dy-w-4" }),
2411
+ " Start Import (",
2412
+ validatedData.length - totalErrors,
2413
+ " Rows)"
2414
+ ]
2415
+ })]
2416
+ })
2417
+ ]
2418
+ }),
2419
+ step === "importing" && /* @__PURE__ */ jsxs("div", {
2420
+ className: "dy-space-y-6 dy-py-6",
2421
+ children: [/* @__PURE__ */ jsxs("div", {
2422
+ className: "dy-space-y-2",
2423
+ children: [/* @__PURE__ */ jsxs("div", {
2424
+ className: "dy-flex dy-items-center dy-justify-between dy-text-sm dy-font-semibold",
2425
+ children: [/* @__PURE__ */ jsx("span", { children: "Importing rows..." }), /* @__PURE__ */ jsxs("span", { children: [
2426
+ processedCount,
2427
+ " / ",
2428
+ totalRows
2429
+ ] })]
2430
+ }), /* @__PURE__ */ jsx(Progress, {
2431
+ value: processedCount / totalRows * 100,
2432
+ className: "dy-h-2"
2433
+ })]
2434
+ }), /* @__PURE__ */ jsxs("div", {
2435
+ className: "dy-grid dy-grid-cols-2 dy-gap-4",
2436
+ children: [/* @__PURE__ */ jsxs("div", {
2437
+ className: "dy-p-4 dy-bg-muted/30 dy-border dy-border-border dy-rounded-xl dy-text-center",
2438
+ children: [/* @__PURE__ */ jsx("p", {
2439
+ className: "dy-text-2xl dy-font-bold dy-text-primary",
2440
+ children: successCount
2441
+ }), /* @__PURE__ */ jsx("p", {
2442
+ className: "dy-text-xs dy-text-muted-foreground",
2443
+ children: "Successful"
2444
+ })]
2445
+ }), /* @__PURE__ */ jsxs("div", {
2446
+ className: "dy-p-4 dy-bg-muted/30 dy-border dy-border-border dy-rounded-xl dy-text-center",
2447
+ children: [/* @__PURE__ */ jsx("p", {
2448
+ className: "dy-text-2xl dy-font-bold dy-text-destructive",
2449
+ children: failedRows.length
2450
+ }), /* @__PURE__ */ jsx("p", {
2451
+ className: "dy-text-xs dy-text-muted-foreground",
2452
+ children: "Failed/Skipped"
2453
+ })]
2454
+ })]
2455
+ })]
2456
+ }),
2457
+ step === "complete" && /* @__PURE__ */ jsxs("div", {
2458
+ className: "dy-space-y-6",
2459
+ children: [
2460
+ /* @__PURE__ */ jsxs("div", {
2461
+ className: "dy-flex dy-flex-col dy-items-center dy-text-center dy-space-y-3 dy-py-6",
2462
+ children: [
2463
+ /* @__PURE__ */ jsx(CheckCircle2, { className: "dy-h-12 dy-w-12 dy-text-green-500" }),
2464
+ /* @__PURE__ */ jsx("h3", {
2465
+ className: "dy-text-lg dy-font-semibold",
2466
+ children: "Import Process Completed"
2467
+ }),
2468
+ /* @__PURE__ */ jsxs("p", {
2469
+ className: "dy-text-sm dy-text-muted-foreground dy-max-w-md",
2470
+ children: [
2471
+ "Successfully imported ",
2472
+ successCount,
2473
+ " rows. ",
2474
+ failedRows.length,
2475
+ " rows failed or were skipped due to validation/server errors."
2476
+ ]
2477
+ })
2478
+ ]
2479
+ }),
2480
+ failedRows.length > 0 && /* @__PURE__ */ jsxs("div", {
2481
+ className: "dy-space-y-3",
2482
+ children: [/* @__PURE__ */ jsxs("div", {
2483
+ className: "dy-flex dy-items-center dy-justify-between",
2484
+ children: [/* @__PURE__ */ jsxs("p", {
2485
+ className: "dy-text-xs dy-font-bold dy-text-destructive uppercase tracking-wide",
2486
+ children: [
2487
+ "Failure Logs (",
2488
+ failedRows.length,
2489
+ " rows)"
2490
+ ]
2491
+ }), /* @__PURE__ */ jsxs(Button, {
2492
+ size: "sm",
2493
+ variant: "outline",
2494
+ className: "dy-h-8 dy-text-xs dy-gap-1.5",
2495
+ onClick: downloadFailedCsv,
2496
+ children: [/* @__PURE__ */ jsx(Download, { className: "dy-h-3.5 dy-w-3.5" }), " Download Errors CSV"]
2497
+ })]
2498
+ }), /* @__PURE__ */ jsx(ScrollArea, {
2499
+ className: "dy-h-[180px] dy-border dy-border-border dy-rounded-xl",
2500
+ children: /* @__PURE__ */ jsx("div", {
2501
+ className: "dy-divide-y dy-divide-border",
2502
+ children: failedRows.map((f, idx) => /* @__PURE__ */ jsxs("div", {
2503
+ className: "dy-flex dy-items-start dy-gap-3 dy-p-3 dy-text-xs",
2504
+ children: [/* @__PURE__ */ jsxs(Badge, {
2505
+ variant: "destructive",
2506
+ className: "dy-rounded dy-shrink-0",
2507
+ children: ["Row ", f.row]
2508
+ }), /* @__PURE__ */ jsx("p", {
2509
+ className: "dy-text-muted-foreground font-medium dy-pt-0.5",
2510
+ children: f.error
2511
+ })]
2512
+ }, idx))
2513
+ })
2514
+ })]
2515
+ }),
2516
+ /* @__PURE__ */ jsxs("div", {
2517
+ className: "dy-flex dy-justify-end dy-gap-2 dy-pt-4",
2518
+ children: [/* @__PURE__ */ jsxs(Button, {
2519
+ variant: "outline",
2520
+ className: "dy-gap-2",
2521
+ onClick: () => {
2522
+ setCsvHeaders([]);
2523
+ setParsedRows([]);
2524
+ setMapping({});
2525
+ setValidatedData([]);
2526
+ setFailedRows([]);
2527
+ setStep("upload");
2528
+ },
2529
+ children: [/* @__PURE__ */ jsx(RotateCcw, { className: "dy-h-4 dy-w-4" }), " Import Another File"]
2530
+ }), /* @__PURE__ */ jsx(Button, {
2531
+ onClick: onClose,
2532
+ children: "Close Importer"
2533
+ })]
2534
+ })
2535
+ ]
2536
+ })
2537
+ ]
2538
+ });
2539
+ }
2540
+ //#endregion
2541
+ //#region src/components/ui/csv-importer-dialog.tsx
2542
+ function CsvImporterDialog({ open, onOpenChange, slug, schema }) {
2543
+ const title = schema.labels?.singular || schema.slug || slug;
2544
+ return /* @__PURE__ */ jsx(Dialog, {
2545
+ open,
2546
+ onOpenChange,
2547
+ children: /* @__PURE__ */ jsxs(DialogContent, {
2548
+ className: "sm:dy-max-w-2xl",
2549
+ children: [/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: "Import CSV Data" }), /* @__PURE__ */ jsxs(DialogDescription, { children: [
2550
+ "Import records into the ",
2551
+ title,
2552
+ " collection."
2553
+ ] })] }), /* @__PURE__ */ jsx(CsvImporter, {
2554
+ slug,
2555
+ schema,
2556
+ onClose: () => onOpenChange(false)
2557
+ })]
2558
+ })
2559
+ });
2560
+ }
2561
+ //#endregion
1946
2562
  //#region src/components/ui/render-cell.tsx
1947
2563
  function RenderCell({ value, field, client, schemas }) {
1948
2564
  if (value === null || value === void 0) return /* @__PURE__ */ jsx("span", {
@@ -2158,14 +2774,14 @@ function MediaGrid({ items, baseUrl, onDelete, slug }) {
2158
2774
  }
2159
2775
  //#endregion
2160
2776
  //#region src/components/ui/sheet.tsx
2161
- var Sheet = SheetPrimitive.Root;
2162
- var SheetPortal = SheetPrimitive.Portal;
2163
- var SheetOverlay = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Overlay, {
2777
+ var Sheet = DialogPrimitive.Root;
2778
+ var SheetPortal = DialogPrimitive.Portal;
2779
+ var SheetOverlay = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Overlay, {
2164
2780
  className: cn("dy-fixed dy-inset-0 dy-z-50 dy-bg-black/40 dy-backdrop-blur-[2px] data-[state=open]:dy-animate-in data-[state=closed]:dy-animate-out data-[state=closed]:dy-fade-out-0 data-[state=open]:dy-fade-in-0", className),
2165
2781
  ...props,
2166
2782
  ref
2167
2783
  }));
2168
- SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
2784
+ SheetOverlay.displayName = DialogPrimitive.Overlay.displayName;
2169
2785
  var sheetVariants = cva("dy-fixed dy-z-50 dy-gap-4 dy-bg-background dy-p-6 dy-shadow-2xl dy-transition dy-ease-in-out data-[state=open]:dy-animate-in data-[state=closed]:dy-animate-out data-[state=closed]:dy-duration-300 data-[state=open]:dy-duration-500", {
2170
2786
  variants: { side: {
2171
2787
  top: "dy-inset-x-0 dy-top-0 dy-border-b data-[state=closed]:dy-slide-out-to-top data-[state=open]:dy-slide-in-from-top",
@@ -2177,11 +2793,11 @@ var sheetVariants = cva("dy-fixed dy-z-50 dy-gap-4 dy-bg-background dy-p-6 dy-sh
2177
2793
  });
2178
2794
  var SheetContent = React$1.forwardRef(({ side = "right", className, children, ...props }, ref) => /* @__PURE__ */ jsx(SheetPortal, { children: /* @__PURE__ */ jsxs("div", {
2179
2795
  className: "dy-admin-ui",
2180
- children: [/* @__PURE__ */ jsx(SheetOverlay, {}), /* @__PURE__ */ jsxs(SheetPrimitive.Content, {
2796
+ children: [/* @__PURE__ */ jsx(SheetOverlay, {}), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
2181
2797
  ref,
2182
2798
  className: cn(sheetVariants({ side }), className),
2183
2799
  ...props,
2184
- children: [children, /* @__PURE__ */ jsxs(SheetPrimitive.Close, {
2800
+ children: [children, /* @__PURE__ */ jsxs(DialogPrimitive.Close, {
2185
2801
  className: "dy-absolute dy-right-4 dy-top-4 dy-rounded-sm dy-opacity-70 dy-ring-offset-background dy-transition-opacity hover:dy-opacity-100 focus:dy-outline-none focus:dy-ring-2 focus:dy-ring-ring focus:dy-ring-offset-2 disabled:dy-pointer-events-none data-[state=open]:dy-bg-secondary",
2186
2802
  children: [/* @__PURE__ */ jsx(X, { className: "dy-h-4 dy-w-4" }), /* @__PURE__ */ jsx("span", {
2187
2803
  className: "dy-sr-only",
@@ -2190,7 +2806,7 @@ var SheetContent = React$1.forwardRef(({ side = "right", className, children, ..
2190
2806
  })]
2191
2807
  })]
2192
2808
  }) }));
2193
- SheetContent.displayName = SheetPrimitive.Content.displayName;
2809
+ SheetContent.displayName = DialogPrimitive.Content.displayName;
2194
2810
  var SheetHeader = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
2195
2811
  className: cn("dy-flex dy-flex-col dy-space-y-2 dy-text-center sm:dy-text-left", className),
2196
2812
  ...props
@@ -2201,18 +2817,18 @@ var SheetFooter = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
2201
2817
  ...props
2202
2818
  });
2203
2819
  SheetFooter.displayName = "SheetFooter";
2204
- var SheetTitle = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Title, {
2820
+ var SheetTitle = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Title, {
2205
2821
  ref,
2206
2822
  className: cn("dy-text-lg dy-font-semibold dy-text-foreground", className),
2207
2823
  ...props
2208
2824
  }));
2209
- SheetTitle.displayName = SheetPrimitive.Title.displayName;
2210
- var SheetDescription = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Description, {
2825
+ SheetTitle.displayName = DialogPrimitive.Title.displayName;
2826
+ var SheetDescription = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Description, {
2211
2827
  ref,
2212
2828
  className: cn("dy-text-sm dy-text-muted-foreground", className),
2213
2829
  ...props
2214
2830
  }));
2215
- SheetDescription.displayName = SheetPrimitive.Description.displayName;
2831
+ SheetDescription.displayName = DialogPrimitive.Description.displayName;
2216
2832
  //#endregion
2217
2833
  //#region src/components/ui/toggle.tsx
2218
2834
  var toggleVariants = cva("dy-inline-flex dy-items-center dy-justify-center dy-rounded-md dy-text-sm dy-font-medium dy-ring-offset-background dy-transition-colors hover:dy-bg-muted hover:dy-text-muted-foreground focus-visible:dy-outline-none focus-visible:dy-ring-2 focus-visible:dy-ring-ring focus-visible:dy-ring-offset-2 disabled:dy-pointer-events-none disabled:dy-opacity-50 data-[state=on]:dy-bg-accent data-[state=on]:dy-text-accent-foreground [&_svg]:dy-pointer-events-none [&_svg]:dy-size-4 [&_svg]:dy-shrink-0 dy-gap-2", {
@@ -2243,55 +2859,6 @@ var Toggle = React$1.forwardRef(({ className, variant, size, ...props }, ref) =>
2243
2859
  }));
2244
2860
  Toggle.displayName = TogglePrimitive.Root.displayName;
2245
2861
  //#endregion
2246
- //#region src/components/ui/dialog.tsx
2247
- var Dialog = SheetPrimitive.Root;
2248
- var DialogTrigger = SheetPrimitive.Trigger;
2249
- var DialogPortal = SheetPrimitive.Portal;
2250
- var DialogOverlay = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Overlay, {
2251
- ref,
2252
- className: cn("dy-fixed dy-inset-0 dy-z-50 dy-bg-black/40 dy-backdrop-blur-[2px] data-[state=open]:dy-animate-in data-[state=closed]:dy-animate-out data-[state=closed]:dy-fade-out-0 data-[state=open]:dy-fade-in-0", className),
2253
- ...props
2254
- }));
2255
- DialogOverlay.displayName = SheetPrimitive.Overlay.displayName;
2256
- var DialogContent = React$1.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsx(DialogPortal, { children: /* @__PURE__ */ jsxs("div", {
2257
- className: "dy-admin-ui",
2258
- children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(SheetPrimitive.Content, {
2259
- ref,
2260
- className: cn("dy-fixed dy-left-[50%] dy-top-[50%] dy-z-50 dy-grid dy-w-full dy-max-w-lg dy-translate-x-[-50%] dy-translate-y-[-50%] dy-gap-4 dy-border dy-bg-background dy-p-6 dy-shadow-2xl dy-duration-200 data-[state=open]:dy-animate-in data-[state=closed]:dy-animate-out data-[state=closed]:dy-fade-out-0 data-[state=open]:dy-fade-in-0 data-[state=closed]:dy-zoom-out-95 data-[state=open]:dy-zoom-in-95 data-[state=closed]:dy-slide-out-to-left-1/2 data-[state=closed]:dy-slide-out-to-top-[48%] data-[state=open]:dy-slide-in-from-left-1/2 data-[state=open]:dy-slide-in-from-top-[48%] sm:dy-rounded-xl", className),
2261
- ...props,
2262
- children: [children, /* @__PURE__ */ jsxs(SheetPrimitive.Close, {
2263
- className: "dy-absolute dy-right-4 dy-top-4 dy-rounded-sm dy-opacity-70 dy-ring-offset-background dy-transition-opacity hover:dy-opacity-100 focus:dy-outline-none focus:dy-ring-2 focus:dy-ring-ring focus:dy-ring-offset-2 disabled:dy-pointer-events-none data-[state=open]:dy-bg-accent data-[state=open]:dy-text-muted-foreground",
2264
- children: [/* @__PURE__ */ jsx(X, { className: "dy-h-4 dy-w-4" }), /* @__PURE__ */ jsx("span", {
2265
- className: "dy-sr-only",
2266
- children: "Close"
2267
- })]
2268
- })]
2269
- })]
2270
- }) }));
2271
- DialogContent.displayName = SheetPrimitive.Content.displayName;
2272
- var DialogHeader = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
2273
- className: cn("dy-flex dy-flex-col dy-space-y-1.5 dy-text-center sm:dy-text-left", className),
2274
- ...props
2275
- });
2276
- DialogHeader.displayName = "DialogHeader";
2277
- var DialogFooter = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
2278
- className: cn("dy-flex dy-flex-col-reverse sm:dy-flex-row sm:dy-justify-end sm:dy-space-x-2", className),
2279
- ...props
2280
- });
2281
- DialogFooter.displayName = "DialogFooter";
2282
- var DialogTitle = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Title, {
2283
- ref,
2284
- className: cn("dy-text-lg dy-font-semibold dy-leading-none dy-tracking-tight", className),
2285
- ...props
2286
- }));
2287
- DialogTitle.displayName = SheetPrimitive.Title.displayName;
2288
- var DialogDescription = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(SheetPrimitive.Description, {
2289
- ref,
2290
- className: cn("dy-text-sm dy-text-muted-foreground", className),
2291
- ...props
2292
- }));
2293
- DialogDescription.displayName = SheetPrimitive.Description.displayName;
2294
- //#endregion
2295
2862
  //#region src/components/ui/tabs.tsx
2296
2863
  var Tabs = TabsPrimitive.Root;
2297
2864
  var TabsList = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(TabsPrimitive.List, {
@@ -2308,34 +2875,10 @@ var TabsTrigger = React$1.forwardRef(({ className, ...props }, ref) => /* @__PUR
2308
2875
  TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
2309
2876
  var TabsContent = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(TabsPrimitive.Content, {
2310
2877
  ref,
2311
- className: cn("dy-mt-2 dy-ring-offset-background focus-visible:dy-outline-none focus-visible:dy-ring-2 focus-visible:dy-ring-ring focus-visible:dy-ring-offset-2", className),
2312
- ...props
2313
- }));
2314
- TabsContent.displayName = TabsPrimitive.Content.displayName;
2315
- //#endregion
2316
- //#region src/components/ui/scroll-area.tsx
2317
- var ScrollArea = React$1.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(ScrollAreaPrimitive.Root, {
2318
- ref,
2319
- className: cn("dy-relative dy-overflow-hidden", className),
2320
- ...props,
2321
- children: [
2322
- /* @__PURE__ */ jsx(ScrollAreaPrimitive.Viewport, {
2323
- className: "dy-h-full dy-w-full dy-rounded-[inherit]",
2324
- children
2325
- }),
2326
- /* @__PURE__ */ jsx(ScrollBar, {}),
2327
- /* @__PURE__ */ jsx(ScrollAreaPrimitive.Corner, {})
2328
- ]
2329
- }));
2330
- ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
2331
- var ScrollBar = React$1.forwardRef(({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsx(ScrollAreaPrimitive.ScrollAreaScrollbar, {
2332
- ref,
2333
- orientation,
2334
- className: cn("dy-flex dy-touch-none dy-select-none dy-transition-colors", orientation === "vertical" && "dy-h-full dy-w-2.5 dy-border-l dy-border-l-transparent dy-p-[1px]", orientation === "horizontal" && "dy-h-2.5 dy-flex-col dy-border-t dy-border-t-transparent dy-p-[1px]", className),
2335
- ...props,
2336
- children: /* @__PURE__ */ jsx(ScrollAreaPrimitive.ScrollAreaThumb, { className: "dy-relative dy-flex-1 dy-rounded-full dy-bg-border" })
2878
+ className: cn("dy-mt-2 dy-ring-offset-background focus-visible:dy-outline-none focus-visible:dy-ring-2 focus-visible:dy-ring-ring focus-visible:dy-ring-offset-2", className),
2879
+ ...props
2337
2880
  }));
2338
- ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
2881
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
2339
2882
  //#endregion
2340
2883
  //#region src/components/media/media-library-dialog.tsx
2341
2884
  function MediaLibraryDialog({ collection, isOpen, onOpenChange, selectedValues, onSelect, multiple, onConfirm }) {
@@ -8724,13 +9267,41 @@ function SpreadsheetEditor({ slug, schema, data, onSave, isSaving }) {
8724
9267
  }
8725
9268
  //#endregion
8726
9269
  //#region src/pages/collections/list-page.tsx
9270
+ function SortableColumnItem({ id, label, visible, onToggleVisible }) {
9271
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
9272
+ return /* @__PURE__ */ jsxs("div", {
9273
+ ref: setNodeRef,
9274
+ style: {
9275
+ transform: CSS.Transform.toString(transform),
9276
+ transition,
9277
+ zIndex: isDragging ? 10 : 1
9278
+ },
9279
+ className: cn("dy-flex dy-items-center dy-gap-2.5 dy-p-2 dy-bg-background dy-border dy-border-border/60 dy-rounded-lg dy-shadow-sm dy-transition-all", isDragging && "dy-opacity-50 dy-border-primary"),
9280
+ children: [
9281
+ /* @__PURE__ */ jsx("div", {
9282
+ ...attributes,
9283
+ ...listeners,
9284
+ className: "dy-cursor-grab active:dy-cursor-grabbing dy-text-muted-foreground/60 hover:dy-text-foreground dy-p-0.5",
9285
+ children: /* @__PURE__ */ jsx(GripVertical, { className: "dy-h-3.5 dy-w-3.5" })
9286
+ }),
9287
+ /* @__PURE__ */ jsx(Checkbox, {
9288
+ checked: visible,
9289
+ onCheckedChange: (val) => onToggleVisible(!!val)
9290
+ }),
9291
+ /* @__PURE__ */ jsx("div", {
9292
+ className: "dy-flex-1 dy-text-xs dy-font-medium dy-text-foreground",
9293
+ children: label
9294
+ })
9295
+ ]
9296
+ });
9297
+ }
8727
9298
  function CollectionListPage({ slug }) {
8728
9299
  const { client, components, user } = useDyrected();
8729
9300
  const queryClient = useQueryClient();
8730
9301
  const [page, setPage] = React$1.useState(1);
8731
9302
  const [rowSelection, setRowSelection] = React$1.useState({});
9303
+ const [isImportOpen, setIsImportOpen] = React$1.useState(false);
8732
9304
  const [searchParams, setSearchParams] = useSearchParams();
8733
- const [viewMode, setViewMode] = React$1.useState("list");
8734
9305
  const whereParam = searchParams.get("where");
8735
9306
  const rules = React$1.useMemo(() => {
8736
9307
  if (!whereParam) return [];
@@ -8760,6 +9331,145 @@ function CollectionListPage({ slug }) {
8760
9331
  enabled: !!client
8761
9332
  });
8762
9333
  const schema = schemas?.collections.find((c) => c.slug === slug);
9334
+ const allAvailableColumns = React$1.useMemo(() => {
9335
+ if (!schema) return [];
9336
+ return [
9337
+ ...schema.fields.filter((f) => f.name !== "password" && !f.admin?.hidden && f.type !== "row" && f.type !== "join").map((field) => field.name),
9338
+ "id",
9339
+ "createdAt",
9340
+ "updatedAt"
9341
+ ].filter((name, index, self) => self.indexOf(name) === index);
9342
+ }, [schema]);
9343
+ const defaultListColumns = React$1.useMemo(() => {
9344
+ if (!schema) return [];
9345
+ const allDisplayFields = schema.fields.filter((f) => f.name !== "password" && !f.admin?.hidden && f.type !== "row" && f.type !== "join");
9346
+ const configuredColumns = Array.isArray(schema.admin?.defaultColumns) ? schema.admin.defaultColumns : [];
9347
+ return configuredColumns.length > 0 ? configuredColumns : allDisplayFields.slice(0, 3).map((field) => field.name);
9348
+ }, [schema]);
9349
+ const defaultListPreference = React$1.useMemo(() => {
9350
+ const visibleSet = new Set(defaultListColumns);
9351
+ return {
9352
+ viewMode: "list",
9353
+ columns: allAvailableColumns.map((name) => ({
9354
+ name,
9355
+ visible: visibleSet.has(name)
9356
+ }))
9357
+ };
9358
+ }, [allAvailableColumns, defaultListColumns]);
9359
+ const prefKey = `layout:collections:${slug}:list`;
9360
+ const { data: rawPreference } = useQuery({
9361
+ queryKey: ["preferences", prefKey],
9362
+ queryFn: async () => {
9363
+ if (!client) return null;
9364
+ return (await client.getPreference(prefKey)).value;
9365
+ },
9366
+ enabled: !!client,
9367
+ staleTime: 5e3,
9368
+ refetchOnWindowFocus: true
9369
+ });
9370
+ const reconciledPreference = React$1.useMemo(() => {
9371
+ if (!rawPreference) return defaultListPreference;
9372
+ let parsedViewMode = "list";
9373
+ let parsedColumns = [];
9374
+ if (Array.isArray(rawPreference)) {
9375
+ parsedViewMode = "list";
9376
+ parsedColumns = rawPreference.map((item) => {
9377
+ if (typeof item === "string") return {
9378
+ name: item,
9379
+ visible: true
9380
+ };
9381
+ if (item && typeof item === "object" && "name" in item) {
9382
+ const obj = item;
9383
+ return {
9384
+ name: obj.name,
9385
+ visible: obj.visible !== false
9386
+ };
9387
+ }
9388
+ return null;
9389
+ }).filter((x) => x !== null);
9390
+ } else if (rawPreference && typeof rawPreference === "object") {
9391
+ const rawPrefObj = rawPreference;
9392
+ parsedViewMode = rawPrefObj.viewMode === "spreadsheet" ? "spreadsheet" : "list";
9393
+ if (Array.isArray(rawPrefObj.columns)) parsedColumns = rawPrefObj.columns.map((item) => {
9394
+ if (typeof item === "string") return {
9395
+ name: item,
9396
+ visible: true
9397
+ };
9398
+ if (item && typeof item === "object" && "name" in item) {
9399
+ const obj = item;
9400
+ return {
9401
+ name: obj.name,
9402
+ visible: obj.visible !== false
9403
+ };
9404
+ }
9405
+ return null;
9406
+ }).filter((x) => x !== null);
9407
+ }
9408
+ const validColumns = parsedColumns.filter((col) => allAvailableColumns.includes(col.name));
9409
+ const validNames = validColumns.map((col) => col.name);
9410
+ const missingColumns = allAvailableColumns.filter((name) => !validNames.includes(name)).map((name) => ({
9411
+ name,
9412
+ visible: false
9413
+ }));
9414
+ return {
9415
+ viewMode: parsedViewMode,
9416
+ columns: [...validColumns, ...missingColumns]
9417
+ };
9418
+ }, [
9419
+ rawPreference,
9420
+ defaultListPreference,
9421
+ allAvailableColumns
9422
+ ]);
9423
+ const [localPreference, setLocalPreference] = React$1.useState(reconciledPreference);
9424
+ const syncPreference = React$1.useState(() => {
9425
+ return (next) => {
9426
+ setLocalPreference((prev) => {
9427
+ const isViewModeSame = prev.viewMode === next.viewMode;
9428
+ const isColsSame = prev.columns.length === next.columns.length && prev.columns.every((col, i) => col.name === next.columns[i].name && col.visible === next.columns[i].visible);
9429
+ return isViewModeSame && isColsSame ? prev : next;
9430
+ });
9431
+ };
9432
+ })[0];
9433
+ React$1.useEffect(() => {
9434
+ syncPreference(reconciledPreference);
9435
+ }, [reconciledPreference, syncPreference]);
9436
+ const savePreferenceMutation = useMutation({
9437
+ mutationFn: async ({ scope, value }) => {
9438
+ if (!client) throw new Error("Client not available");
9439
+ await client.setPreference(prefKey, value, { scope });
9440
+ },
9441
+ onSuccess: () => {
9442
+ queryClient.invalidateQueries({ queryKey: ["preferences", prefKey] });
9443
+ }
9444
+ });
9445
+ const resetPreferenceMutation = useMutation({
9446
+ mutationFn: async ({ scope }) => {
9447
+ if (!client) throw new Error("Client not available");
9448
+ await client.deletePreference(prefKey, { scope });
9449
+ },
9450
+ onSuccess: () => {
9451
+ queryClient.invalidateQueries({ queryKey: ["preferences", prefKey] });
9452
+ }
9453
+ });
9454
+ const saveColumns = (scope) => savePreferenceMutation.mutateAsync({
9455
+ scope,
9456
+ value: localPreference
9457
+ });
9458
+ const resetColumns = () => resetPreferenceMutation.mutateAsync({ scope: "personal" });
9459
+ const dndSensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
9460
+ const handleDndDragEnd = (event) => {
9461
+ const { active, over } = event;
9462
+ if (!over || active.id === over.id) return;
9463
+ setLocalPreference((prev) => {
9464
+ const oldIndex = prev.columns.findIndex((c) => c.name === active.id);
9465
+ const newIndex = prev.columns.findIndex((c) => c.name === over.id);
9466
+ if (oldIndex === -1 || newIndex === -1) return prev;
9467
+ return {
9468
+ ...prev,
9469
+ columns: arrayMove(prev.columns, oldIndex, newIndex)
9470
+ };
9471
+ });
9472
+ };
8763
9473
  const { data: response, isLoading } = useQuery({
8764
9474
  queryKey: [
8765
9475
  "collection",
@@ -8908,18 +9618,9 @@ function CollectionListPage({ slug }) {
8908
9618
  if (!schema) return [];
8909
9619
  const allDisplayFields = schema.fields.filter((f) => f.name !== "password" && !f.admin?.hidden && f.type !== "row" && f.type !== "join");
8910
9620
  const fieldByName = new Map(allDisplayFields.map((field) => [field.name, field]));
8911
- const configuredColumns = Array.isArray(schema.admin?.defaultColumns) ? schema.admin.defaultColumns : [];
9621
+ const configuredColumns = localPreference.columns.filter((col) => col.visible).map((col) => col.name);
8912
9622
  const visibleColumnNames = configuredColumns.length > 0 ? configuredColumns : allDisplayFields.slice(0, 3).map((field) => field.name);
8913
- const systemColumnNames = [
8914
- "id",
8915
- "createdAt",
8916
- "updatedAt"
8917
- ];
8918
- const allColumnNames = [
8919
- ...visibleColumnNames,
8920
- ...allDisplayFields.map((field) => field.name),
8921
- ...systemColumnNames
8922
- ].filter((name, index, names) => names.indexOf(name) === index);
9623
+ const allColumnNames = visibleColumnNames;
8923
9624
  const firstVisibleFieldName = visibleColumnNames.find((name) => fieldByName.has(name));
8924
9625
  const titleFieldName = visibleColumnNames.includes(schema.admin?.useAsTitle || "") ? schema.admin?.useAsTitle : firstVisibleFieldName;
8925
9626
  const deleteAccess = schema.access?.delete;
@@ -9041,29 +9742,9 @@ function CollectionListPage({ slug }) {
9041
9742
  user,
9042
9743
  handleDelete,
9043
9744
  slug,
9044
- schemas
9745
+ schemas,
9746
+ localPreference.columns
9045
9747
  ]);
9046
- const initialColumnVisibility = React$1.useMemo(() => {
9047
- if (!schema) return {};
9048
- const allDisplayFields = schema.fields.filter((f) => f.name !== "password" && !f.admin?.hidden && f.type !== "row" && f.type !== "join");
9049
- const validColumnNames = new Set([
9050
- ...allDisplayFields.map((field) => field.name),
9051
- "id",
9052
- "createdAt",
9053
- "updatedAt"
9054
- ]);
9055
- const configuredColumns = Array.isArray(schema.admin?.defaultColumns) ? schema.admin.defaultColumns : [];
9056
- const visibleColumnNames = new Set((configuredColumns.length > 0 ? configuredColumns : allDisplayFields.slice(0, 3).map((field) => field.name)).filter((name) => validColumnNames.has(name)));
9057
- const visibility = {};
9058
- validColumnNames.forEach((name) => {
9059
- visibility[name] = visibleColumnNames.has(name);
9060
- });
9061
- return visibility;
9062
- }, [schema]);
9063
- const columnPreferenceKey = React$1.useMemo(() => {
9064
- if (!schema) return slug;
9065
- return `${slug}:${(Array.isArray(schema.admin?.defaultColumns) ? schema.admin.defaultColumns : []).join(",") || "default"}`;
9066
- }, [schema, slug]);
9067
9748
  if (!schema) return /* @__PURE__ */ jsxs("div", { children: ["Collection not found: ", slug] });
9068
9749
  const readAccess = schema.access?.read;
9069
9750
  let canRead = true;
@@ -9200,63 +9881,139 @@ function CollectionListPage({ slug }) {
9200
9881
  icon: Database,
9201
9882
  children: /* @__PURE__ */ jsxs("div", {
9202
9883
  className: "dy-flex dy-items-center dy-gap-2 dy-w-full sm:dy-w-auto",
9203
- children: [/* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
9884
+ children: [/* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
9204
9885
  asChild: true,
9205
9886
  children: /* @__PURE__ */ jsxs(Button, {
9206
9887
  variant: "outline",
9207
9888
  size: "sm",
9208
9889
  className: "dy-h-8 dy-px-3 dy-gap-1.5 dy-text-xs",
9209
- children: [
9210
- viewMode === "list" ? /* @__PURE__ */ jsx(List, { className: "dy-h-3.5 dy-w-3.5 dy-text-muted-foreground" }) : /* @__PURE__ */ jsx(Grid, { className: "dy-h-3.5 dy-w-3.5 dy-text-muted-foreground" }),
9211
- /* @__PURE__ */ jsx("span", { children: viewMode === "list" ? "List View" : "Spreadsheet" }),
9212
- /* @__PURE__ */ jsx(ChevronDown, { className: "dy-h-3 dy-w-3 dy-text-muted-foreground/60" })
9213
- ]
9890
+ children: [/* @__PURE__ */ jsx(Settings2, { className: "dy-h-3.5 dy-w-3.5" }), /* @__PURE__ */ jsx("span", { children: "View Settings" })]
9214
9891
  })
9215
- }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
9892
+ }), /* @__PURE__ */ jsxs(PopoverContent, {
9216
9893
  align: "end",
9217
- className: "dy-w-56",
9894
+ className: "dy-w-80 dy-p-4 dy-space-y-4",
9218
9895
  children: [
9219
- /* @__PURE__ */ jsx(DropdownMenuLabel, {
9220
- className: "dy-text-xs dy-font-normal dy-text-muted-foreground/50 dy-px-3 dy-py-1.5",
9221
- children: "Layout"
9222
- }),
9223
- /* @__PURE__ */ jsx(DropdownMenuSeparator, { className: "dy-opacity-50" }),
9224
- /* @__PURE__ */ jsxs(DropdownMenuItem, {
9225
- onClick: () => setViewMode("list"),
9226
- className: "dy-flex dy-items-center dy-justify-between dy-py-2 dy-cursor-pointer",
9227
- children: [/* @__PURE__ */ jsxs("div", {
9228
- className: "dy-flex dy-items-center dy-gap-2.5",
9229
- children: [/* @__PURE__ */ jsx(List, { className: "dy-h-4 dy-w-4 dy-text-muted-foreground" }), /* @__PURE__ */ jsxs("div", {
9230
- className: "dy-flex dy-flex-col",
9231
- children: [/* @__PURE__ */ jsx("span", {
9232
- className: "dy-text-xs dy-font-medium",
9233
- children: "List View"
9234
- }), /* @__PURE__ */ jsx("span", {
9235
- className: "dy-text-[10px] dy-text-muted-foreground",
9236
- children: "Read-only table listing"
9237
- })]
9896
+ /* @__PURE__ */ jsxs("div", {
9897
+ className: "dy-space-y-1.5",
9898
+ children: [/* @__PURE__ */ jsx("h4", {
9899
+ className: "dy-text-xs dy-font-semibold dy-text-foreground dy-text-left",
9900
+ children: "Layout Mode"
9901
+ }), /* @__PURE__ */ jsxs("div", {
9902
+ className: "dy-grid dy-grid-cols-2 dy-gap-2",
9903
+ children: [/* @__PURE__ */ jsx(Button, {
9904
+ size: "sm",
9905
+ variant: localPreference.viewMode === "list" ? "default" : "outline",
9906
+ className: "dy-h-8 dy-text-xs",
9907
+ onClick: () => setLocalPreference((prev) => ({
9908
+ ...prev,
9909
+ viewMode: "list"
9910
+ })),
9911
+ children: "List View"
9912
+ }), /* @__PURE__ */ jsx(Button, {
9913
+ size: "sm",
9914
+ variant: localPreference.viewMode === "spreadsheet" ? "default" : "outline",
9915
+ className: "dy-h-8 dy-text-xs",
9916
+ onClick: () => setLocalPreference((prev) => ({
9917
+ ...prev,
9918
+ viewMode: "spreadsheet"
9919
+ })),
9920
+ children: "Spreadsheet"
9238
9921
  })]
9239
- }), viewMode === "list" && /* @__PURE__ */ jsx(Check, { className: "dy-h-3.5 dy-w-3.5 dy-text-primary" })]
9922
+ })]
9240
9923
  }),
9241
- /* @__PURE__ */ jsxs(DropdownMenuItem, {
9242
- onClick: () => setViewMode("spreadsheet"),
9243
- className: "dy-flex dy-items-center dy-justify-between dy-py-2 dy-cursor-pointer",
9924
+ /* @__PURE__ */ jsxs("div", {
9925
+ className: "dy-space-y-1.5",
9926
+ children: [
9927
+ /* @__PURE__ */ jsx("h4", {
9928
+ className: "dy-text-xs dy-font-semibold dy-text-foreground dy-text-left",
9929
+ children: "Columns"
9930
+ }),
9931
+ /* @__PURE__ */ jsx("p", {
9932
+ className: "dy-text-[10px] dy-text-muted-foreground dy-text-left",
9933
+ children: "Reorder and toggle column visibility."
9934
+ }),
9935
+ /* @__PURE__ */ jsx("div", {
9936
+ className: "dy-max-h-[200px] dy-overflow-y-auto dy-space-y-1 dy-pr-1 dy-outline-none",
9937
+ children: /* @__PURE__ */ jsx(DndContext, {
9938
+ sensors: dndSensors,
9939
+ collisionDetection: closestCenter,
9940
+ onDragEnd: handleDndDragEnd,
9941
+ children: /* @__PURE__ */ jsx(SortableContext, {
9942
+ items: localPreference.columns.map((c) => c.name),
9943
+ strategy: verticalListSortingStrategy,
9944
+ children: /* @__PURE__ */ jsx("div", {
9945
+ className: "dy-space-y-1",
9946
+ children: localPreference.columns.map((col) => {
9947
+ let label = col.name;
9948
+ if (col.name === "id") label = "ID";
9949
+ else if (col.name === "createdAt") label = "Created";
9950
+ else if (col.name === "updatedAt") label = "Last Updated";
9951
+ else {
9952
+ const field = schema.fields.find((f) => f.name === col.name);
9953
+ if (field) label = field.label || field.name;
9954
+ }
9955
+ return /* @__PURE__ */ jsx(SortableColumnItem, {
9956
+ id: col.name,
9957
+ label,
9958
+ visible: col.visible,
9959
+ onToggleVisible: (visible) => {
9960
+ setLocalPreference((prev) => ({
9961
+ ...prev,
9962
+ columns: prev.columns.map((c) => c.name === col.name ? {
9963
+ ...c,
9964
+ visible
9965
+ } : c)
9966
+ }));
9967
+ }
9968
+ }, col.name);
9969
+ })
9970
+ })
9971
+ })
9972
+ })
9973
+ })
9974
+ ]
9975
+ }),
9976
+ /* @__PURE__ */ jsxs("div", {
9977
+ className: "dy-pt-2 dy-border-t dy-border-border/60 dy-flex dy-flex-col dy-gap-2",
9244
9978
  children: [/* @__PURE__ */ jsxs("div", {
9245
- className: "dy-flex dy-items-center dy-gap-2.5",
9246
- children: [/* @__PURE__ */ jsx(Grid, { className: "dy-h-4 dy-w-4 dy-text-muted-foreground" }), /* @__PURE__ */ jsxs("div", {
9247
- className: "dy-flex dy-flex-col",
9248
- children: [/* @__PURE__ */ jsx("span", {
9249
- className: "dy-text-xs dy-font-medium",
9250
- children: "Spreadsheet"
9251
- }), /* @__PURE__ */ jsx("span", {
9252
- className: "dy-text-[10px] dy-text-muted-foreground",
9253
- children: "Airtable-style editing"
9254
- })]
9979
+ className: "dy-grid dy-grid-cols-2 dy-gap-2",
9980
+ children: [/* @__PURE__ */ jsx(Button, {
9981
+ size: "sm",
9982
+ className: "dy-h-7 dy-text-[10px]",
9983
+ onClick: () => {
9984
+ saveColumns("personal");
9985
+ toast.success("Saved personal view preferences");
9986
+ },
9987
+ children: "Save for Me"
9988
+ }), user?.role === "admin" && /* @__PURE__ */ jsx(Button, {
9989
+ size: "sm",
9990
+ variant: "secondary",
9991
+ className: "dy-h-7 dy-text-[10px]",
9992
+ onClick: () => {
9993
+ saveColumns("global");
9994
+ toast.success("Saved global view preferences");
9995
+ },
9996
+ children: "Save for Everyone"
9255
9997
  })]
9256
- }), viewMode === "spreadsheet" && /* @__PURE__ */ jsx(Check, { className: "dy-h-3.5 dy-w-3.5 dy-text-primary" })]
9998
+ }), /* @__PURE__ */ jsx(Button, {
9999
+ size: "sm",
10000
+ variant: "outline",
10001
+ className: "dy-h-7 dy-text-[10px] dy-w-full",
10002
+ onClick: () => {
10003
+ resetColumns();
10004
+ toast.success("Reset view preferences to default");
10005
+ },
10006
+ children: "Reset to Default"
10007
+ })]
9257
10008
  })
9258
10009
  ]
9259
- })] }), canCreate && /* @__PURE__ */ jsx(Link, {
10010
+ })] }), canCreate && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Button, {
10011
+ variant: "outline",
10012
+ size: "sm",
10013
+ className: "dy-h-8 dy-px-3 dy-gap-1.5 dy-text-xs dy-w-full sm:dy-w-auto",
10014
+ onClick: () => setIsImportOpen(true),
10015
+ children: [/* @__PURE__ */ jsx(FileUp, { className: "dy-h-3.5 dy-w-3.5" }), /* @__PURE__ */ jsx("span", { children: "Import CSV" })]
10016
+ }), /* @__PURE__ */ jsx(Link, {
9260
10017
  to: `/collections/${slug}/new`,
9261
10018
  className: "dy-w-full sm:dy-w-auto",
9262
10019
  children: /* @__PURE__ */ jsxs(Button, {
@@ -9273,7 +10030,7 @@ function CollectionListPage({ slug }) {
9273
10030
  })
9274
10031
  ]
9275
10032
  })
9276
- })]
10033
+ })] })]
9277
10034
  })
9278
10035
  }),
9279
10036
  /* @__PURE__ */ jsx(AdminComponentSlot, {
@@ -9288,7 +10045,7 @@ function CollectionListPage({ slug }) {
9288
10045
  isLoading ? /* @__PURE__ */ jsx("div", {
9289
10046
  className: "dy-flex dy-h-[400px] dy-items-center dy-justify-center",
9290
10047
  children: /* @__PURE__ */ jsx("div", { className: "dy-h-8 dy-w-8 dy-animate-spin dy-rounded-full dy-border-4 dy-border-primary dy-border-t-transparent" })
9291
- }) : viewMode === "spreadsheet" ? /* @__PURE__ */ jsx(SpreadsheetEditor, {
10048
+ }) : localPreference.viewMode === "spreadsheet" ? /* @__PURE__ */ jsx(SpreadsheetEditor, {
9292
10049
  slug,
9293
10050
  schema,
9294
10051
  data: response?.docs || [],
@@ -9305,8 +10062,7 @@ function CollectionListPage({ slug }) {
9305
10062
  searchKey: schema.admin?.useAsTitle || schema.fields.find((f) => !f.admin?.hidden)?.name || "id",
9306
10063
  onRowSelectionChange: setRowSelection,
9307
10064
  rowSelection,
9308
- persistenceKey: columnPreferenceKey,
9309
- initialColumnVisibility,
10065
+ hideViewButton: true,
9310
10066
  toolbarActions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FilterBuilder, {
9311
10067
  schema,
9312
10068
  rules,
@@ -9383,6 +10139,12 @@ function CollectionListPage({ slug }) {
9383
10139
  componentProps: collectionComponentProps
9384
10140
  })
9385
10141
  ]
10142
+ }),
10143
+ /* @__PURE__ */ jsx(CsvImporterDialog, {
10144
+ open: isImportOpen,
10145
+ onOpenChange: setIsImportOpen,
10146
+ slug,
10147
+ schema
9386
10148
  })
9387
10149
  ]
9388
10150
  });
@@ -9744,7 +10506,172 @@ function WorkflowPanel({ collection, documentId, workflowMeta, workflowConfig })
9744
10506
  });
9745
10507
  }
9746
10508
  //#endregion
10509
+ //#region src/hooks/useLayoutPreference.ts
10510
+ function useLayoutPreference({ key, defaultKeys }) {
10511
+ const { client } = useDyrected();
10512
+ const queryClient = useQueryClient();
10513
+ const { data, isLoading } = useQuery({
10514
+ queryKey: ["preferences", key],
10515
+ queryFn: async () => {
10516
+ if (!client) return null;
10517
+ return (await client.getPreference(key)).value;
10518
+ },
10519
+ enabled: !!client,
10520
+ staleTime: 5e3,
10521
+ refetchOnWindowFocus: true
10522
+ });
10523
+ const reconciledLayout = useMemo(() => {
10524
+ const isStringArray = defaultKeys.every((k) => typeof k === "string");
10525
+ if (!data || !Array.isArray(data)) return defaultKeys;
10526
+ if (isStringArray) {
10527
+ const normalizedData = data.map((k) => typeof k === "string" ? k : k?.name || "").filter(Boolean);
10528
+ const defaultStrings = defaultKeys;
10529
+ const validKeys = normalizedData.filter((k) => defaultStrings.includes(k));
10530
+ const missingKeys = defaultStrings.filter((k) => !validKeys.includes(k));
10531
+ return [...validKeys, ...missingKeys];
10532
+ } else {
10533
+ const normalizedData = data.map((item) => {
10534
+ if (typeof item === "string") return { name: item };
10535
+ if (item && typeof item === "object" && "name" in item && typeof item.name === "string") return {
10536
+ name: item.name,
10537
+ width: item.width
10538
+ };
10539
+ return { name: "" };
10540
+ }).filter((item) => item.name !== "");
10541
+ const defaultItems = defaultKeys;
10542
+ const defaultNames = defaultItems.map((item) => item.name);
10543
+ const defaultMap = new Map(defaultItems.map((item) => [item.name, item]));
10544
+ const validItems = normalizedData.filter((item) => defaultNames.includes(item.name));
10545
+ const validNames = validItems.map((item) => item.name);
10546
+ const missingItems = defaultNames.filter((name) => !validNames.includes(name)).map((name) => defaultMap.get(name));
10547
+ return [...validItems.map((item) => {
10548
+ const def = defaultMap.get(item.name);
10549
+ return {
10550
+ ...item,
10551
+ width: item.width || def?.width
10552
+ };
10553
+ }), ...missingItems];
10554
+ }
10555
+ }, [data, defaultKeys]);
10556
+ const [localLayout, setLocalLayout] = useState(reconciledLayout);
10557
+ const syncLayout = useState(() => {
10558
+ return (next) => {
10559
+ setLocalLayout((prev) => {
10560
+ return prev.length === next.length && prev.every((v, i) => {
10561
+ if (typeof v === "string") return v === next[i];
10562
+ return v.name === next[i].name && v.width === next[i].width;
10563
+ }) ? prev : next;
10564
+ });
10565
+ };
10566
+ })[0];
10567
+ useEffect(() => {
10568
+ syncLayout(reconciledLayout);
10569
+ }, [reconciledLayout, syncLayout]);
10570
+ const saveMutation = useMutation({
10571
+ mutationFn: async ({ scope, value }) => {
10572
+ if (!client) throw new Error("Client not available");
10573
+ await client.setPreference(key, value, { scope });
10574
+ },
10575
+ onSuccess: () => {
10576
+ queryClient.invalidateQueries({ queryKey: ["preferences", key] });
10577
+ }
10578
+ });
10579
+ const resetMutation = useMutation({
10580
+ mutationFn: async ({ scope }) => {
10581
+ if (!client) throw new Error("Client not available");
10582
+ await client.deletePreference(key, { scope });
10583
+ },
10584
+ onSuccess: () => {
10585
+ queryClient.invalidateQueries({ queryKey: ["preferences", key] });
10586
+ }
10587
+ });
10588
+ return {
10589
+ layout: localLayout,
10590
+ setLayout: setLocalLayout,
10591
+ reconciledLayout,
10592
+ saveLayout: (scope) => saveMutation.mutateAsync({
10593
+ scope,
10594
+ value: localLayout
10595
+ }),
10596
+ resetLayout: (scope) => resetMutation.mutateAsync({ scope }),
10597
+ isLoading,
10598
+ isSaving: saveMutation.isPending,
10599
+ isResetting: resetMutation.isPending
10600
+ };
10601
+ }
10602
+ //#endregion
9747
10603
  //#region src/pages/collections/edit-page.tsx
10604
+ function SortableFieldItem({ id, label, type, width, onChangeWidth }) {
10605
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
10606
+ return /* @__PURE__ */ jsxs("div", {
10607
+ ref: setNodeRef,
10608
+ style: {
10609
+ transform: CSS.Transform.toString(transform),
10610
+ transition,
10611
+ zIndex: isDragging ? 10 : 1
10612
+ },
10613
+ className: cn("dy-flex dy-items-center dy-gap-3 dy-p-3 dy-bg-card dy-border dy-border-border/60 dy-rounded-xl dy-shadow-sm dy-transition-all", isDragging && "dy-opacity-50 dy-border-primary"),
10614
+ children: [
10615
+ /* @__PURE__ */ jsx("div", {
10616
+ ...attributes,
10617
+ ...listeners,
10618
+ className: "dy-cursor-grab active:dy-cursor-grabbing dy-text-muted-foreground/60 hover:dy-text-foreground dy-p-1",
10619
+ children: /* @__PURE__ */ jsx(GripVertical, { className: "dy-h-4 dy-w-4" })
10620
+ }),
10621
+ /* @__PURE__ */ jsxs("div", {
10622
+ className: "dy-flex-1 dy-min-w-0",
10623
+ children: [/* @__PURE__ */ jsx("div", {
10624
+ className: "dy-text-sm dy-font-semibold dy-text-foreground dy-truncate",
10625
+ children: label
10626
+ }), /* @__PURE__ */ jsxs("div", {
10627
+ className: "dy-text-xs dy-text-muted-foreground",
10628
+ children: [
10629
+ id,
10630
+ " • ",
10631
+ /* @__PURE__ */ jsx("span", {
10632
+ className: "dy-uppercase",
10633
+ children: type
10634
+ })
10635
+ ]
10636
+ })]
10637
+ }),
10638
+ /* @__PURE__ */ jsx("div", {
10639
+ className: "dy-shrink-0",
10640
+ children: /* @__PURE__ */ jsxs("select", {
10641
+ value: width || "100%",
10642
+ onChange: (e) => onChangeWidth(e.target.value),
10643
+ className: "dy-text-[11px] dy-font-semibold dy-bg-muted/50 hover:dy-bg-muted dy-border dy-border-border/40 dy-rounded-lg dy-px-2 dy-py-1 dy-outline-none dy-cursor-pointer dy-transition-all",
10644
+ children: [
10645
+ /* @__PURE__ */ jsx("option", {
10646
+ value: "25%",
10647
+ children: "25%"
10648
+ }),
10649
+ /* @__PURE__ */ jsx("option", {
10650
+ value: "33.33%",
10651
+ children: "33%"
10652
+ }),
10653
+ /* @__PURE__ */ jsx("option", {
10654
+ value: "50%",
10655
+ children: "50%"
10656
+ }),
10657
+ /* @__PURE__ */ jsx("option", {
10658
+ value: "66.66%",
10659
+ children: "66%"
10660
+ }),
10661
+ /* @__PURE__ */ jsx("option", {
10662
+ value: "75%",
10663
+ children: "75%"
10664
+ }),
10665
+ /* @__PURE__ */ jsx("option", {
10666
+ value: "100%",
10667
+ children: "100%"
10668
+ })
10669
+ ]
10670
+ })
10671
+ })
10672
+ ]
10673
+ });
10674
+ }
9748
10675
  function EditEntryPage() {
9749
10676
  const { slug, id } = useParams();
9750
10677
  const [searchParams] = useSearchParams();
@@ -9755,6 +10682,77 @@ function EditEntryPage() {
9755
10682
  const [isDirty, setIsDirty] = useState(false);
9756
10683
  const [previewData, setPreviewData] = useState(null);
9757
10684
  const isEdit = !!id;
10685
+ const [isConfiguringView, setIsConfiguringView] = useState(false);
10686
+ const { data: schemas } = useQuery({
10687
+ queryKey: ["schemas"],
10688
+ queryFn: () => client.getSchemas(),
10689
+ enabled: !!client
10690
+ });
10691
+ const schema = schemas?.collections.find((c) => c.slug === slug);
10692
+ const fieldsList = useMemo(() => {
10693
+ if (!schema) return [];
10694
+ let fields = [...schema.fields];
10695
+ if (schema.upload) {
10696
+ const hasAlt = fields.some((f) => f.name === "alt");
10697
+ const hasCaption = fields.some((f) => f.name === "caption");
10698
+ const mediaFields = [];
10699
+ if (!hasAlt) mediaFields.push({
10700
+ name: "alt",
10701
+ type: "text",
10702
+ label: "Alt Text",
10703
+ admin: { description: "Describe the image for accessibility/screen readers." }
10704
+ });
10705
+ if (!hasCaption) mediaFields.push({
10706
+ name: "caption",
10707
+ type: "textarea",
10708
+ label: "Caption",
10709
+ admin: { description: "Add a caption/description for this media file." }
10710
+ });
10711
+ fields = [...mediaFields, ...fields];
10712
+ }
10713
+ return fields;
10714
+ }, [schema]);
10715
+ const defaultKeys = useMemo(() => {
10716
+ return fieldsList.filter((f) => !!f.name).map((f) => ({
10717
+ name: f.name,
10718
+ width: f.admin?.width || "100%"
10719
+ }));
10720
+ }, [fieldsList]);
10721
+ const { layout, setLayout, saveLayout, resetLayout, reconciledLayout, isLoading: isPreferenceLoading } = useLayoutPreference({
10722
+ key: `layout:collections:${slug}:edit`,
10723
+ defaultKeys
10724
+ });
10725
+ const orderedFields = useMemo(() => {
10726
+ const fieldMap = new Map(fieldsList.map((f) => [f.name, f]));
10727
+ const ordered = [];
10728
+ for (const item of layout) {
10729
+ const field = fieldMap.get(item.name);
10730
+ if (field) {
10731
+ const overriddenField = {
10732
+ ...field,
10733
+ admin: {
10734
+ ...field.admin,
10735
+ width: item.width || field.admin?.width || "100%"
10736
+ }
10737
+ };
10738
+ ordered.push(overriddenField);
10739
+ fieldMap.delete(item.name);
10740
+ }
10741
+ }
10742
+ for (const field of fieldMap.values()) ordered.push(field);
10743
+ return ordered;
10744
+ }, [fieldsList, layout]);
10745
+ const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
10746
+ const handleDragEnd = (event) => {
10747
+ const { active, over } = event;
10748
+ if (!over || active.id === over.id) return;
10749
+ setLayout((items) => {
10750
+ const oldIndex = items.findIndex((item) => item.name === active.id);
10751
+ const newIndex = items.findIndex((item) => item.name === over.id);
10752
+ if (oldIndex === -1 || newIndex === -1) return items;
10753
+ return arrayMove(items, oldIndex, newIndex);
10754
+ });
10755
+ };
9758
10756
  useEffect(() => {
9759
10757
  const handleBeforeUnload = (e) => {
9760
10758
  if (isDirty) {
@@ -9775,18 +10773,19 @@ function EditEntryPage() {
9775
10773
  window.addEventListener("keydown", handleSave);
9776
10774
  return () => window.removeEventListener("keydown", handleSave);
9777
10775
  }, []);
9778
- const { data: schemas } = useQuery({
9779
- queryKey: ["schemas"],
9780
- queryFn: () => client.getSchemas(),
9781
- enabled: !!client
9782
- });
9783
- const schema = schemas?.collections.find((c) => c.slug === slug);
9784
10776
  const schemaSlug = schema?.slug;
9785
10777
  const schemaPreviewUrl = schema?.admin?.previewUrl;
10778
+ const syncShowPreview = useState(() => (next) => {
10779
+ setShowPreview((prev) => prev === next ? prev : next);
10780
+ })[0];
9786
10781
  useEffect(() => {
9787
10782
  if (!schemaSlug || !schemaPreviewUrl) return;
9788
- setShowPreview((previous) => previous ? false : previous);
9789
- }, [schemaSlug, schemaPreviewUrl]);
10783
+ syncShowPreview(false);
10784
+ }, [
10785
+ schemaSlug,
10786
+ schemaPreviewUrl,
10787
+ syncShowPreview
10788
+ ]);
9790
10789
  const { data: entry, isLoading: isEntryLoading } = useQuery({
9791
10790
  queryKey: [
9792
10791
  "entry",
@@ -9796,14 +10795,13 @@ function EditEntryPage() {
9796
10795
  queryFn: () => client.collection(slug).findOne(id),
9797
10796
  enabled: !!client && isEdit
9798
10797
  });
10798
+ const syncPreviewData = useState(() => (next) => {
10799
+ setPreviewData((prev) => prev === next ? prev : next);
10800
+ })[0];
9799
10801
  useEffect(() => {
9800
10802
  if (!entry) return;
9801
- setPreviewData((previous) => previous === entry ? previous : entry);
9802
- }, [
9803
- entry,
9804
- id,
9805
- slug
9806
- ]);
10803
+ syncPreviewData(entry);
10804
+ }, [entry, syncPreviewData]);
9807
10805
  const isAdminUser = Array.isArray(user?.roles) && user.roles.includes("admin");
9808
10806
  const isSelf = !!user && !!id && (user.id === id || user.sub === id);
9809
10807
  const passwordChangeMode = schema?.auth ? isSelf ? "self" : isAdminUser ? "admin" : null : null;
@@ -9979,6 +10977,14 @@ function EditEntryPage() {
9979
10977
  title: showPreview ? "Hide Preview" : "Live Preview",
9980
10978
  children: showPreview ? /* @__PURE__ */ jsx(EyeOff, { className: "dy-h-3.5 dy-w-3.5" }) : /* @__PURE__ */ jsx(Eye, { className: "dy-h-3.5 dy-w-3.5" })
9981
10979
  }),
10980
+ /* @__PURE__ */ jsx(Button, {
10981
+ variant: "ghost",
10982
+ size: "icon",
10983
+ className: cn("dy-h-8 dy-w-8 dy-rounded-lg dy-transition-all", isConfiguringView ? "dy-bg-background dy-text-primary dy-shadow-sm hover:dy-bg-background" : "dy-text-muted-foreground hover:dy-bg-muted hover:dy-text-foreground"),
10984
+ onClick: () => setIsConfiguringView(!isConfiguringView),
10985
+ title: "View",
10986
+ children: /* @__PURE__ */ jsx(Settings2, { className: "dy-h-3.5 dy-w-3.5" })
10987
+ }),
9982
10988
  isEdit && canCreate && /* @__PURE__ */ jsx(Button, {
9983
10989
  variant: "outline",
9984
10990
  size: "icon",
@@ -10061,33 +11067,111 @@ function EditEntryPage() {
10061
11067
  })
10062
11068
  })]
10063
11069
  }),
10064
- (() => {
10065
- let fields = [...schema.fields];
10066
- if (schema.upload) {
10067
- const hasAlt = fields.some((f) => f.name === "alt");
10068
- const hasCaption = fields.some((f) => f.name === "caption");
10069
- const mediaFields = [];
10070
- if (!hasAlt) mediaFields.push({
10071
- name: "alt",
10072
- type: "text",
10073
- label: "Alt Text",
10074
- admin: { description: "Describe the image for accessibility/screen readers." }
10075
- });
10076
- if (!hasCaption) mediaFields.push({
10077
- name: "caption",
10078
- type: "textarea",
10079
- label: "Caption",
10080
- admin: { description: "Add a caption/description for this media file." }
10081
- });
10082
- fields = [...mediaFields, ...fields];
10083
- }
11070
+ isConfiguringView ? /* @__PURE__ */ jsxs("div", {
11071
+ className: "dy-space-y-6",
11072
+ children: [/* @__PURE__ */ jsxs("div", {
11073
+ className: "dy-p-4 dy-rounded-xl",
11074
+ children: [/* @__PURE__ */ jsx("p", {
11075
+ className: "mb-2 dy-text-sm dy-text-muted-foreground",
11076
+ children: "Drag and drop fields to reorder the form layout. Changes will be saved as your personal preference or global default."
11077
+ }), /* @__PURE__ */ jsx("div", {
11078
+ className: "dy-space-y-2 dy-pr-1 dy-outline-none",
11079
+ children: /* @__PURE__ */ jsx(DndContext, {
11080
+ sensors,
11081
+ collisionDetection: closestCenter,
11082
+ onDragEnd: handleDragEnd,
11083
+ children: /* @__PURE__ */ jsx(SortableContext, {
11084
+ items: layout.map((item) => item.name),
11085
+ strategy: verticalListSortingStrategy,
11086
+ children: /* @__PURE__ */ jsx("div", {
11087
+ className: "dy-space-y-1.5",
11088
+ children: layout.map((item) => {
11089
+ const field = fieldsList.find((f) => f.name === item.name);
11090
+ if (!field) return null;
11091
+ return /* @__PURE__ */ jsx(SortableFieldItem, {
11092
+ id: item.name,
11093
+ label: field.label || field.name,
11094
+ type: field.type,
11095
+ width: item.width || "100%",
11096
+ onChangeWidth: (newWidth) => {
11097
+ setLayout((prev) => prev.map((x) => x.name === item.name ? {
11098
+ ...x,
11099
+ width: newWidth
11100
+ } : x));
11101
+ }
11102
+ }, item.name);
11103
+ })
11104
+ })
11105
+ })
11106
+ })
11107
+ })]
11108
+ }), /* @__PURE__ */ jsx("div", {
11109
+ className: "dy-sticky dy-bottom-0 dy-left-0 dy-right-0 dy-z-20 dy-pointer-events-none",
11110
+ children: /* @__PURE__ */ jsx("div", {
11111
+ className: "dy-pointer-events-auto dy-mx-auto dy-max-w-2xl dy-px-4 dy-pb-6",
11112
+ children: /* @__PURE__ */ jsxs("div", {
11113
+ className: "dy-flex dy-items-center dy-justify-between dy-gap-3 dy-rounded-2xl dy-border dy-border-border/50 dy-bg-background/80 dy-backdrop-blur-xl dy-px-4 dy-py-3 dy-shadow-xl dy-shadow-black/10 dy-animate-in dy-slide-in-from-bottom-2 dy-fade-in dy-duration-200",
11114
+ children: [/* @__PURE__ */ jsx("p", {
11115
+ className: "dy-text-sm dy-font-medium dy-text-muted-foreground",
11116
+ children: "View"
11117
+ }), /* @__PURE__ */ jsxs("div", {
11118
+ className: "dy-flex dy-items-center dy-gap-2",
11119
+ children: [
11120
+ /* @__PURE__ */ jsx(Button, {
11121
+ variant: "outline",
11122
+ size: "sm",
11123
+ className: "dy-rounded-xl",
11124
+ onClick: async () => {
11125
+ await resetLayout("personal");
11126
+ setIsConfiguringView(false);
11127
+ toast.success("Personal layout reset to default");
11128
+ },
11129
+ children: "Reset"
11130
+ }),
11131
+ isAdminUser && /* @__PURE__ */ jsx(Button, {
11132
+ variant: "outline",
11133
+ size: "sm",
11134
+ className: "dy-rounded-xl",
11135
+ onClick: async () => {
11136
+ await saveLayout("global");
11137
+ setIsConfiguringView(false);
11138
+ toast.success("Saved for everyone successfully");
11139
+ },
11140
+ children: "Save for Everyone"
11141
+ }),
11142
+ /* @__PURE__ */ jsx(Button, {
11143
+ size: "sm",
11144
+ className: "dy-rounded-xl dy-bg-primary dy-text-primary-foreground",
11145
+ onClick: async () => {
11146
+ await saveLayout("personal");
11147
+ setIsConfiguringView(false);
11148
+ toast.success("Saved for me successfully");
11149
+ },
11150
+ children: "Save for Me"
11151
+ }),
11152
+ /* @__PURE__ */ jsx(Button, {
11153
+ variant: "ghost",
11154
+ size: "sm",
11155
+ className: "dy-rounded-xl",
11156
+ onClick: () => {
11157
+ setLayout(reconciledLayout);
11158
+ setIsConfiguringView(false);
11159
+ },
11160
+ children: "Cancel"
11161
+ })
11162
+ ]
11163
+ })]
11164
+ })
11165
+ })
11166
+ })]
11167
+ }) : (() => {
10084
11168
  const queryParamsDefaults = {};
10085
11169
  if (!isEdit) searchParams.forEach((value, key) => {
10086
11170
  queryParamsDefaults[key] = value;
10087
11171
  });
10088
11172
  return /* @__PURE__ */ jsx(FormEngine, {
10089
11173
  collection: slug,
10090
- fields,
11174
+ fields: orderedFields,
10091
11175
  defaultValues: isEdit ? entry : {
10092
11176
  ...queryParamsDefaults,
10093
11177
  ...entry
@@ -10098,7 +11182,7 @@ function EditEntryPage() {
10098
11182
  ...newData
10099
11183
  }),
10100
11184
  onChange: (dirty) => setIsDirty(dirty),
10101
- isLoading: saveMutation.isPending,
11185
+ isLoading: saveMutation.isPending || isPreferenceLoading,
10102
11186
  submitLabel: isEdit ? "Save Changes" : "Create Entry",
10103
11187
  readOnly: isEdit ? !canUpdate : !canCreate,
10104
11188
  passwordChangeMode: isEdit ? passwordChangeMode : null,
@@ -10240,18 +11324,6 @@ CardFooter.displayName = "CardFooter";
10240
11324
  //#region src/components/ui/aspect-ratio.tsx
10241
11325
  var AspectRatio = AspectRatioPrimitive.Root;
10242
11326
  //#endregion
10243
- //#region src/components/ui/progress.tsx
10244
- var Progress = React$1.forwardRef(({ className, value, ...props }, ref) => /* @__PURE__ */ jsx(ProgressPrimitive.Root, {
10245
- ref,
10246
- className: cn("dy-relative dy-h-4 dy-w-full dy-overflow-hidden dy-rounded-full dy-bg-secondary", className),
10247
- ...props,
10248
- children: /* @__PURE__ */ jsx(ProgressPrimitive.Indicator, {
10249
- className: "dy-h-full dy-w-full dy-flex-1 dy-bg-primary dy-transition-all",
10250
- style: { transform: `translateX(-${100 - (value || 0)}%)` }
10251
- })
10252
- }));
10253
- Progress.displayName = ProgressPrimitive.Root.displayName;
10254
- //#endregion
10255
11327
  //#region src/components/ui/separator.tsx
10256
11328
  var Separator = React$1.forwardRef(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => /* @__PURE__ */ jsx(SeparatorPrimitive.Root, {
10257
11329
  ref,