From e5df0e102ba5832a0a13015dc0dacbf4216d58ca Mon Sep 17 00:00:00 2001 From: Hyko Date: Wed, 22 Apr 2026 17:30:48 -0400 Subject: [PATCH] style(ui): replace dark hover bg from neutral-950 to neutral-900 and use RelativeDate component in UsersPage --- src/features/admin/pages/UsersPage.client.js | 23 +----------- src/shared/components/Breadcrumb.js | 2 +- src/shared/components/FilterTabs.js | 2 +- src/shared/components/LoadingState.js | 2 +- src/shared/components/RelativeDate.client.js | 39 ++++++++++++++++++++ src/shared/components/Table.js | 2 +- src/shared/components/index.js | 1 + 7 files changed, 46 insertions(+), 25 deletions(-) create mode 100644 src/shared/components/RelativeDate.client.js diff --git a/src/features/admin/pages/UsersPage.client.js b/src/features/admin/pages/UsersPage.client.js index 3e5e545..b4591a0 100644 --- a/src/features/admin/pages/UsersPage.client.js +++ b/src/features/admin/pages/UsersPage.client.js @@ -1,7 +1,7 @@ 'use client'; import { useState, useEffect } from 'react'; -import { Card, Table, Badge, StatusBadge, Button, UserAvatar } from '@zen/core/shared/components'; +import { Card, Table, Badge, StatusBadge, Button, UserAvatar, RelativeDate } from '@zen/core/shared/components'; import { PencilEdit01Icon } from '@zen/core/shared/icons'; import { useToast } from '@zen/core/toast'; import AdminHeader from '../components/AdminHeader.js'; @@ -65,11 +65,7 @@ const UsersPageClient = () => { key: 'created_at', label: 'Créé le', sortable: true, - render: (user) => ( - - {formatDate(user.created_at)} - - ), + render: (user) => , skeleton: { height: 'h-4', width: '70%' }, }, { @@ -144,21 +140,6 @@ const UsersPageClient = () => { setSortOrder(newSortOrder); }; - const formatDate = (dateString) => { - if (!dateString) return 'N/A'; - try { - return new Date(dateString).toLocaleDateString('fr-FR', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', - }); - } catch { - return 'Date invalide'; - } - }; - return ( <> diff --git a/src/shared/components/Breadcrumb.js b/src/shared/components/Breadcrumb.js index ec4e7a3..e206e1c 100644 --- a/src/shared/components/Breadcrumb.js +++ b/src/shared/components/Breadcrumb.js @@ -25,7 +25,7 @@ const Breadcrumb = ({ items = [], className = '' }) => { item.active ? 'text-black dark:text-white cursor-default' : item.onClick - ? 'text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-950 cursor-pointer' + ? 'text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-900 cursor-pointer' : 'text-neutral-400 cursor-default', ].filter(Boolean).join(' ')} > diff --git a/src/shared/components/FilterTabs.js b/src/shared/components/FilterTabs.js index 9a7d56a..6ec157e 100644 --- a/src/shared/components/FilterTabs.js +++ b/src/shared/components/FilterTabs.js @@ -14,7 +14,7 @@ const FilterTabs = ({ tabs = [], value, onChange, className = ''}) => { index !== 0 ? 'border-l border-neutral-200 dark:border-[#1B1B1B]' : '', value === tab.key ? 'bg-neutral-100 dark:bg-neutral-700/40 text-neutral-900 dark:text-white' - : 'bg-white dark:bg-[#0B0B0B] text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-950', + : 'bg-white dark:bg-[#0B0B0B] text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-900', ].filter(Boolean).join(' ')} > {tab.label} diff --git a/src/shared/components/LoadingState.js b/src/shared/components/LoadingState.js index c6f2459..b1d29d5 100644 --- a/src/shared/components/LoadingState.js +++ b/src/shared/components/LoadingState.js @@ -67,7 +67,7 @@ export const TableSkeleton = ({ rows = 5, columns = 4 }) => ( {Array.from({ length: rows }).map((_, rowIndex) => ( - + {Array.from({ length: columns }).map((_, colIndex) => ( diff --git a/src/shared/components/RelativeDate.client.js b/src/shared/components/RelativeDate.client.js new file mode 100644 index 0000000..df05818 --- /dev/null +++ b/src/shared/components/RelativeDate.client.js @@ -0,0 +1,39 @@ +'use client'; + +const DEFAULT_CLASS = 'text-xs text-neutral-400 dark:text-gray-500 font-ibm-plex-mono'; + +function getRelativeTime(date) { + if (!date) return null; + const d = date instanceof Date ? date : new Date(date); + if (isNaN(d.getTime())) return null; + + const rtf = new Intl.RelativeTimeFormat('fr', { numeric: 'auto' }); + const diffMs = d.getTime() - Date.now(); + const diffSec = Math.round(diffMs / 1000); + const absMin = Math.abs(Math.round(diffSec / 60)); + const absH = Math.abs(Math.round(diffSec / 3600)); + const absDays = Math.abs(Math.round(diffSec / 86400)); + + if (Math.abs(diffSec) < 60) return rtf.format(diffSec, 'second'); + if (absMin < 60) return rtf.format(Math.round(diffSec / 60), 'minute'); + if (absH < 24) return rtf.format(Math.round(diffSec / 3600), 'hour'); + if (absDays < 7) return rtf.format(Math.round(diffSec / 86400), 'day'); + if (absDays < 30) return rtf.format(Math.round(diffSec / 604800), 'week'); + if (absDays < 365) return rtf.format(Math.round(diffSec / 2592000), 'month'); + return rtf.format(Math.round(diffSec / 31536000), 'year'); +} + +const RelativeDate = ({ date, className = DEFAULT_CLASS, ...props }) => { + const text = getRelativeTime(date); + if (!text) return null; + + const d = date instanceof Date ? date : new Date(date); + + return ( + + ); +}; + +export default RelativeDate; diff --git a/src/shared/components/Table.js b/src/shared/components/Table.js index 63e0733..0774885 100644 --- a/src/shared/components/Table.js +++ b/src/shared/components/Table.js @@ -60,7 +60,7 @@ const Table = ({ ); const SkeletonRow = () => ( - + {columns.map((column, index) => ( {column.skeleton ? ( diff --git a/src/shared/components/index.js b/src/shared/components/index.js index 94f29d9..7d464ed 100644 --- a/src/shared/components/index.js +++ b/src/shared/components/index.js @@ -28,3 +28,4 @@ export { default as Switch } from './Switch'; export { default as TagInput } from './TagInput'; export { default as UserAvatar } from './UserAvatar'; export { default as ColorPicker } from './ColorPicker.client'; +export { default as RelativeDate } from './RelativeDate.client';