feat: enhance CalendarPage and DutyList components for improved loading state handling
- Removed the loading state placeholder from CalendarPage, directly rendering the CalendarGrid component. - Updated DutyList to display a loading message when data is being fetched, enhancing user experience during data loading. - Introduced a new dataForMonthKey in the app store to manage month-specific data more effectively. - Refactored useMonthData hook to reset duties and calendarEvents when a new month is detected, ensuring accurate data representation. - Added tests to verify the new loading state behavior in both components.
This commit is contained in:
@@ -14,10 +14,9 @@ import { useStickyScroll } from "@/hooks/use-sticky-scroll";
|
||||
import { useAutoRefresh } from "@/hooks/use-auto-refresh";
|
||||
import { CalendarHeader } from "@/components/calendar/CalendarHeader";
|
||||
import { CalendarGrid } from "@/components/calendar/CalendarGrid";
|
||||
import { DutyList, DutyListSkeleton } from "@/components/duty/DutyList";
|
||||
import { DutyList } from "@/components/duty/DutyList";
|
||||
import { DayDetail, type DayDetailHandle } from "@/components/day-detail";
|
||||
import { callMiniAppReadyOnce } from "@/lib/telegram-ready";
|
||||
import { LoadingState } from "@/components/states/LoadingState";
|
||||
import { ErrorState } from "@/components/states/ErrorState";
|
||||
import { AccessDenied } from "@/components/states/AccessDenied";
|
||||
|
||||
@@ -129,15 +128,14 @@ export function CalendarPage({ isAllowed, initDataRaw }: CalendarPageProps) {
|
||||
retry();
|
||||
}, [setCurrentMonth, retry]);
|
||||
|
||||
const isInitialLoad =
|
||||
loading && duties.length === 0 && calendarEvents.length === 0;
|
||||
|
||||
// Signal Telegram to hide loading when calendar first load finishes.
|
||||
const readyCalledRef = useRef(false);
|
||||
// Signal Telegram to hide loading when the first load finishes (loading goes true -> false).
|
||||
useEffect(() => {
|
||||
if (!isInitialLoad) {
|
||||
if (!loading && !readyCalledRef.current) {
|
||||
readyCalledRef.current = true;
|
||||
callMiniAppReadyOnce();
|
||||
}
|
||||
}, [isInitialLoad]);
|
||||
}, [loading]);
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex min-h-screen w-full max-w-[var(--max-width-app)] flex-col bg-background px-3 pb-6 pt-safe">
|
||||
@@ -154,19 +152,12 @@ export function CalendarPage({ isAllowed, initDataRaw }: CalendarPageProps) {
|
||||
onPrevMonth={handlePrevMonth}
|
||||
onNextMonth={handleNextMonth}
|
||||
/>
|
||||
{isInitialLoad ? (
|
||||
<LoadingState
|
||||
asPlaceholder
|
||||
className="min-h-[var(--calendar-block-min-height)]"
|
||||
/>
|
||||
) : (
|
||||
<CalendarGrid
|
||||
currentMonth={currentMonth}
|
||||
duties={duties}
|
||||
calendarEvents={calendarEvents}
|
||||
onDayClick={handleDayClick}
|
||||
/>
|
||||
)}
|
||||
<CalendarGrid
|
||||
currentMonth={currentMonth}
|
||||
duties={duties}
|
||||
calendarEvents={calendarEvents}
|
||||
onDayClick={handleDayClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{accessDenied && (
|
||||
@@ -175,14 +166,12 @@ export function CalendarPage({ isAllowed, initDataRaw }: CalendarPageProps) {
|
||||
{!accessDenied && error && (
|
||||
<ErrorState message={error} onRetry={retry} className="my-3" />
|
||||
)}
|
||||
{!accessDenied && !error && loading && !isInitialLoad ? (
|
||||
<DutyListSkeleton className="mt-2" />
|
||||
) : !accessDenied && !error && !isInitialLoad ? (
|
||||
{!accessDenied && !error && (
|
||||
<DutyList
|
||||
scrollMarginTop={stickyBlockHeight}
|
||||
className="mt-2"
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
|
||||
<DayDetail
|
||||
ref={dayDetailRef}
|
||||
|
||||
@@ -64,8 +64,8 @@ export interface DutyListProps {
|
||||
export function DutyList({ scrollMarginTop = 268, className }: DutyListProps) {
|
||||
const { t } = useTranslation();
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
const { currentMonth, duties } = useAppStore(
|
||||
useShallow((s) => ({ currentMonth: s.currentMonth, duties: s.duties }))
|
||||
const { currentMonth, duties, loading } = useAppStore(
|
||||
useShallow((s) => ({ currentMonth: s.currentMonth, duties: s.duties, loading: s.loading }))
|
||||
);
|
||||
|
||||
const { filtered, dates, dutiesByDateKey } = useMemo(() => {
|
||||
@@ -142,8 +142,14 @@ export function DutyList({ scrollMarginTop = 268, className }: DutyListProps) {
|
||||
if (filtered.length === 0) {
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-1", className)}>
|
||||
<p className="text-sm text-muted m-0">{t("duty.none_this_month")}</p>
|
||||
<p className="text-xs text-muted m-0">{t("duty.none_this_month_hint")}</p>
|
||||
{loading ? (
|
||||
<p className="text-sm text-muted m-0">{t("loading")}</p>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm text-muted m-0">{t("duty.none_this_month")}</p>
|
||||
<p className="text-xs text-muted m-0">{t("duty.none_this_month_hint")}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user