@kyro-cms/admin 0.9.0 → 0.9.2
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.cjs +11715 -11292
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +67 -65
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +564 -0
- package/dist/index.d.ts +11 -10
- package/dist/index.js +11326 -10912
- package/dist/index.js.map +1 -1
- package/package.json +16 -12
- package/src/components/ActionBar.tsx +25 -161
- package/src/components/Admin.tsx +2 -4
- package/src/components/ApiKeysManager.tsx +5 -5
- package/src/components/AuditLogsPage.tsx +2 -13
- package/src/components/AutoForm.tsx +572 -461
- package/src/components/BrandingHub.tsx +7 -4
- package/src/components/CreateView.tsx +2 -0
- package/src/components/DetailView.tsx +52 -65
- package/src/components/DeveloperCenter.tsx +8 -6
- package/src/components/FieldRenderer.tsx +94 -19
- package/src/components/ListView.tsx +57 -216
- package/src/components/MediaGallery.tsx +334 -367
- package/src/components/PluginsManager.tsx +197 -70
- package/src/components/RestPlayground.tsx +59 -52
- package/src/components/SessionsManager.tsx +1 -1
- package/src/components/SettingsPage.tsx +22 -0
- package/src/components/Sidebar.astro +13 -41
- package/src/components/UserManagement.tsx +153 -15
- package/src/components/UserMenu.tsx +30 -4
- package/src/components/VersionHistoryPanel.tsx +112 -119
- package/src/components/WebhookManager.tsx +6 -4
- package/src/components/blocks/ArrayBlock.tsx +6 -23
- package/src/components/blocks/BlockEditModal.tsx +82 -309
- package/src/components/blocks/CardBlock.tsx +35 -0
- package/src/components/blocks/ChildBlocksTree.tsx +57 -31
- package/src/components/blocks/GenericBlock.tsx +44 -0
- package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
- package/src/components/blocks/HeroBlock.tsx +5 -14
- package/src/components/blocks/RichTextBlock.tsx +5 -5
- package/src/components/blocks/index.ts +5 -3
- package/src/components/fields/AccordionField.tsx +2 -2
- package/src/components/fields/ArrayField.tsx +1 -1
- package/src/components/fields/ArrayLayout.tsx +120 -29
- package/src/components/fields/BlocksField.tsx +433 -55
- package/src/components/fields/CardField.tsx +73 -0
- package/src/components/fields/CheckboxField.tsx +7 -3
- package/src/components/fields/DateField.tsx +4 -1
- package/src/components/fields/GroupLayout.tsx +2 -2
- package/src/components/fields/HeadingSubheadingField.tsx +43 -0
- package/src/components/fields/ListField.tsx +2 -2
- package/src/components/fields/NumberField.tsx +4 -1
- package/src/components/fields/RelationshipBlockField.tsx +2 -3
- package/src/components/fields/RelationshipField.tsx +155 -90
- package/src/components/fields/RichTextField.tsx +781 -0
- package/src/components/fields/SecretField.tsx +102 -0
- package/src/components/fields/SelectField.tsx +19 -6
- package/src/components/fields/TabsLayout.tsx +19 -9
- package/src/components/fields/TextField.tsx +4 -1
- package/src/components/fields/UploadField.tsx +122 -56
- package/src/components/fields/extensions/blockComponents.tsx +103 -174
- package/src/components/fields/extensions/blocksStore.ts +8 -1
- package/src/components/fields/index.ts +4 -2
- package/src/components/fix_imports.cjs +23 -0
- package/src/components/fix_imports2.cjs +19 -0
- package/src/components/replace_svgs.cjs +63 -0
- package/src/components/ui/Dropdown.tsx +7 -2
- package/src/components/ui/Modal.tsx +24 -27
- package/src/components/ui/PageHeader.tsx +5 -5
- package/src/components/ui/PromptModal.tsx +2 -10
- package/src/components/ui/SlidePanel.tsx +10 -13
- package/src/components/ui/SplitButton.tsx +107 -0
- package/src/components/ui/Toaster.tsx +0 -1
- package/src/components/ui/icons.tsx +110 -109
- package/src/components/users/UserDetail.tsx +79 -16
- package/src/components/users/UsersList.tsx +8 -85
- package/src/hooks/useAutoFormState.ts +187 -196
- package/src/hooks/useQueue.ts +60 -0
- package/src/integration.ts +148 -46
- package/src/kyro-cms.d.ts +7 -2
- package/src/layouts/AdminLayout.astro +22 -2
- package/src/layouts/AuthLayout.astro +67 -7
- package/src/lib/autoform-store.ts +90 -53
- package/src/lib/change-source.ts +9 -0
- package/src/lib/config.ts +104 -8
- package/src/lib/globals.ts +48 -11
- package/src/lib/normalize-upload-fields.ts +41 -0
- package/src/lib/paths.ts +2 -2
- package/src/lib/resolve-field-value.ts +110 -0
- package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
- package/src/lib/shim/use-sync-external-store.js +1 -0
- package/src/lib/stores/index.ts +1 -0
- package/src/lib/useResourceManager.ts +4 -4
- package/src/lib/vite-shim-plugin.ts +100 -0
- package/src/pages/[collection]/[id].astro +1 -1
- package/src/pages/auth/register.astro +5 -2
- package/src/pages/preview/[collection]/[id].astro +4 -4
- package/src/pages/settings/[slug].astro +2 -2
- package/src/styles/main.css +60 -54
- package/README.md +0 -46
- package/dist/EditorClient-Q23UXR37.cjs +0 -468
- package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
- package/dist/EditorClient-T5PASFNR.js +0 -466
- package/dist/EditorClient-T5PASFNR.js.map +0 -1
- package/dist/chunk-3BGDYKTD.cjs +0 -348
- package/dist/chunk-3BGDYKTD.cjs.map +0 -1
- package/dist/chunk-EEFXLQVT.js +0 -3
- package/dist/chunk-EEFXLQVT.js.map +0 -1
- package/src/components/blocks/ButtonBlock.tsx +0 -64
- package/src/components/blocks/ColumnsBlock.tsx +0 -55
- package/src/components/blocks/DividerBlock.tsx +0 -43
- package/src/components/blocks/LinkBlock.tsx +0 -65
- package/src/components/blocks/VStackBlock.tsx +0 -29
- package/src/components/fields/EditorClient.tsx +0 -535
- package/src/components/fields/PortableTextField.tsx +0 -155
- package/src/components/fields/PortableTextRenderer.tsx +0 -68
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
import { Search, Filter, Columns3, X, Trash2, Archive, ChevronUp, Edit2 } from "./ui/icons";
|
|
1
2
|
import { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
|
2
3
|
import { Spinner } from "./ui/Spinner";
|
|
3
4
|
import { Shimmer } from "./ui/Shimmer";
|
|
4
5
|
import { Plus } from "./ui/icons";
|
|
5
6
|
import { apiGet, apiDelete, withCacheBust } from "../lib/api";
|
|
6
7
|
|
|
7
|
-
import { useAuthStore } from "../lib/stores";
|
|
8
|
+
import { useAuthStore, toast } from "../lib/stores";
|
|
8
9
|
import { useUIStore } from "../lib/stores";
|
|
9
10
|
import { adminPath as ADMIN_BASE } from "../lib/paths";
|
|
10
11
|
import { PageHeader } from "./ui/PageHeader";
|
|
11
12
|
import { Badge } from "./ui/Badge";
|
|
13
|
+
import { Pagination } from "./ui/Pagination";
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
import type { CollectionConfig, Field } from "@kyro-cms/core";
|
|
17
|
+
import { resolveFieldValue } from "../lib/resolve-field-value";
|
|
15
18
|
|
|
16
19
|
type FieldConfig = Field;
|
|
17
20
|
|
|
@@ -114,7 +117,7 @@ export function ListView({
|
|
|
114
117
|
function flattenFields(fields: FieldConfig[]): FieldConfig[] {
|
|
115
118
|
const result: FieldConfig[] = [];
|
|
116
119
|
for (const field of fields || []) {
|
|
117
|
-
if (!field.name || field.admin?.hidden || field.name === "id") continue;
|
|
120
|
+
if (!field.name || field.hidden === true || field.admin?.hidden || field.name === "id") continue;
|
|
118
121
|
if (field.type === "tabs" && field.tabs) {
|
|
119
122
|
for (const tab of field.tabs) {
|
|
120
123
|
if (tab.fields) {
|
|
@@ -197,7 +200,21 @@ export function ListView({
|
|
|
197
200
|
}, []);
|
|
198
201
|
|
|
199
202
|
const displayFields = useMemo(
|
|
200
|
-
() =>
|
|
203
|
+
() => {
|
|
204
|
+
const fields = allFields.filter((f): f is typeof f & { name: string } => !!f.name && visibleColumns.has(f.name));
|
|
205
|
+
if (visibleColumns.has("status")) {
|
|
206
|
+
fields.push({
|
|
207
|
+
name: "status",
|
|
208
|
+
type: "select",
|
|
209
|
+
label: "Status",
|
|
210
|
+
options: [
|
|
211
|
+
{ value: "draft", label: "Draft" },
|
|
212
|
+
{ value: "published", label: "Published" },
|
|
213
|
+
],
|
|
214
|
+
} as any);
|
|
215
|
+
}
|
|
216
|
+
return fields;
|
|
217
|
+
},
|
|
201
218
|
[allFields, visibleColumns],
|
|
202
219
|
);
|
|
203
220
|
|
|
@@ -211,21 +228,8 @@ export function ListView({
|
|
|
211
228
|
|
|
212
229
|
function extractFieldValue(doc: any, field: FieldConfig): any {
|
|
213
230
|
if (!field.name) return null;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
if (field.type === "group" && typeof doc[field.name] === "object") {
|
|
218
|
-
const firstFieldName = field.fields?.[0]?.name;
|
|
219
|
-
if (
|
|
220
|
-
firstFieldName &&
|
|
221
|
-
doc[field.name][firstFieldName] !== undefined
|
|
222
|
-
) {
|
|
223
|
-
return doc[field.name][firstFieldName];
|
|
224
|
-
}
|
|
225
|
-
const firstKey = Object.keys(doc[field.name] || {})[0];
|
|
226
|
-
if (firstKey) return doc[field.name][firstKey];
|
|
227
|
-
}
|
|
228
|
-
return null;
|
|
231
|
+
const val = resolveFieldValue(collection.fields as any, doc, field.name);
|
|
232
|
+
return val ?? null;
|
|
229
233
|
}
|
|
230
234
|
|
|
231
235
|
const fetchDocs = useCallback(async () => {
|
|
@@ -303,9 +307,10 @@ export function ListView({
|
|
|
303
307
|
}
|
|
304
308
|
setSelectedIds(new Set());
|
|
305
309
|
fetchDocs();
|
|
310
|
+
toast.success("Documents deleted");
|
|
306
311
|
} catch (error) {
|
|
307
312
|
console.error("Bulk delete failed:", error);
|
|
308
|
-
|
|
313
|
+
toast.error("Failed to delete some documents");
|
|
309
314
|
}
|
|
310
315
|
}
|
|
311
316
|
});
|
|
@@ -320,9 +325,10 @@ export function ListView({
|
|
|
320
325
|
try {
|
|
321
326
|
await apiDelete(`/api/${collectionSlug}/${id}`);
|
|
322
327
|
fetchDocs();
|
|
328
|
+
toast.success("Document deleted");
|
|
323
329
|
} catch (error) {
|
|
324
330
|
console.error("Delete failed:", error);
|
|
325
|
-
|
|
331
|
+
toast.error("Failed to delete document");
|
|
326
332
|
}
|
|
327
333
|
}
|
|
328
334
|
});
|
|
@@ -354,19 +360,7 @@ export function ListView({
|
|
|
354
360
|
<div className="surface-tile p-4 flex flex-col lg:flex-row gap-4 items-start lg:items-center">
|
|
355
361
|
{/* Search */}
|
|
356
362
|
<div className="relative flex-1 max-w-md">
|
|
357
|
-
<
|
|
358
|
-
className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--kyro-text-secondary)]"
|
|
359
|
-
fill="none"
|
|
360
|
-
stroke="currentColor"
|
|
361
|
-
viewBox="0 0 24 24"
|
|
362
|
-
>
|
|
363
|
-
<path
|
|
364
|
-
strokeLinecap="round"
|
|
365
|
-
strokeLinejoin="round"
|
|
366
|
-
strokeWidth="2"
|
|
367
|
-
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
368
|
-
/>
|
|
369
|
-
</svg>
|
|
363
|
+
<Search className="w-4 h-4" />
|
|
370
364
|
<input
|
|
371
365
|
type="text"
|
|
372
366
|
placeholder="Search..."
|
|
@@ -386,19 +380,7 @@ export function ListView({
|
|
|
386
380
|
: "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)]"
|
|
387
381
|
}`}
|
|
388
382
|
>
|
|
389
|
-
<
|
|
390
|
-
className="w-4 h-4"
|
|
391
|
-
fill="none"
|
|
392
|
-
stroke="currentColor"
|
|
393
|
-
viewBox="0 0 24 24"
|
|
394
|
-
>
|
|
395
|
-
<path
|
|
396
|
-
strokeLinecap="round"
|
|
397
|
-
strokeLinejoin="round"
|
|
398
|
-
strokeWidth="2"
|
|
399
|
-
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
|
|
400
|
-
/>
|
|
401
|
-
</svg>
|
|
383
|
+
<Filter className="w-4 h-4" />
|
|
402
384
|
Filters
|
|
403
385
|
{filters.length > 0 && (
|
|
404
386
|
<span className="ml-1 px-1.5 py-0.5 bg-[var(--kyro-sidebar-text-active)] text-[var(--kyro-sidebar-active)] rounded-full text-xs">
|
|
@@ -414,19 +396,7 @@ export function ListView({
|
|
|
414
396
|
onClick={() => setShowColumns(!showColumns)}
|
|
415
397
|
className="flex items-center gap-2 px-4 py-2 rounded-xl font-bold text-sm bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] transition-all"
|
|
416
398
|
>
|
|
417
|
-
<
|
|
418
|
-
className="w-4 h-4"
|
|
419
|
-
fill="none"
|
|
420
|
-
stroke="currentColor"
|
|
421
|
-
viewBox="0 0 24 24"
|
|
422
|
-
>
|
|
423
|
-
<path
|
|
424
|
-
strokeLinecap="round"
|
|
425
|
-
strokeLinejoin="round"
|
|
426
|
-
strokeWidth="2"
|
|
427
|
-
d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"
|
|
428
|
-
/>
|
|
429
|
-
</svg>
|
|
399
|
+
<Columns3 className="w-4 h-4" />
|
|
430
400
|
Columns
|
|
431
401
|
</button>
|
|
432
402
|
{showColumns && (
|
|
@@ -486,19 +456,7 @@ export function ListView({
|
|
|
486
456
|
onClick={addFilter}
|
|
487
457
|
className="flex items-center gap-2 px-3 py-1.5 text-sm font-bold text-[var(--kyro-sidebar-active)] hover:bg-[var(--kyro-surface-accent)] rounded-lg transition-all"
|
|
488
458
|
>
|
|
489
|
-
<
|
|
490
|
-
className="w-4 h-4"
|
|
491
|
-
fill="none"
|
|
492
|
-
stroke="currentColor"
|
|
493
|
-
viewBox="0 0 24 24"
|
|
494
|
-
>
|
|
495
|
-
<path
|
|
496
|
-
strokeLinecap="round"
|
|
497
|
-
strokeLinejoin="round"
|
|
498
|
-
strokeWidth="2"
|
|
499
|
-
d="M12 5v14M5 12h14"
|
|
500
|
-
/>
|
|
501
|
-
</svg>
|
|
459
|
+
<Plus className="w-4 h-4" />
|
|
502
460
|
Add Filter
|
|
503
461
|
</button>
|
|
504
462
|
</div>
|
|
@@ -548,19 +506,7 @@ export function ListView({
|
|
|
548
506
|
onClick={() => removeFilter(index)}
|
|
549
507
|
className="p-2 text-[var(--kyro-text-muted)] hover:text-red-500 transition-colors"
|
|
550
508
|
>
|
|
551
|
-
<
|
|
552
|
-
className="w-4 h-4"
|
|
553
|
-
fill="none"
|
|
554
|
-
stroke="currentColor"
|
|
555
|
-
viewBox="0 0 24 24"
|
|
556
|
-
>
|
|
557
|
-
<path
|
|
558
|
-
strokeLinecap="round"
|
|
559
|
-
strokeLinejoin="round"
|
|
560
|
-
strokeWidth="2"
|
|
561
|
-
d="M6 18L18 6M6 6l12 12"
|
|
562
|
-
/>
|
|
563
|
-
</svg>
|
|
509
|
+
<X className="w-4 h-4" />
|
|
564
510
|
</button>
|
|
565
511
|
</div>
|
|
566
512
|
))}
|
|
@@ -586,19 +532,7 @@ export function ListView({
|
|
|
586
532
|
onClick={handleBulkDelete}
|
|
587
533
|
className="flex items-center gap-2 px-4 py-2 bg-red-500 text-white rounded-lg font-bold text-sm hover:bg-red-600 transition-all"
|
|
588
534
|
>
|
|
589
|
-
<
|
|
590
|
-
className="w-4 h-4"
|
|
591
|
-
fill="none"
|
|
592
|
-
stroke="currentColor"
|
|
593
|
-
viewBox="0 0 24 24"
|
|
594
|
-
>
|
|
595
|
-
<path
|
|
596
|
-
strokeLinecap="round"
|
|
597
|
-
strokeLinejoin="round"
|
|
598
|
-
strokeWidth="2"
|
|
599
|
-
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
600
|
-
/>
|
|
601
|
-
</svg>
|
|
535
|
+
<Trash2 className="w-4 h-4" />
|
|
602
536
|
Delete Selected
|
|
603
537
|
</button>
|
|
604
538
|
)}
|
|
@@ -622,19 +556,7 @@ export function ListView({
|
|
|
622
556
|
) : docs.length === 0 ? (
|
|
623
557
|
<div className="flex flex-col items-center justify-center py-16 px-8">
|
|
624
558
|
<div className="w-16 h-16 rounded-2xl bg-[var(--kyro-surface-accent)] flex items-center justify-center mb-4">
|
|
625
|
-
<
|
|
626
|
-
className="w-8 h-8 text-[var(--kyro-text-muted)]"
|
|
627
|
-
fill="none"
|
|
628
|
-
stroke="currentColor"
|
|
629
|
-
viewBox="0 0 24 24"
|
|
630
|
-
>
|
|
631
|
-
<path
|
|
632
|
-
strokeLinecap="round"
|
|
633
|
-
strokeLinejoin="round"
|
|
634
|
-
strokeWidth="1.5"
|
|
635
|
-
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
|
|
636
|
-
/>
|
|
637
|
-
</svg>
|
|
559
|
+
<Archive className="w-4 h-4" />
|
|
638
560
|
</div>
|
|
639
561
|
<p className="font-medium text-[var(--kyro-text-primary)] text-base">
|
|
640
562
|
No documents found
|
|
@@ -650,19 +572,7 @@ export function ListView({
|
|
|
650
572
|
onClick={handleCreate}
|
|
651
573
|
className="mt-4 kyro-btn kyro-btn-md kyro-btn-primary shadow-md flex items-center gap-2"
|
|
652
574
|
>
|
|
653
|
-
<
|
|
654
|
-
className="w-3.5 h-3.5"
|
|
655
|
-
fill="none"
|
|
656
|
-
stroke="currentColor"
|
|
657
|
-
viewBox="0 0 24 24"
|
|
658
|
-
>
|
|
659
|
-
<path
|
|
660
|
-
strokeLinecap="round"
|
|
661
|
-
strokeLinejoin="round"
|
|
662
|
-
strokeWidth="2.5"
|
|
663
|
-
d="M12 5v14M5 12h14"
|
|
664
|
-
/>
|
|
665
|
-
</svg>
|
|
575
|
+
<Plus className="w-4 h-4" />
|
|
666
576
|
Create{" "}
|
|
667
577
|
{String(collection.singularLabel || collection.label || collectionSlug)}
|
|
668
578
|
</button>
|
|
@@ -693,19 +603,7 @@ export function ListView({
|
|
|
693
603
|
{checkTabbedValue(displayFields, field.type) ??
|
|
694
604
|
(field.label || field.name)}
|
|
695
605
|
{sort && sort.field === field.name && (
|
|
696
|
-
<
|
|
697
|
-
className={`w-3 h-3 ${sort.direction === "desc" ? "rotate-180" : ""}`}
|
|
698
|
-
fill="none"
|
|
699
|
-
stroke="currentColor"
|
|
700
|
-
viewBox="0 0 24 24"
|
|
701
|
-
>
|
|
702
|
-
<path
|
|
703
|
-
strokeLinecap="round"
|
|
704
|
-
strokeLinejoin="round"
|
|
705
|
-
strokeWidth="2"
|
|
706
|
-
d="M5 15l7-7 7 7"
|
|
707
|
-
/>
|
|
708
|
-
</svg>
|
|
606
|
+
<ChevronUp className="w-4 h-4" />
|
|
709
607
|
)}
|
|
710
608
|
</div>
|
|
711
609
|
</th>
|
|
@@ -775,19 +673,7 @@ export function ListView({
|
|
|
775
673
|
className="flex items-center gap-2 px-3 py-1.5 hover:bg-[var(--kyro-surface-accent)] rounded-lg text-sm font-bold text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] transition-all"
|
|
776
674
|
title={canUpdate ? "Edit" : "View"}
|
|
777
675
|
>
|
|
778
|
-
<
|
|
779
|
-
className="w-4 h-4"
|
|
780
|
-
fill="none"
|
|
781
|
-
stroke="currentColor"
|
|
782
|
-
viewBox="0 0 24 24"
|
|
783
|
-
>
|
|
784
|
-
<path
|
|
785
|
-
strokeLinecap="round"
|
|
786
|
-
strokeLinejoin="round"
|
|
787
|
-
strokeWidth="2"
|
|
788
|
-
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
|
789
|
-
/>
|
|
790
|
-
</svg>
|
|
676
|
+
<Edit2 className="w-4 h-4" />
|
|
791
677
|
</button>
|
|
792
678
|
{canDelete && (
|
|
793
679
|
<button
|
|
@@ -796,19 +682,7 @@ export function ListView({
|
|
|
796
682
|
className="inline-flex items-center justify-center w-8 h-8 rounded-md text-[var(--kyro-text-muted)] hover:bg-red-50 hover:text-red-500 dark:hover:bg-red-500/10 transition-colors"
|
|
797
683
|
title="Delete"
|
|
798
684
|
>
|
|
799
|
-
<
|
|
800
|
-
className="w-4 h-4"
|
|
801
|
-
fill="none"
|
|
802
|
-
stroke="currentColor"
|
|
803
|
-
viewBox="0 0 24 24"
|
|
804
|
-
>
|
|
805
|
-
<path
|
|
806
|
-
strokeLinecap="round"
|
|
807
|
-
strokeLinejoin="round"
|
|
808
|
-
strokeWidth="2"
|
|
809
|
-
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
810
|
-
/>
|
|
811
|
-
</svg>
|
|
685
|
+
<Trash2 className="w-4 h-4" />
|
|
812
686
|
</button>
|
|
813
687
|
)}
|
|
814
688
|
</div>
|
|
@@ -822,59 +696,17 @@ export function ListView({
|
|
|
822
696
|
</div>
|
|
823
697
|
|
|
824
698
|
{/* Pagination */}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
</span>{" "}
|
|
837
|
-
of{" "}
|
|
838
|
-
<span className="text-[var(--kyro-text-primary)] font-bold">
|
|
839
|
-
{totalDocs}
|
|
840
|
-
</span>
|
|
841
|
-
</span>
|
|
842
|
-
<select
|
|
843
|
-
value={limit}
|
|
844
|
-
onChange={(e) => {
|
|
845
|
-
setLimit(Number(e.target.value));
|
|
846
|
-
setPage(1);
|
|
847
|
-
}}
|
|
848
|
-
className="px-2 py-1 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-lg text-sm font-medium text-[var(--kyro-text-primary)]"
|
|
849
|
-
>
|
|
850
|
-
<option value={10}>10 / page</option>
|
|
851
|
-
<option value={25}>25 / page</option>
|
|
852
|
-
<option value={50}>50 / page</option>
|
|
853
|
-
<option value={100}>100 / page</option>
|
|
854
|
-
</select>
|
|
855
|
-
</div>
|
|
856
|
-
<div className="flex gap-2">
|
|
857
|
-
{page > 1 && (
|
|
858
|
-
<button
|
|
859
|
-
type="button"
|
|
860
|
-
onClick={() => setPage(page - 1)}
|
|
861
|
-
className="px-4 py-2 border border-[var(--kyro-border)] rounded-lg text-sm font-medium text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] transition-colors"
|
|
862
|
-
>
|
|
863
|
-
← Previous
|
|
864
|
-
</button>
|
|
865
|
-
)}
|
|
866
|
-
{page < totalPages && (
|
|
867
|
-
<button
|
|
868
|
-
type="button"
|
|
869
|
-
onClick={() => setPage(page + 1)}
|
|
870
|
-
className="px-4 py-2 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-lg text-sm font-bold hover:opacity-90 transition-all"
|
|
871
|
-
>
|
|
872
|
-
Next →
|
|
873
|
-
</button>
|
|
874
|
-
)}
|
|
875
|
-
</div>
|
|
876
|
-
</div>
|
|
877
|
-
)}
|
|
699
|
+
<Pagination
|
|
700
|
+
page={page}
|
|
701
|
+
totalPages={totalPages}
|
|
702
|
+
totalDocs={totalDocs}
|
|
703
|
+
limit={limit}
|
|
704
|
+
onPageChange={setPage}
|
|
705
|
+
onLimitChange={(newLimit) => {
|
|
706
|
+
setLimit(newLimit);
|
|
707
|
+
setPage(1);
|
|
708
|
+
}}
|
|
709
|
+
/>
|
|
878
710
|
</div>
|
|
879
711
|
);
|
|
880
712
|
}
|
|
@@ -891,6 +723,15 @@ function formatCellValue(value: any, type?: string): string {
|
|
|
891
723
|
});
|
|
892
724
|
}
|
|
893
725
|
|
|
726
|
+
if (Array.isArray(value)) {
|
|
727
|
+
return value.map(item => {
|
|
728
|
+
if (item && typeof item === "object") {
|
|
729
|
+
return item.title || item.name || item.email || item.filename || item.url || JSON.stringify(item).slice(0, 30);
|
|
730
|
+
}
|
|
731
|
+
return String(item ?? "").slice(0, 30);
|
|
732
|
+
}).filter(Boolean).join(", ");
|
|
733
|
+
}
|
|
734
|
+
|
|
894
735
|
if (typeof value === "object") {
|
|
895
736
|
if (value.title) return value.title;
|
|
896
737
|
if (value.name) return value.name;
|