feat: update loading state handling in DutyList component

- Replaced the loading skeleton with a compact loading placeholder to improve user experience when data is not yet loaded for the month.
- Enhanced the rendering logic to ensure the loading state is visually distinct and does not display the skeleton when data is being fetched.
- Updated related tests to verify the new loading behavior and ensure accurate feedback during data fetching.
This commit is contained in:
2026-03-03 17:42:03 +03:00
parent edf0186682
commit 50d734e192
3 changed files with 6 additions and 10 deletions

View File

@@ -42,12 +42,13 @@ describe("DutyList", () => {
expect(screen.getByText(/No duties this month/i)).toBeInTheDocument(); expect(screen.getByText(/No duties this month/i)).toBeInTheDocument();
}); });
it("renders quiet placeholder when data not yet loaded for month", () => { it("renders compact loading placeholder when data not yet loaded for month (no skeleton)", () => {
useAppStore.getState().setDuties([]); useAppStore.getState().setDuties([]);
useAppStore.getState().batchUpdate({ dataForMonthKey: null }); useAppStore.getState().batchUpdate({ dataForMonthKey: null });
render(<DutyList />); render(<DutyList />);
expect(screen.queryByText(/No duties this month/i)).not.toBeInTheDocument(); expect(screen.queryByText(/No duties this month/i)).not.toBeInTheDocument();
expect(document.querySelector('[aria-busy="true"]')).toBeInTheDocument(); expect(document.querySelector('[aria-busy="true"]')).toBeInTheDocument();
expect(document.querySelector('[data-slot="skeleton"]')).not.toBeInTheDocument();
}); });
it("renders duty with full_name and time range", () => { it("renders duty with full_name and time range", () => {

View File

@@ -17,6 +17,7 @@ import {
} from "@/lib/date-utils"; } from "@/lib/date-utils";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { LoadingState } from "@/components/states/LoadingState";
import { DutyTimelineCard } from "./DutyTimelineCard"; import { DutyTimelineCard } from "./DutyTimelineCard";
/** Extra offset so the sticky calendar slightly overlaps the target card (card sits a bit under the calendar). */ /** Extra offset so the sticky calendar slightly overlaps the target card (card sits a bit under the calendar). */
@@ -146,14 +147,8 @@ export function DutyList({ scrollMarginTop = 268, className }: DutyListProps) {
if (!hasDataForMonth) { if (!hasDataForMonth) {
return ( return (
<div <div aria-busy="true" className={className}>
role="status" <LoadingState asPlaceholder />
aria-busy="true"
aria-live="polite"
aria-label={t("loading")}
>
<span className="sr-only">{t("loading")}</span>
<DutyListSkeleton className={className} />
</div> </div>
); );
} }

View File

@@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="skeleton" data-slot="skeleton"
className={cn("animate-pulse rounded-md bg-accent", className)} className={cn("animate-pulse rounded-md bg-muted", className)}
{...props} {...props}
/> />
) )