style(timesheet): add loading skeleton and soften dark surfaces
This commit is contained in:
@@ -1583,7 +1583,7 @@ function RecordedEntryCard({
|
||||
<div
|
||||
ref={rowRef}
|
||||
onBlurCapture={handleBlurCapture}
|
||||
className="border-b border-slate-200 bg-white px-4 py-4 dark:border-slate-800 dark:bg-slate-950"
|
||||
className="border-b border-slate-200 bg-white px-4 py-4 dark:border-slate-700 dark:bg-slate-900/95"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<EntryEditorFields
|
||||
@@ -1598,7 +1598,7 @@ function RecordedEntryCard({
|
||||
portalOwnerId={editorOwnerId}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between gap-3 border-t border-slate-200 pt-3 dark:border-slate-800">
|
||||
<div className="flex items-center justify-between gap-3 border-t border-slate-200 pt-3 dark:border-slate-700">
|
||||
<div className="text-sm text-slate-500 dark:text-slate-400">
|
||||
{formatDateTime(entry.start_time, lang)}
|
||||
</div>
|
||||
@@ -1634,7 +1634,7 @@ function RecordedEntryCard({
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={rowRef} onBlurCapture={handleBlurCapture} className="border-b border-slate-200 bg-white px-4 py-4 dark:border-slate-800 dark:bg-slate-950">
|
||||
<div ref={rowRef} onBlurCapture={handleBlurCapture} className="border-b border-slate-200 bg-white px-4 py-4 dark:border-slate-700 dark:bg-slate-900/95">
|
||||
<div className="flex min-w-0 items-center">
|
||||
<EntryEditorFields
|
||||
state={draft}
|
||||
@@ -1649,20 +1649,20 @@ function RecordedEntryCard({
|
||||
portalOwnerId={editorOwnerId}
|
||||
/>
|
||||
|
||||
<div className="flex h-12 shrink-0 items-center border-s border-slate-200 px-5 text-sm font-semibold text-slate-700 dark:border-slate-800 dark:text-slate-200">
|
||||
<div className="flex h-12 shrink-0 items-center border-s border-slate-200 px-5 text-sm font-semibold text-slate-700 dark:border-slate-700 dark:text-slate-200">
|
||||
{formatDuration(entry)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onRestart(entry)}
|
||||
className="inline-flex h-12 w-10 shrink-0 items-center justify-center border-s border-slate-200 bg-transparent text-slate-400 transition-colors hover:bg-slate-50 hover:text-slate-700 dark:border-slate-800 dark:text-slate-500 dark:hover:bg-slate-900 dark:hover:text-white"
|
||||
className="inline-flex h-12 w-10 shrink-0 items-center justify-center border-s border-slate-200 bg-transparent text-slate-400 transition-colors hover:bg-slate-50 hover:text-slate-700 dark:border-slate-700 dark:text-slate-500 dark:hover:bg-slate-800 dark:hover:text-white"
|
||||
title="Start from this entry"
|
||||
>
|
||||
<Play className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div className="border-s border-slate-200 dark:border-slate-800">
|
||||
<div className="border-s border-slate-200 dark:border-slate-700">
|
||||
<DeleteEntryButton onDelete={() => onDelete(entry)} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -1676,7 +1676,7 @@ function RecordedEntryCard({
|
||||
);
|
||||
}
|
||||
|
||||
function MobileRecordedEntryCard({
|
||||
function MobileRecordedEntryCard({
|
||||
entry,
|
||||
t,
|
||||
projects,
|
||||
@@ -1791,7 +1791,7 @@ function MobileRecordedEntryCard({
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} className="relative overflow-hidden border-b border-slate-200 bg-slate-100/70 dark:border-slate-800 dark:bg-slate-900/70 xl:hidden">
|
||||
<div ref={wrapperRef} className="relative overflow-hidden border-b border-slate-200 bg-slate-100/70 dark:border-slate-700 dark:bg-slate-800/70 xl:hidden">
|
||||
<div className="pointer-events-none absolute inset-y-0 left-0 flex w-24 items-center justify-start bg-emerald-500/12 ps-4 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-300">
|
||||
<Play className="h-4 w-4" />
|
||||
</div>
|
||||
@@ -1800,7 +1800,7 @@ function MobileRecordedEntryCard({
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="relative bg-white px-4 py-5 transition-transform duration-150 ease-out dark:bg-slate-950"
|
||||
className="relative bg-white px-4 py-5 transition-transform duration-150 ease-out dark:bg-slate-900/95"
|
||||
style={{ transform: `translateX(${swipeOffset}px)` }}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
@@ -1914,10 +1914,101 @@ function MobileRecordedEntryCard({
|
||||
document.body,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Timesheet() {
|
||||
);
|
||||
}
|
||||
|
||||
function TimesheetSkeleton({ loadingLabel }: { loadingLabel: string }) {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col gap-4 animate-pulse">
|
||||
<div className="hidden rounded-xl border border-slate-200 bg-white shadow-sm dark:border-slate-700 dark:bg-slate-900/95 xl:block">
|
||||
<div className="flex items-center gap-2 px-3 py-3">
|
||||
<div className="h-12 flex-1 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-12 w-52 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-12 w-48 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-12 w-10 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-12 w-28 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-12 w-12 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-slate-200 bg-white p-3 shadow-sm dark:border-slate-700 dark:bg-slate-900/95 xl:hidden">
|
||||
<div className="space-y-3">
|
||||
<div className="h-10 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="grid grid-cols-[minmax(0,1fr)_auto] gap-2">
|
||||
<div className="h-10 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-10 w-28 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<div className="h-9 w-28 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-9 w-10 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-10 w-10 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-10 w-10 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-slate-200 bg-white p-2.5 shadow-sm dark:border-slate-700 dark:bg-slate-900/95">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-9 flex-1 rounded-md bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-9 w-9 rounded-md bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-9 w-9 rounded-md bg-slate-200 dark:bg-slate-800" />
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="h-6 w-24 rounded-full bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-6 w-28 rounded-full bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-6 w-20 rounded-full bg-slate-200 dark:bg-slate-800" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-1 flex items-center gap-3 text-slate-500 dark:text-slate-400">
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-sky-500" />
|
||||
<span className="text-sm font-medium">{loadingLabel}</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{[0, 1].map((weekIndex) => (
|
||||
<div key={weekIndex} className="space-y-2">
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<div className="h-4 w-40 rounded-full bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="h-4 w-28 rounded-full bg-slate-200 dark:bg-slate-800" />
|
||||
</div>
|
||||
{[0, 1].map((dayIndex) => (
|
||||
<div
|
||||
key={dayIndex}
|
||||
className="overflow-hidden border border-slate-200 bg-white shadow-sm dark:border-slate-700 dark:bg-slate-900/95"
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-slate-200 bg-slate-100/80 px-4 py-2 dark:border-slate-700 dark:bg-slate-800/85">
|
||||
<div className="h-3 w-24 rounded-full bg-slate-200 dark:bg-slate-700" />
|
||||
<div className="h-3 w-20 rounded-full bg-slate-200 dark:bg-slate-700" />
|
||||
</div>
|
||||
<div className="space-y-px bg-slate-200/80 dark:bg-slate-700/70">
|
||||
{[0, 1].map((entryIndex) => (
|
||||
<div key={entryIndex} className="bg-white px-4 py-4 dark:bg-slate-900/95">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="h-10 flex-1 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="hidden h-10 w-28 rounded-lg bg-slate-200 dark:bg-slate-800 md:block" />
|
||||
<div className="h-10 w-10 rounded-lg bg-slate-200 dark:bg-slate-800" />
|
||||
<div className="hidden h-10 w-20 rounded-lg bg-slate-200 dark:bg-slate-800 lg:block" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Timesheet() {
|
||||
const { t, lang } = useTranslation();
|
||||
const { activeWorkspace } = useWorkspace();
|
||||
const isRtl = lang === "fa";
|
||||
@@ -2473,7 +2564,7 @@ export default function Timesheet() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-[calc(100vh-73px)] flex-col bg-slate-100/70 p-4 dark:bg-slate-900">
|
||||
<div className="flex min-h-[calc(100vh-73px)] flex-col bg-slate-100/70 p-4 dark:bg-slate-900">
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8 gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">{t.timesheet?.title || 'Timesheets'}</h1>
|
||||
@@ -2482,9 +2573,9 @@ export default function Timesheet() {
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={desktopTimerRef}
|
||||
onBlurCapture={handleTimerBlurCapture}
|
||||
className="mb-4 hidden rounded-xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-950 xl:block"
|
||||
ref={desktopTimerRef}
|
||||
onBlurCapture={handleTimerBlurCapture}
|
||||
className="mb-4 hidden rounded-xl border border-slate-200 bg-white shadow-sm dark:border-slate-700 dark:bg-slate-900/95 xl:block"
|
||||
>
|
||||
<div className="flex min-w-0 items-center gap-2 px-3 py-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
@@ -2591,9 +2682,9 @@ export default function Timesheet() {
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={mobileTimerRef}
|
||||
onBlurCapture={handleTimerBlurCapture}
|
||||
className="mb-4 rounded-xl border border-slate-200 bg-white p-3 shadow-sm dark:border-slate-800 dark:bg-slate-950 xl:hidden"
|
||||
ref={mobileTimerRef}
|
||||
onBlurCapture={handleTimerBlurCapture}
|
||||
className="mb-4 rounded-xl border border-slate-200 bg-white p-3 shadow-sm dark:border-slate-700 dark:bg-slate-900/95 xl:hidden"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<Input
|
||||
@@ -2728,11 +2819,11 @@ export default function Timesheet() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center p-12 text-slate-500">{t.loading || "Loading..."}</div>
|
||||
) : (
|
||||
<InfiniteScroll
|
||||
className="flex flex-1 flex-col"
|
||||
{isLoading ? (
|
||||
<TimesheetSkeleton loadingLabel={t.loading || "Loading..."} />
|
||||
) : (
|
||||
<InfiniteScroll
|
||||
className="flex flex-1 flex-col"
|
||||
onLoadMore={handleLoadMore}
|
||||
hasMore={hasMoreHistory}
|
||||
isLoading={isLoadingMore}
|
||||
@@ -2750,8 +2841,8 @@ export default function Timesheet() {
|
||||
</div>
|
||||
|
||||
{week.days.map((day) => (
|
||||
<div key={day.key} className="border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-950">
|
||||
<div className="flex items-center justify-between border-b border-slate-200 bg-slate-100/80 px-4 py-2 dark:border-slate-800 dark:bg-slate-900">
|
||||
<div key={day.key} className="border border-slate-200 bg-white shadow-sm dark:border-slate-700 dark:bg-slate-900/95">
|
||||
<div className="flex items-center justify-between border-b border-slate-200 bg-slate-100/80 px-4 py-2 dark:border-slate-700 dark:bg-slate-800/85">
|
||||
<p className="text-xs font-medium text-slate-500 dark:text-slate-400">
|
||||
{formatDayLabel(new Date(`${day.date}T00:00:00`), lang)}
|
||||
</p>
|
||||
@@ -2795,11 +2886,11 @@ export default function Timesheet() {
|
||||
</div>
|
||||
))}
|
||||
|
||||
{groupedHistory.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center border-2 border-dashed border-slate-200 py-16 text-slate-500 dark:border-slate-800 dark:text-slate-400">
|
||||
<Clock3 className="mb-3 h-10 w-10" />
|
||||
<p>{t.timesheet?.emptyState || "No time entries found"}</p>
|
||||
</div>
|
||||
{groupedHistory.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center border-2 border-dashed border-slate-200 bg-white/60 py-16 text-slate-500 dark:border-slate-700 dark:bg-slate-900/60 dark:text-slate-400">
|
||||
<Clock3 className="mb-3 h-10 w-10" />
|
||||
<p>{t.timesheet?.emptyState || "No time entries found"}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</InfiniteScroll>
|
||||
@@ -2853,7 +2944,7 @@ export default function Timesheet() {
|
||||
<p className="text-sm leading-relaxed text-slate-600 dark:text-slate-400">
|
||||
{extendedTimesheet.deleteConfirmMessage || t.timesheet?.deleteConfirmMessage || "Are you sure you want to delete this time entry?"}
|
||||
</p>
|
||||
<div className="rounded-2xl border border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-900/60">
|
||||
<div className="rounded-2xl border border-slate-200 bg-slate-50 p-4 dark:border-slate-700 dark:bg-slate-800/60">
|
||||
<p className="font-medium text-slate-900 dark:text-white">
|
||||
{deleteModal.entry.description || t.timesheet?.emptyDescription || "No description"}
|
||||
</p>
|
||||
@@ -2887,7 +2978,7 @@ export default function Timesheet() {
|
||||
<p className="text-sm leading-relaxed text-slate-600 dark:text-slate-400">
|
||||
{extendedTimesheet.restartConfirmMessage || t.timesheet?.restartConfirmMessage || "Start a new running timer from this entry?"}
|
||||
</p>
|
||||
<div className="rounded-2xl border border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-900/60">
|
||||
<div className="rounded-2xl border border-slate-200 bg-slate-50 p-4 dark:border-slate-700 dark:bg-slate-800/60">
|
||||
<p className="font-medium text-slate-900 dark:text-white">
|
||||
{restartModal.entry.description || t.timesheet?.emptyDescription || "No description"}
|
||||
</p>
|
||||
@@ -2921,7 +3012,7 @@ export default function Timesheet() {
|
||||
<p className="text-sm leading-relaxed text-slate-600 dark:text-slate-400">
|
||||
{extendedTimesheet.discardConfirmMessage || t.timesheet?.discardConfirmMessage || "Are you sure you want to discard this running timer?"}
|
||||
</p>
|
||||
<div className="rounded-2xl border border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-900/60">
|
||||
<div className="rounded-2xl border border-slate-200 bg-slate-50 p-4 dark:border-slate-700 dark:bg-slate-800/60">
|
||||
<p className="font-medium text-slate-900 dark:text-white">
|
||||
{discardTimerModal.entry.description || t.timesheet?.emptyDescription || "No description"}
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user