@elsapiens/cli 0.1.4 → 0.1.5
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/templates/pages/list.ejs +79 -20
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useMemo, useCallback, useEffect } from 'react';
|
|
1
|
+
import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
|
2
2
|
import { useNavigate, useSearchParams, Link } from 'react-router-dom';
|
|
3
3
|
import { cn } from '@elsapiens/utils';
|
|
4
4
|
import { usePageHeader } from '@elsapiens/providers';
|
|
@@ -89,7 +89,36 @@ export function <%= pascalName %>ListPage({ className }: <%= pascalName %>ListPa
|
|
|
89
89
|
const [itemToDelete, setItemToDelete] = useState<<%= pascalName %>Row | null>(null);
|
|
90
90
|
|
|
91
91
|
// Loading state - set to true when fetching data
|
|
92
|
-
const [loading] = useState(false);
|
|
92
|
+
const [loading, setLoading] = useState(false);
|
|
93
|
+
// Minimum loading time to prevent flickering
|
|
94
|
+
const [showLoading, setShowLoading] = useState(false);
|
|
95
|
+
const loadingStartTimeRef = useRef<number | null>(null);
|
|
96
|
+
const MIN_LOADING_TIME = 300; // milliseconds
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (loading) {
|
|
100
|
+
// Start loading - record the start time
|
|
101
|
+
loadingStartTimeRef.current = Date.now();
|
|
102
|
+
setShowLoading(true);
|
|
103
|
+
} else if (loadingStartTimeRef.current !== null) {
|
|
104
|
+
// Loading finished - ensure minimum display time
|
|
105
|
+
const elapsed = Date.now() - loadingStartTimeRef.current;
|
|
106
|
+
const remaining = MIN_LOADING_TIME - elapsed;
|
|
107
|
+
|
|
108
|
+
if (remaining > 0) {
|
|
109
|
+
// Keep showing loading for the remaining time
|
|
110
|
+
const timer = setTimeout(() => {
|
|
111
|
+
setShowLoading(false);
|
|
112
|
+
loadingStartTimeRef.current = null;
|
|
113
|
+
}, remaining);
|
|
114
|
+
return () => clearTimeout(timer);
|
|
115
|
+
} else {
|
|
116
|
+
// Already past minimum time, hide immediately
|
|
117
|
+
setShowLoading(false);
|
|
118
|
+
loadingStartTimeRef.current = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}, [loading]);
|
|
93
122
|
|
|
94
123
|
// Collapsible info state (persisted in localStorage)
|
|
95
124
|
const [isInfoCollapsed, setIsInfoCollapsed] = useState(() => {
|
|
@@ -293,7 +322,7 @@ export function <%= pascalName %>ListPage({ className }: <%= pascalName %>ListPa
|
|
|
293
322
|
key: 'status',
|
|
294
323
|
header: 'Status',
|
|
295
324
|
render: (item: <%= pascalName %>Row) => (
|
|
296
|
-
<Badge className={cn(
|
|
325
|
+
<Badge className={cn(statusColors[item.status])}>
|
|
297
326
|
{statusLabels[item.status]}
|
|
298
327
|
</Badge>
|
|
299
328
|
),
|
|
@@ -408,21 +437,49 @@ export function <%= pascalName %>ListPage({ className }: <%= pascalName %>ListPa
|
|
|
408
437
|
/>
|
|
409
438
|
|
|
410
439
|
{/* Table */}
|
|
411
|
-
{
|
|
412
|
-
<div className="el-
|
|
413
|
-
|
|
414
|
-
<
|
|
415
|
-
<
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
440
|
+
{showLoading && paginatedData.length === 0 ? (
|
|
441
|
+
<div className="el-table-aligned" style={{ marginLeft: 'calc(var(--el-table-spacer-width, var(--el-container-px)) * -1)', marginRight: 'calc(var(--el-table-spacer-width, var(--el-container-px)) * -1)', marginBottom: 'calc(var(--el-table-spacer-width, var(--el-container-px)) * -1)', width: 'calc(100% + var(--el-table-spacer-width, var(--el-container-px)) * 2)' }}>
|
|
442
|
+
<table className="w-full border-collapse el-table">
|
|
443
|
+
<thead>
|
|
444
|
+
<tr className="border-b border-border bg-wisteria-100 dark:bg-wisteria-800">
|
|
445
|
+
<th className="p-0 border-none" style={{ width: 'var(--el-table-spacer-width)' }} />
|
|
446
|
+
<th className="font-medium text-foreground text-left" style={{ paddingLeft: 0, paddingRight: 'var(--table-cell-padding-x)', paddingTop: 'var(--table-header-padding-y)', paddingBottom: 'var(--table-header-padding-y)', fontSize: 'var(--table-font-size)' }}>Name</th>
|
|
447
|
+
<th className="font-medium text-foreground text-left" style={{ width: '120px', paddingLeft: 'var(--table-cell-padding-x)', paddingRight: 'var(--table-cell-padding-x)', paddingTop: 'var(--table-header-padding-y)', paddingBottom: 'var(--table-header-padding-y)', fontSize: 'var(--table-font-size)' }}>Category</th>
|
|
448
|
+
<th className="font-medium text-foreground text-left" style={{ width: '100px', paddingLeft: 'var(--table-cell-padding-x)', paddingRight: 'var(--table-cell-padding-x)', paddingTop: 'var(--table-header-padding-y)', paddingBottom: 'var(--table-header-padding-y)', fontSize: 'var(--table-font-size)' }}>Status</th>
|
|
449
|
+
<th className="font-medium text-foreground text-left" style={{ width: '120px', paddingLeft: 'var(--table-cell-padding-x)', paddingRight: 'var(--table-cell-padding-x)', paddingTop: 'var(--table-header-padding-y)', paddingBottom: 'var(--table-header-padding-y)', fontSize: 'var(--table-font-size)' }}>Created</th>
|
|
450
|
+
<th style={{ width: '100px', paddingLeft: 'var(--table-cell-padding-x)', paddingRight: 0, paddingTop: 'var(--table-header-padding-y)', paddingBottom: 'var(--table-header-padding-y)' }} />
|
|
451
|
+
<th className="p-0 border-none" style={{ width: 'var(--el-table-spacer-width)' }} />
|
|
452
|
+
</tr>
|
|
453
|
+
</thead>
|
|
454
|
+
<tbody>
|
|
455
|
+
{Array.from({ length: 5 }).map((_, i) => (
|
|
456
|
+
<tr key={i} className="border-b border-border">
|
|
457
|
+
<td className="p-0 border-none" />
|
|
458
|
+
<td style={{ paddingLeft: 0, paddingRight: 'var(--table-cell-padding-x)', paddingTop: 'var(--table-cell-padding-y)', paddingBottom: 'var(--table-cell-padding-y)' }}>
|
|
459
|
+
<div className="flex items-center el-gap-sm">
|
|
460
|
+
<Skeleton className="h-8 w-8 rounded flex-shrink-0" />
|
|
461
|
+
<Skeleton className="h-4 w-32" />
|
|
462
|
+
</div>
|
|
463
|
+
</td>
|
|
464
|
+
<td style={{ paddingLeft: 'var(--table-cell-padding-x)', paddingRight: 'var(--table-cell-padding-x)', paddingTop: 'var(--table-cell-padding-y)', paddingBottom: 'var(--table-cell-padding-y)' }}>
|
|
465
|
+
<Skeleton className="h-6 w-20 rounded-full" />
|
|
466
|
+
</td>
|
|
467
|
+
<td style={{ paddingLeft: 'var(--table-cell-padding-x)', paddingRight: 'var(--table-cell-padding-x)', paddingTop: 'var(--table-cell-padding-y)', paddingBottom: 'var(--table-cell-padding-y)' }}>
|
|
468
|
+
<Skeleton className="h-6 w-14 rounded-full" />
|
|
469
|
+
</td>
|
|
470
|
+
<td style={{ paddingLeft: 'var(--table-cell-padding-x)', paddingRight: 'var(--table-cell-padding-x)', paddingTop: 'var(--table-cell-padding-y)', paddingBottom: 'var(--table-cell-padding-y)' }}>
|
|
471
|
+
<Skeleton className="h-4 w-20" />
|
|
472
|
+
</td>
|
|
473
|
+
<td style={{ paddingLeft: 'var(--table-cell-padding-x)', paddingRight: 0, paddingTop: 'var(--table-cell-padding-y)', paddingBottom: 'var(--table-cell-padding-y)' }} />
|
|
474
|
+
<td className="p-0 border-none" />
|
|
475
|
+
</tr>
|
|
476
|
+
))}
|
|
477
|
+
</tbody>
|
|
478
|
+
</table>
|
|
422
479
|
</div>
|
|
423
480
|
) : paginatedData.length === 0 && hasActiveFilters ? (
|
|
424
481
|
/* Filtered empty state - no results for current filters */
|
|
425
|
-
<div className="flex-1 flex flex-col items-center justify-center el-gap-
|
|
482
|
+
<div className="flex-1 flex flex-col items-center justify-center el-gap-lg el-py-xl">
|
|
426
483
|
<div className="w-16 h-16 rounded-full bg-muted/50 flex items-center justify-center">
|
|
427
484
|
<SearchX className="w-8 h-8 text-muted-foreground" />
|
|
428
485
|
</div>
|
|
@@ -432,9 +489,11 @@ export function <%= pascalName %>ListPage({ className }: <%= pascalName %>ListPa
|
|
|
432
489
|
No <%= title.toLowerCase() %> match your current filters. Try adjusting your search or filters.
|
|
433
490
|
</p>
|
|
434
491
|
</div>
|
|
435
|
-
<
|
|
436
|
-
|
|
437
|
-
|
|
492
|
+
<div className="flex items-center el-gap-field">
|
|
493
|
+
<Button variant="outline" onClick={clearFilters}>
|
|
494
|
+
Clear filters
|
|
495
|
+
</Button>
|
|
496
|
+
</div>
|
|
438
497
|
</div>
|
|
439
498
|
) : paginatedData.length === 0 ? (
|
|
440
499
|
/* True empty state - no data at all */
|
|
@@ -495,8 +554,8 @@ export function <%= pascalName %>ListPage({ className }: <%= pascalName %>ListPa
|
|
|
495
554
|
</div>
|
|
496
555
|
)}
|
|
497
556
|
|
|
498
|
-
{/* Collapsible Page Info - shows when
|
|
499
|
-
{
|
|
557
|
+
{/* Collapsible Page Info - shows when data exists in the system */}
|
|
558
|
+
{sampleData.length > 0 && (
|
|
500
559
|
<div className="relative">
|
|
501
560
|
{isInfoCollapsed ? (
|
|
502
561
|
<button
|