Skip to content

Commit 6834e31

Browse files
authored
Merge pull request #1703 from Skords-01/devin/1777903841-finyk-datastate-adoption
2 parents fbd4e36 + 9bd7b4f commit 6834e31

4 files changed

Lines changed: 487 additions & 276 deletions

File tree

apps/web/src/modules/finyk/pages/Overview.tsx

Lines changed: 101 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,14 @@ import {
3232
} from "@sergeant/finyk-domain/domain/budget";
3333
import { filterStatTransactions } from "@sergeant/finyk-domain/domain/transactions";
3434
import { Skeleton } from "@shared/components/ui/Skeleton";
35+
import {
36+
DataState,
37+
type DataStateQueryLike,
38+
} from "@shared/components/ui/DataState";
3539
import { safeReadStringLS, safeWriteLS } from "@shared/lib/storage/storage";
3640
import { THEME_HEX } from "@shared/lib/ui/themeHex";
3741
import { SyncStatusBadge } from "../components/SyncStatusBadge";
42+
import type { Transaction } from "@sergeant/finyk-domain/domain/types";
3843

3944
import { FirstInsightBanner } from "./overview/FirstInsightBanner";
4045
import { HeroCard } from "./overview/HeroCard";
@@ -348,18 +353,26 @@ export function Overview({
348353
[subscriptionFlows, debtOutFlows, debtInFlows, currentYear, currentMonth],
349354
);
350355

351-
if (loadingTx && realTx.length === 0) {
352-
return (
353-
<div className="flex-1 overflow-y-auto">
354-
<div className="px-4 pt-4 page-tabbar-pad space-y-4 max-w-4xl mx-auto">
355-
<Skeleton className="h-[168px] rounded-3xl" />
356-
<Skeleton className="h-[120px] opacity-80 rounded-2xl" />
357-
<Skeleton className="h-[110px] opacity-60 rounded-2xl" />
358-
<Skeleton className="h-[90px] opacity-40 rounded-2xl" />
359-
</div>
356+
// DataState contract: `data === undefined` triggers the skeleton slot.
357+
// We treat the very first month load (no realTx yet) as loading;
358+
// subsequent background refetches keep `data` defined so the page stays
359+
// visible while a stale-revalidate happens. Mirrors the prior
360+
// `if (loadingTx && realTx.length === 0)` early-return guard exactly.
361+
const overviewQuery: DataStateQueryLike<readonly Transaction[]> = {
362+
data: loadingTx && realTx.length === 0 ? undefined : realTx,
363+
isLoading: loadingTx,
364+
};
365+
366+
const overviewLoadingSkeleton = (
367+
<div className="flex-1 overflow-y-auto">
368+
<div className="px-4 pt-4 page-tabbar-pad space-y-4 max-w-4xl mx-auto">
369+
<Skeleton className="h-[168px] rounded-3xl" />
370+
<Skeleton className="h-[120px] opacity-80 rounded-2xl" />
371+
<Skeleton className="h-[110px] opacity-60 rounded-2xl" />
372+
<Skeleton className="h-[90px] opacity-40 rounded-2xl" />
360373
</div>
361-
);
362-
}
374+
</div>
375+
);
363376

364377
const recurringOutThisMonth = monthFlows
365378
.filter(
@@ -400,78 +413,82 @@ export function Overview({
400413
});
401414

402415
return (
403-
<div className="flex-1 overflow-y-auto overscroll-contain">
404-
<div className="px-4 pt-4 page-tabbar-pad space-y-4 max-w-4xl mx-auto">
405-
{(clientInfo ||
406-
syncState?.status === "error" ||
407-
syncState?.status === "loading" ||
408-
monoError) && (
409-
<SyncStatusBadge
410-
syncState={syncState}
411-
lastUpdated={lastUpdated}
412-
error={monoError}
413-
onRetry={monoRefresh}
414-
loading={loadingTx}
415-
/>
416-
)}
417-
418-
{showFirstInsight && hasAnyData && (
419-
<FirstInsightBanner
420-
onSetBudget={handleSetBudgetFromInsight}
421-
onDismiss={dismissFirstInsight}
422-
/>
423-
)}
424-
425-
<HeroCard
426-
networth={networth}
427-
monoTotal={monoTotal}
428-
totalDebt={totalDebt}
429-
daysInMonth={daysInMonth}
430-
daysPassed={daysPassed}
431-
dayBudget={dayBudget}
432-
hasExpensePlan={hasExpensePlan}
433-
spendPlanRatio={spendPlanRatio}
434-
showBalance={showBalance}
435-
/>
436-
437-
<MonthPulseCard
438-
dateLabel={dateLabel}
439-
daysPassed={daysPassed}
440-
spent={spent}
441-
income={income}
442-
showBalance={showBalance}
443-
showMonthForecast={showMonthForecast}
444-
projectedSpend={projectedSpend}
445-
hasExpensePlan={hasExpensePlan}
446-
spendPlanRatio={spendPlanRatio}
447-
planExpense={planExpense}
448-
forecastTrendPct={forecastTrendPct}
449-
forecastBarClass={forecastBarClass}
450-
recurringOutThisMonth={recurringOutThisMonth}
451-
recurringInThisMonth={recurringInThisMonth}
452-
unknownOutCount={unknownOutCount}
453-
/>
454-
455-
<NetworthSection networthHistory={networthHistory} />
456-
457-
<BudgetAlertsList
458-
budgetAlerts={budgetAlerts}
459-
statTx={statTx}
460-
txCategories={txCategories}
461-
txSplits={txSplits}
462-
customCategories={customCategories}
463-
/>
464-
465-
<PlannedFlowsCard
466-
plannedFlows={plannedFlows}
467-
onNavigate={onNavigate ?? (() => {})}
468-
showBalance={showBalance}
469-
/>
470-
471-
{loadingTx && (
472-
<p className="text-center text-xs text-subtle py-4">Оновлення…</p>
473-
)}
474-
</div>
475-
</div>
416+
<DataState query={overviewQuery} skeleton={overviewLoadingSkeleton}>
417+
{() => (
418+
<div className="flex-1 overflow-y-auto overscroll-contain">
419+
<div className="px-4 pt-4 page-tabbar-pad space-y-4 max-w-4xl mx-auto">
420+
{(clientInfo ||
421+
syncState?.status === "error" ||
422+
syncState?.status === "loading" ||
423+
monoError) && (
424+
<SyncStatusBadge
425+
syncState={syncState}
426+
lastUpdated={lastUpdated}
427+
error={monoError}
428+
onRetry={monoRefresh}
429+
loading={loadingTx}
430+
/>
431+
)}
432+
433+
{showFirstInsight && hasAnyData && (
434+
<FirstInsightBanner
435+
onSetBudget={handleSetBudgetFromInsight}
436+
onDismiss={dismissFirstInsight}
437+
/>
438+
)}
439+
440+
<HeroCard
441+
networth={networth}
442+
monoTotal={monoTotal}
443+
totalDebt={totalDebt}
444+
daysInMonth={daysInMonth}
445+
daysPassed={daysPassed}
446+
dayBudget={dayBudget}
447+
hasExpensePlan={hasExpensePlan}
448+
spendPlanRatio={spendPlanRatio}
449+
showBalance={showBalance}
450+
/>
451+
452+
<MonthPulseCard
453+
dateLabel={dateLabel}
454+
daysPassed={daysPassed}
455+
spent={spent}
456+
income={income}
457+
showBalance={showBalance}
458+
showMonthForecast={showMonthForecast}
459+
projectedSpend={projectedSpend}
460+
hasExpensePlan={hasExpensePlan}
461+
spendPlanRatio={spendPlanRatio}
462+
planExpense={planExpense}
463+
forecastTrendPct={forecastTrendPct}
464+
forecastBarClass={forecastBarClass}
465+
recurringOutThisMonth={recurringOutThisMonth}
466+
recurringInThisMonth={recurringInThisMonth}
467+
unknownOutCount={unknownOutCount}
468+
/>
469+
470+
<NetworthSection networthHistory={networthHistory} />
471+
472+
<BudgetAlertsList
473+
budgetAlerts={budgetAlerts}
474+
statTx={statTx}
475+
txCategories={txCategories}
476+
txSplits={txSplits}
477+
customCategories={customCategories}
478+
/>
479+
480+
<PlannedFlowsCard
481+
plannedFlows={plannedFlows}
482+
onNavigate={onNavigate ?? (() => {})}
483+
showBalance={showBalance}
484+
/>
485+
486+
{loadingTx && (
487+
<p className="text-center text-xs text-subtle py-4">Оновлення…</p>
488+
)}
489+
</div>
490+
</div>
491+
)}
492+
</DataState>
476493
);
477494
}

0 commit comments

Comments
 (0)