@kyro-cms/admin 0.9.1 → 0.9.3
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 +1196 -1727
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +891 -1422
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/ActionBar.tsx +25 -174
- package/src/components/Admin.tsx +1 -3
- package/src/components/AuditLogsPage.tsx +2 -13
- package/src/components/AutoForm.tsx +160 -265
- package/src/components/DetailView.tsx +38 -66
- package/src/components/FieldRenderer.tsx +1 -1
- package/src/components/ListView.tsx +26 -198
- package/src/components/MediaGallery.tsx +117 -175
- package/src/components/RestPlayground.tsx +54 -47
- package/src/components/fields/BlocksField.tsx +8 -10
- package/src/components/fields/RelationshipBlockField.tsx +2 -3
- package/src/components/fields/RelationshipField.tsx +2 -3
- 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/PromptModal.tsx +2 -10
- package/src/components/ui/SlidePanel.tsx +2 -10
- package/src/components/ui/SplitButton.tsx +107 -0
- package/src/components/ui/Toaster.tsx +0 -1
- package/src/components/ui/icons.tsx +1 -0
- package/src/components/users/UsersList.tsx +8 -85
- package/src/hooks/useAutoFormState.ts +89 -161
- package/src/hooks/useQueue.ts +60 -0
- package/src/layouts/AdminLayout.astro +22 -2
- package/src/layouts/AuthLayout.astro +66 -18
- package/src/lib/autoform-store.ts +6 -2
- package/src/lib/globals.ts +5 -3
- package/src/pages/auth/register.astro +5 -1
|
@@ -9,10 +9,10 @@ import { AutoForm } from "./AutoForm";
|
|
|
9
9
|
import { ActionBar, type DocumentStatus, type SaveStatus } from "./ActionBar";
|
|
10
10
|
import { Spinner } from "./ui/Spinner";
|
|
11
11
|
import { Shimmer } from "./ui/Shimmer";
|
|
12
|
-
import {
|
|
13
|
-
import { useUIStore } from "../lib/stores";
|
|
12
|
+
import { useUIStore, toast } from "../lib/stores";
|
|
14
13
|
import { PageHeader } from "./ui/PageHeader";
|
|
15
14
|
import { Badge } from "./ui/Badge";
|
|
15
|
+
import { SplitButton } from "./ui/SplitButton";
|
|
16
16
|
import { adminPath } from "../lib/paths";
|
|
17
17
|
import { resolveFieldValue } from "../lib/resolve-field-value";
|
|
18
18
|
|
|
@@ -40,7 +40,6 @@ export function DetailView({
|
|
|
40
40
|
onError,
|
|
41
41
|
mode = "collection",
|
|
42
42
|
}: DetailViewProps) {
|
|
43
|
-
const { addToast } = useToast();
|
|
44
43
|
const { confirm, alert } = useUIStore();
|
|
45
44
|
const [data, setData] = useState<Record<string, unknown>>({});
|
|
46
45
|
const [originalData, setOriginalData] = useState<Record<string, unknown>>({});
|
|
@@ -95,7 +94,7 @@ export function DetailView({
|
|
|
95
94
|
const docData = result.data || {};
|
|
96
95
|
setData(docData);
|
|
97
96
|
setOriginalData(docData);
|
|
98
|
-
setStatus(((docData as any)?.
|
|
97
|
+
setStatus(((docData as any)?.status || result.status || "draft") as DocumentStatus);
|
|
99
98
|
setCreatedAt(result.createdAt || (docData.createdAt as string) || null);
|
|
100
99
|
setUpdatedAt(result.updatedAt || (docData.updatedAt as string) || null);
|
|
101
100
|
setPublishedAt(result.publishedAt || (docData.publishedAt as string) || null);
|
|
@@ -135,7 +134,8 @@ export function DetailView({
|
|
|
135
134
|
? `/api/globals/${slug}`
|
|
136
135
|
: `/api/${slug}/${documentId}`;
|
|
137
136
|
|
|
138
|
-
const
|
|
137
|
+
const isDraft = status === "draft" || (data as any)?.status === "draft";
|
|
138
|
+
const result = (await apiPatch(endpoint, data, { autoToast: false, headers: { "X-Draft": String(isDraft) } }) as { data?: Record<string, unknown> });
|
|
139
139
|
const savedData = (result && (result.data || result)) || data;
|
|
140
140
|
|
|
141
141
|
if (!isAutosave) {
|
|
@@ -144,7 +144,7 @@ export function DetailView({
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
setData(savedData);
|
|
147
|
-
setStatus((savedData as any)?.
|
|
147
|
+
setStatus((savedData as any)?.status || status);
|
|
148
148
|
setSaveStatus("saved");
|
|
149
149
|
setUpdatedAt(new Date().toISOString());
|
|
150
150
|
|
|
@@ -153,8 +153,9 @@ export function DetailView({
|
|
|
153
153
|
setTimeout(() => setJustSaved(false), 3000);
|
|
154
154
|
|
|
155
155
|
if (!isAutosave) {
|
|
156
|
-
const isDraft = status === "draft" || (savedData as any)?.
|
|
157
|
-
|
|
156
|
+
const isDraft = status === "draft" || (savedData as any)?.status === "draft";
|
|
157
|
+
if (isDraft) toast.warning("Draft saved");
|
|
158
|
+
else toast.success("Updated");
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
setTimeout(() => {
|
|
@@ -164,25 +165,29 @@ export function DetailView({
|
|
|
164
165
|
setSaveStatus("error");
|
|
165
166
|
if (!isAutosave) {
|
|
166
167
|
onError("Failed to save changes");
|
|
167
|
-
|
|
168
|
+
toast.error("Failed to save changes");
|
|
168
169
|
}
|
|
169
170
|
} finally {
|
|
170
171
|
setSaving(false);
|
|
171
172
|
}
|
|
172
173
|
},
|
|
173
|
-
[data, mode, slug, documentId, onSave, onError],
|
|
174
|
+
[data, mode, slug, documentId, status, onSave, onError],
|
|
174
175
|
);
|
|
175
176
|
|
|
176
177
|
const handlePublish = async () => {
|
|
177
178
|
try {
|
|
178
179
|
setSaving(true);
|
|
179
|
-
await
|
|
180
|
+
await apiPatch(`/api/${slug}/${documentId}`, data, {
|
|
181
|
+
autoToast: false,
|
|
182
|
+
headers: { "X-Draft": "false" },
|
|
183
|
+
} as any);
|
|
180
184
|
setStatus("published");
|
|
181
185
|
setPublishedAt(new Date().toISOString());
|
|
182
|
-
|
|
186
|
+
toast.success("Published successfully");
|
|
187
|
+
onSave();
|
|
183
188
|
} catch {
|
|
184
189
|
onError("Failed to publish");
|
|
185
|
-
|
|
190
|
+
toast.error("Failed to publish");
|
|
186
191
|
} finally {
|
|
187
192
|
setSaving(false);
|
|
188
193
|
}
|
|
@@ -191,12 +196,16 @@ export function DetailView({
|
|
|
191
196
|
const handleUnpublish = async () => {
|
|
192
197
|
try {
|
|
193
198
|
setSaving(true);
|
|
194
|
-
await
|
|
199
|
+
await apiPatch(`/api/${slug}/${documentId}`, { status: 'draft' }, {
|
|
200
|
+
autoToast: false,
|
|
201
|
+
headers: { "X-Draft": "false" },
|
|
202
|
+
} as any);
|
|
195
203
|
setStatus("draft");
|
|
196
|
-
|
|
204
|
+
toast.warning("Document unpublished");
|
|
205
|
+
onSave();
|
|
197
206
|
} catch {
|
|
198
207
|
onError("Failed to unpublish");
|
|
199
|
-
|
|
208
|
+
toast.error("Failed to unpublish");
|
|
200
209
|
} finally {
|
|
201
210
|
setSaving(false);
|
|
202
211
|
}
|
|
@@ -206,11 +215,11 @@ export function DetailView({
|
|
|
206
215
|
try {
|
|
207
216
|
setSaving(true);
|
|
208
217
|
await apiPost(`/api/${slug}/${documentId}/duplicate`, undefined, { autoToast: false });
|
|
209
|
-
|
|
218
|
+
toast.success("Document duplicated");
|
|
210
219
|
} catch (err: unknown) {
|
|
211
220
|
const message = err instanceof Error ? err.message : "Failed to duplicate document";
|
|
212
221
|
onError(message);
|
|
213
|
-
|
|
222
|
+
toast.error(message);
|
|
214
223
|
} finally {
|
|
215
224
|
setSaving(false);
|
|
216
225
|
}
|
|
@@ -226,10 +235,10 @@ export function DetailView({
|
|
|
226
235
|
setDeleting(true);
|
|
227
236
|
await apiDelete(`/api/${slug}/${documentId}`, { autoToast: false });
|
|
228
237
|
onDelete?.();
|
|
229
|
-
|
|
238
|
+
toast.error("Document deleted");
|
|
230
239
|
} catch (err: unknown) {
|
|
231
240
|
const message = err instanceof Error ? err.message : "Failed to delete document";
|
|
232
|
-
|
|
241
|
+
toast.error(message);
|
|
233
242
|
} finally {
|
|
234
243
|
setDeleting(false);
|
|
235
244
|
}
|
|
@@ -331,8 +340,8 @@ export function DetailView({
|
|
|
331
340
|
layout={isSingleLayout ? "single" : "split"}
|
|
332
341
|
globalSlug={mode === "global" ? slug : undefined}
|
|
333
342
|
collectionSlug={mode === "collection" ? slug : undefined}
|
|
334
|
-
onActionSuccess={(message) =>
|
|
335
|
-
onActionError={(message) =>
|
|
343
|
+
onActionSuccess={(message) => toast.success(message)}
|
|
344
|
+
onActionError={(message) => toast.error(message)}
|
|
336
345
|
documentStatus={status}
|
|
337
346
|
justSaved={justSaved}
|
|
338
347
|
/>
|
|
@@ -347,50 +356,13 @@ export function DetailView({
|
|
|
347
356
|
Delete
|
|
348
357
|
</button>
|
|
349
358
|
)}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
) : (
|
|
358
|
-
<button
|
|
359
|
-
type="button"
|
|
360
|
-
onClick={() => handleSave(false)}
|
|
361
|
-
disabled={saving}
|
|
362
|
-
className="kyro-btn kyro-btn-lg kyro-btn-primary shadow-xl flex items-center gap-2"
|
|
363
|
-
>
|
|
364
|
-
{saving ? (
|
|
365
|
-
<svg
|
|
366
|
-
className="w-4 h-4 animate-spin"
|
|
367
|
-
viewBox="0 0 24 24"
|
|
368
|
-
fill="none"
|
|
369
|
-
stroke="currentColor"
|
|
370
|
-
strokeWidth="2"
|
|
371
|
-
>
|
|
372
|
-
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
373
|
-
</svg>
|
|
374
|
-
) : (
|
|
375
|
-
<svg
|
|
376
|
-
className="w-4 h-4"
|
|
377
|
-
viewBox="0 0 24 24"
|
|
378
|
-
fill="none"
|
|
379
|
-
stroke="currentColor"
|
|
380
|
-
strokeWidth="2"
|
|
381
|
-
>
|
|
382
|
-
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
|
|
383
|
-
<polyline points="17 21 17 13 7 13 7 21" />
|
|
384
|
-
<polyline points="7 3 7 8 15 8" />
|
|
385
|
-
</svg>
|
|
386
|
-
)}
|
|
387
|
-
{saving
|
|
388
|
-
? "Saving..."
|
|
389
|
-
: mode === "global"
|
|
390
|
-
? "Save Configuration"
|
|
391
|
-
: "Save Document"}
|
|
392
|
-
</button>
|
|
393
|
-
)}
|
|
359
|
+
<SplitButton
|
|
360
|
+
status={status}
|
|
361
|
+
saveStatus={saving ? "saving" : "idle"}
|
|
362
|
+
hasChanges={hasChanges}
|
|
363
|
+
onPublish={() => handleSave(false)}
|
|
364
|
+
disabled={saving}
|
|
365
|
+
/>
|
|
394
366
|
</div>
|
|
395
367
|
)}
|
|
396
368
|
</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
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";
|
|
@@ -9,6 +10,7 @@ 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";
|
|
@@ -200,9 +202,9 @@ export function ListView({
|
|
|
200
202
|
const displayFields = useMemo(
|
|
201
203
|
() => {
|
|
202
204
|
const fields = allFields.filter((f): f is typeof f & { name: string } => !!f.name && visibleColumns.has(f.name));
|
|
203
|
-
if (visibleColumns.has("
|
|
205
|
+
if (visibleColumns.has("status")) {
|
|
204
206
|
fields.push({
|
|
205
|
-
name: "
|
|
207
|
+
name: "status",
|
|
206
208
|
type: "select",
|
|
207
209
|
label: "Status",
|
|
208
210
|
options: [
|
|
@@ -358,19 +360,7 @@ export function ListView({
|
|
|
358
360
|
<div className="surface-tile p-4 flex flex-col lg:flex-row gap-4 items-start lg:items-center">
|
|
359
361
|
{/* Search */}
|
|
360
362
|
<div className="relative flex-1 max-w-md">
|
|
361
|
-
<
|
|
362
|
-
className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--kyro-text-secondary)]"
|
|
363
|
-
fill="none"
|
|
364
|
-
stroke="currentColor"
|
|
365
|
-
viewBox="0 0 24 24"
|
|
366
|
-
>
|
|
367
|
-
<path
|
|
368
|
-
strokeLinecap="round"
|
|
369
|
-
strokeLinejoin="round"
|
|
370
|
-
strokeWidth="2"
|
|
371
|
-
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
372
|
-
/>
|
|
373
|
-
</svg>
|
|
363
|
+
<Search className="w-4 h-4" />
|
|
374
364
|
<input
|
|
375
365
|
type="text"
|
|
376
366
|
placeholder="Search..."
|
|
@@ -390,19 +380,7 @@ export function ListView({
|
|
|
390
380
|
: "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)]"
|
|
391
381
|
}`}
|
|
392
382
|
>
|
|
393
|
-
<
|
|
394
|
-
className="w-4 h-4"
|
|
395
|
-
fill="none"
|
|
396
|
-
stroke="currentColor"
|
|
397
|
-
viewBox="0 0 24 24"
|
|
398
|
-
>
|
|
399
|
-
<path
|
|
400
|
-
strokeLinecap="round"
|
|
401
|
-
strokeLinejoin="round"
|
|
402
|
-
strokeWidth="2"
|
|
403
|
-
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"
|
|
404
|
-
/>
|
|
405
|
-
</svg>
|
|
383
|
+
<Filter className="w-4 h-4" />
|
|
406
384
|
Filters
|
|
407
385
|
{filters.length > 0 && (
|
|
408
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">
|
|
@@ -418,19 +396,7 @@ export function ListView({
|
|
|
418
396
|
onClick={() => setShowColumns(!showColumns)}
|
|
419
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"
|
|
420
398
|
>
|
|
421
|
-
<
|
|
422
|
-
className="w-4 h-4"
|
|
423
|
-
fill="none"
|
|
424
|
-
stroke="currentColor"
|
|
425
|
-
viewBox="0 0 24 24"
|
|
426
|
-
>
|
|
427
|
-
<path
|
|
428
|
-
strokeLinecap="round"
|
|
429
|
-
strokeLinejoin="round"
|
|
430
|
-
strokeWidth="2"
|
|
431
|
-
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"
|
|
432
|
-
/>
|
|
433
|
-
</svg>
|
|
399
|
+
<Columns3 className="w-4 h-4" />
|
|
434
400
|
Columns
|
|
435
401
|
</button>
|
|
436
402
|
{showColumns && (
|
|
@@ -490,19 +456,7 @@ export function ListView({
|
|
|
490
456
|
onClick={addFilter}
|
|
491
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"
|
|
492
458
|
>
|
|
493
|
-
<
|
|
494
|
-
className="w-4 h-4"
|
|
495
|
-
fill="none"
|
|
496
|
-
stroke="currentColor"
|
|
497
|
-
viewBox="0 0 24 24"
|
|
498
|
-
>
|
|
499
|
-
<path
|
|
500
|
-
strokeLinecap="round"
|
|
501
|
-
strokeLinejoin="round"
|
|
502
|
-
strokeWidth="2"
|
|
503
|
-
d="M12 5v14M5 12h14"
|
|
504
|
-
/>
|
|
505
|
-
</svg>
|
|
459
|
+
<Plus className="w-4 h-4" />
|
|
506
460
|
Add Filter
|
|
507
461
|
</button>
|
|
508
462
|
</div>
|
|
@@ -552,19 +506,7 @@ export function ListView({
|
|
|
552
506
|
onClick={() => removeFilter(index)}
|
|
553
507
|
className="p-2 text-[var(--kyro-text-muted)] hover:text-red-500 transition-colors"
|
|
554
508
|
>
|
|
555
|
-
<
|
|
556
|
-
className="w-4 h-4"
|
|
557
|
-
fill="none"
|
|
558
|
-
stroke="currentColor"
|
|
559
|
-
viewBox="0 0 24 24"
|
|
560
|
-
>
|
|
561
|
-
<path
|
|
562
|
-
strokeLinecap="round"
|
|
563
|
-
strokeLinejoin="round"
|
|
564
|
-
strokeWidth="2"
|
|
565
|
-
d="M6 18L18 6M6 6l12 12"
|
|
566
|
-
/>
|
|
567
|
-
</svg>
|
|
509
|
+
<X className="w-4 h-4" />
|
|
568
510
|
</button>
|
|
569
511
|
</div>
|
|
570
512
|
))}
|
|
@@ -590,19 +532,7 @@ export function ListView({
|
|
|
590
532
|
onClick={handleBulkDelete}
|
|
591
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"
|
|
592
534
|
>
|
|
593
|
-
<
|
|
594
|
-
className="w-4 h-4"
|
|
595
|
-
fill="none"
|
|
596
|
-
stroke="currentColor"
|
|
597
|
-
viewBox="0 0 24 24"
|
|
598
|
-
>
|
|
599
|
-
<path
|
|
600
|
-
strokeLinecap="round"
|
|
601
|
-
strokeLinejoin="round"
|
|
602
|
-
strokeWidth="2"
|
|
603
|
-
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"
|
|
604
|
-
/>
|
|
605
|
-
</svg>
|
|
535
|
+
<Trash2 className="w-4 h-4" />
|
|
606
536
|
Delete Selected
|
|
607
537
|
</button>
|
|
608
538
|
)}
|
|
@@ -626,19 +556,7 @@ export function ListView({
|
|
|
626
556
|
) : docs.length === 0 ? (
|
|
627
557
|
<div className="flex flex-col items-center justify-center py-16 px-8">
|
|
628
558
|
<div className="w-16 h-16 rounded-2xl bg-[var(--kyro-surface-accent)] flex items-center justify-center mb-4">
|
|
629
|
-
<
|
|
630
|
-
className="w-8 h-8 text-[var(--kyro-text-muted)]"
|
|
631
|
-
fill="none"
|
|
632
|
-
stroke="currentColor"
|
|
633
|
-
viewBox="0 0 24 24"
|
|
634
|
-
>
|
|
635
|
-
<path
|
|
636
|
-
strokeLinecap="round"
|
|
637
|
-
strokeLinejoin="round"
|
|
638
|
-
strokeWidth="1.5"
|
|
639
|
-
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"
|
|
640
|
-
/>
|
|
641
|
-
</svg>
|
|
559
|
+
<Archive className="w-4 h-4" />
|
|
642
560
|
</div>
|
|
643
561
|
<p className="font-medium text-[var(--kyro-text-primary)] text-base">
|
|
644
562
|
No documents found
|
|
@@ -654,19 +572,7 @@ export function ListView({
|
|
|
654
572
|
onClick={handleCreate}
|
|
655
573
|
className="mt-4 kyro-btn kyro-btn-md kyro-btn-primary shadow-md flex items-center gap-2"
|
|
656
574
|
>
|
|
657
|
-
<
|
|
658
|
-
className="w-3.5 h-3.5"
|
|
659
|
-
fill="none"
|
|
660
|
-
stroke="currentColor"
|
|
661
|
-
viewBox="0 0 24 24"
|
|
662
|
-
>
|
|
663
|
-
<path
|
|
664
|
-
strokeLinecap="round"
|
|
665
|
-
strokeLinejoin="round"
|
|
666
|
-
strokeWidth="2.5"
|
|
667
|
-
d="M12 5v14M5 12h14"
|
|
668
|
-
/>
|
|
669
|
-
</svg>
|
|
575
|
+
<Plus className="w-4 h-4" />
|
|
670
576
|
Create{" "}
|
|
671
577
|
{String(collection.singularLabel || collection.label || collectionSlug)}
|
|
672
578
|
</button>
|
|
@@ -697,19 +603,7 @@ export function ListView({
|
|
|
697
603
|
{checkTabbedValue(displayFields, field.type) ??
|
|
698
604
|
(field.label || field.name)}
|
|
699
605
|
{sort && sort.field === field.name && (
|
|
700
|
-
<
|
|
701
|
-
className={`w-3 h-3 ${sort.direction === "desc" ? "rotate-180" : ""}`}
|
|
702
|
-
fill="none"
|
|
703
|
-
stroke="currentColor"
|
|
704
|
-
viewBox="0 0 24 24"
|
|
705
|
-
>
|
|
706
|
-
<path
|
|
707
|
-
strokeLinecap="round"
|
|
708
|
-
strokeLinejoin="round"
|
|
709
|
-
strokeWidth="2"
|
|
710
|
-
d="M5 15l7-7 7 7"
|
|
711
|
-
/>
|
|
712
|
-
</svg>
|
|
606
|
+
<ChevronUp className="w-4 h-4" />
|
|
713
607
|
)}
|
|
714
608
|
</div>
|
|
715
609
|
</th>
|
|
@@ -779,19 +673,7 @@ export function ListView({
|
|
|
779
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"
|
|
780
674
|
title={canUpdate ? "Edit" : "View"}
|
|
781
675
|
>
|
|
782
|
-
<
|
|
783
|
-
className="w-4 h-4"
|
|
784
|
-
fill="none"
|
|
785
|
-
stroke="currentColor"
|
|
786
|
-
viewBox="0 0 24 24"
|
|
787
|
-
>
|
|
788
|
-
<path
|
|
789
|
-
strokeLinecap="round"
|
|
790
|
-
strokeLinejoin="round"
|
|
791
|
-
strokeWidth="2"
|
|
792
|
-
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"
|
|
793
|
-
/>
|
|
794
|
-
</svg>
|
|
676
|
+
<Edit2 className="w-4 h-4" />
|
|
795
677
|
</button>
|
|
796
678
|
{canDelete && (
|
|
797
679
|
<button
|
|
@@ -800,19 +682,7 @@ export function ListView({
|
|
|
800
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"
|
|
801
683
|
title="Delete"
|
|
802
684
|
>
|
|
803
|
-
<
|
|
804
|
-
className="w-4 h-4"
|
|
805
|
-
fill="none"
|
|
806
|
-
stroke="currentColor"
|
|
807
|
-
viewBox="0 0 24 24"
|
|
808
|
-
>
|
|
809
|
-
<path
|
|
810
|
-
strokeLinecap="round"
|
|
811
|
-
strokeLinejoin="round"
|
|
812
|
-
strokeWidth="2"
|
|
813
|
-
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"
|
|
814
|
-
/>
|
|
815
|
-
</svg>
|
|
685
|
+
<Trash2 className="w-4 h-4" />
|
|
816
686
|
</button>
|
|
817
687
|
)}
|
|
818
688
|
</div>
|
|
@@ -826,59 +696,17 @@ export function ListView({
|
|
|
826
696
|
</div>
|
|
827
697
|
|
|
828
698
|
{/* Pagination */}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
</span>{" "}
|
|
841
|
-
of{" "}
|
|
842
|
-
<span className="text-[var(--kyro-text-primary)] font-bold">
|
|
843
|
-
{totalDocs}
|
|
844
|
-
</span>
|
|
845
|
-
</span>
|
|
846
|
-
<select
|
|
847
|
-
value={limit}
|
|
848
|
-
onChange={(e) => {
|
|
849
|
-
setLimit(Number(e.target.value));
|
|
850
|
-
setPage(1);
|
|
851
|
-
}}
|
|
852
|
-
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)]"
|
|
853
|
-
>
|
|
854
|
-
<option value={10}>10 / page</option>
|
|
855
|
-
<option value={25}>25 / page</option>
|
|
856
|
-
<option value={50}>50 / page</option>
|
|
857
|
-
<option value={100}>100 / page</option>
|
|
858
|
-
</select>
|
|
859
|
-
</div>
|
|
860
|
-
<div className="flex gap-2">
|
|
861
|
-
{page > 1 && (
|
|
862
|
-
<button
|
|
863
|
-
type="button"
|
|
864
|
-
onClick={() => setPage(page - 1)}
|
|
865
|
-
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"
|
|
866
|
-
>
|
|
867
|
-
← Previous
|
|
868
|
-
</button>
|
|
869
|
-
)}
|
|
870
|
-
{page < totalPages && (
|
|
871
|
-
<button
|
|
872
|
-
type="button"
|
|
873
|
-
onClick={() => setPage(page + 1)}
|
|
874
|
-
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"
|
|
875
|
-
>
|
|
876
|
-
Next →
|
|
877
|
-
</button>
|
|
878
|
-
)}
|
|
879
|
-
</div>
|
|
880
|
-
</div>
|
|
881
|
-
)}
|
|
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
|
+
/>
|
|
882
710
|
</div>
|
|
883
711
|
);
|
|
884
712
|
}
|