Compare commits

..

7 Commits

8 changed files with 371 additions and 278 deletions

View File

@@ -51,7 +51,7 @@ function Dashboard({ basePath }: { basePath: string }) {
transition: 'transform 0.2s',
'&:hover': { transform: 'translateY(-4px)', boxShadow: 4 }
}}
onClick={() => navigate(`${basePath}/${res.name}`)}
onClick={() => navigate(`/admin/${res.name}`)}
>
<Typography variant="h6" color="primary">{res.pluralLabel}</Typography>
<Typography variant="body2" color="text.secondary">Manage {res.pluralLabel.toLowerCase()}</Typography>
@@ -88,7 +88,7 @@ function AdminApp({ basePath }: { basePath: string }) {
<AdminLayout
username={currentUser.username}
onLogout={logout}
onSelectResource={(name) => navigate(`${basePath}/${name}`)}
onSelectResource={(name) => navigate(`/admin/${name}`)}
resources={config?.resources || []}
>
<Routes>

View File

@@ -104,12 +104,12 @@ export default function EnhancedTable({
<GridActionsCellItem
icon={<VisibilityIcon />}
label="View"
onClick={() => navigate(`/${config.name}/${params.id}`)}
onClick={() => navigate(`/admin/${config.name}/${params.id}`)}
/>,
<GridActionsCellItem
icon={<EditIcon />}
label="Edit"
onClick={() => navigate(`/${config.name}/edit/${params.id}`)}
onClick={() => navigate(`/admin/${config.name}/edit/${params.id}`)}
/>,
<GridActionsCellItem
icon={<DeleteIcon />}
@@ -222,8 +222,8 @@ function MobileCardRow({ row, config, onDelete, onNavigate, navigate }: any) {
<MoreVertIcon fontSize="small" />
</IconButton>
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem onClick={() => { handleClose(); navigate(`/${config.name}/${id}`); }}>View</MenuItem>
<MenuItem onClick={() => { handleClose(); navigate(`/${config.name}/edit/${id}`); }}>Edit</MenuItem>
<MenuItem onClick={() => { handleClose(); navigate(`/admin/${config.name}/${id}`); }}>View</MenuItem>
<MenuItem onClick={() => { handleClose(); navigate(`/admin/${config.name}/edit/${id}`); }}>Edit</MenuItem>
<MenuItem onClick={() => { handleClose(); onDelete(id); }} sx={{ color: 'error.main' }}>Delete</MenuItem>
</Menu>
</Box>
@@ -242,7 +242,7 @@ function MobileCardRow({ row, config, onDelete, onNavigate, navigate }: any) {
</Box>
</CardContent>
<CardActions sx={{ justifyContent: 'flex-end', px: 2, pb: 2 }}>
<Button size="small" onClick={() => navigate(`/${config.name}/${id}`)}>View Details</Button>
<Button size="small" onClick={() => navigate(`/admin/${config.name}/${id}`)}>View Details</Button>
</CardActions>
</Card>
);
@@ -359,7 +359,7 @@ function FieldRenderer({ params, field, fieldKey, config, onNavigate, navigate,
label={value}
size="small"
color="primary"
onClick={(e) => { e.stopPropagation(); navigate(`/${config.name}/${params.row[config.primaryKey]}`); }}
onClick={(e) => { e.stopPropagation(); navigate(`/admin/${config.name}/${params.row[config.primaryKey]}`); }}
sx={{ cursor: 'pointer', fontWeight: 'bold' }}
/>
);

View File

@@ -48,11 +48,11 @@ export default function ResourceView({ config, onNavigateToResource }: ResourceV
const deleteMutation = useDelete();
const handleEdit = (item: any) => {
navigate(`/${config.name}/edit/${item[config.primaryKey]}`);
navigate(`/admin/${config.name}/edit/${item[config.primaryKey]}`);
};
const handleCreate = () => {
navigate(`/${config.name}/create`);
navigate(`/admin/${config.name}/create`);
};
const handleSave = async (formData: any) => {
@@ -62,7 +62,7 @@ export default function ResourceView({ config, onNavigateToResource }: ResourceV
} else {
await createMutation.mutateAsync(formData);
}
navigate(`/${config.name}`);
navigate(`/admin/${config.name}`);
} catch (err) {
console.error('Save failed:', err);
}
@@ -90,7 +90,7 @@ export default function ResourceView({ config, onNavigateToResource }: ResourceV
onEdit={handleEdit}
onDelete={handleDelete}
onCreate={handleCreate}
onNavigateToResource={(res, id) => navigate(`/${res}/${id}`)}
onNavigateToResource={(res, id) => navigate(`/admin/${res}/${id}`)}
/>
) : (
<Paper sx={{ p: 4 }}>
@@ -98,10 +98,10 @@ export default function ResourceView({ config, onNavigateToResource }: ResourceV
config={config}
initialData={isCreate ? null : itemQuery.data}
onSave={handleSave}
onCancel={() => navigate(`/${config.name}`)}
onCancel={() => navigate(`/admin/${config.name}`)}
loading={createMutation.isPending || updateMutation.isPending}
readOnly={isView}
onEditClick={() => navigate(`/${config.name}/edit/${id}`)}
onEditClick={() => navigate(`/admin/${config.name}/edit/${id}`)}
/>
</Paper>
)}

View File

@@ -11,12 +11,14 @@ import {
import LatestItemsList, { LatestItem } from "./components/LatestItemsList";
import HistoryChart from "./components/HistoryChart";
import {
AggregatedDashboardData,
} from "./components/HistoryChart";
import {
fetchLatestTransactions,
fetchAggregatedExpenses,
fetchAggregatedIncome,
AggregatedDashboardData
} from "./utils/dashboardLoader";
export default function Dashboard() {

View File

@@ -5,7 +5,7 @@ export interface ChartDataPoint {
id: string;
amount: number;
compareAmount?: number;
count?: number;
compareLabel?: string;
highlighted?: boolean;
}
@@ -31,6 +31,54 @@ export interface HistoryChartProps {
setComparison: (mode: boolean) => void;
}
const formatDisplay = (
point: ChartDataPoint,
tab: string,
comparison: boolean
) => {
const base = point.amount;
const cmp = point.compareAmount ?? 0;
const formatShort = (val: number) => {
if (tab === "monthly") {
if (val >= 100000) return `${(val / 100000).toFixed(2)}L`;
}
if (tab === "weekly") {
if (val >= 1000) return `${(val / 1000).toFixed(1)}K`;
}
return val.toLocaleString("en-IN");
};
// Only hide diff when comparison OFF or compare is undefined
if (!comparison) {
return `${formatShort(base)}`;
}
const diff = base - cmp;
const sign = diff >= 0 ? "+" : "-";
const absDiff = Math.abs(diff);
return `${formatShort(base)} (${sign}${formatShort(absDiff)})`;
};
const formatLabel = (label: string, type: string) => {
if (type === "monthly") return label;
if (type === "weekly") {
const parts = label.split(" - ");
if (parts.length === 2) {
const [start, end] = parts;
const startDay = start.split(" ")[0];
const endParts = end.split(" ");
const endDay = endParts[0];
const month = endParts[1];
return `${startDay}${endDay} ${month}`;
}
}
return label;
};
export default function HistoryChart({
header,
summary,
@@ -44,9 +92,7 @@ export default function HistoryChart({
const [activeTab, setActiveTab] = React.useState<string>(tabs[0] || "");
const handleTabChange = (_: React.MouseEvent<HTMLElement>, newTab: string | null) => {
if (newTab !== null) {
setActiveTab(newTab);
}
if (newTab !== null) setActiveTab(newTab);
};
const activeDataKey = activeTab.toLowerCase() as keyof ChartData;
@@ -72,24 +118,6 @@ export default function HistoryChart({
)
: 1;
const formatAmount = (amount: number) => {
const tab = activeTab.toLowerCase();
if (amount === 0) return "";
if (tab === "monthly") {
if (amount >= 100000) return `${(amount / 100000).toFixed(2)} L`;
return `${amount.toLocaleString("en-IN")}`;
}
if (tab === "weekly") {
if (amount >= 1000) return `${(amount / 1000).toFixed(1)} K`;
return `${amount.toLocaleString("en-IN")}`;
}
return `${amount.toLocaleString("en-IN")}`;
};
return (
<Paper
sx={{
@@ -111,38 +139,12 @@ export default function HistoryChart({
</Typography>
)}
{/* Tabs */}
<ToggleButtonGroup
value={activeTab}
exclusive
onChange={handleTabChange}
fullWidth
sx={{
mb: 4,
bgcolor: (theme) =>
theme.palette.mode === "dark"
? "rgba(255,255,255,0.05)"
: "rgba(0,0,0,0.02)",
borderRadius: 8,
p: 0.5,
"& .MuiToggleButton-root": {
border: "none",
borderRadius: 8,
textTransform: "capitalize",
fontWeight: 600,
color: "text.secondary",
"&.Mui-selected": {
bgcolor: (theme) =>
theme.palette.mode === "dark"
? "primary.dark"
: "primary.light",
color: (theme) =>
theme.palette.mode === "dark"
? "primary.contrastText"
: "primary.main"
}
}
}}
sx={{ mb: 4 }}
>
{tabs.map((tab) => (
<ToggleButton key={tab} value={tab}>
@@ -151,7 +153,6 @@ export default function HistoryChart({
))}
</ToggleButtonGroup>
{/* Period Toggle */}
<ToggleButtonGroup
value={period}
exclusive
@@ -176,7 +177,6 @@ export default function HistoryChart({
<ToggleButton value="on">Compare</ToggleButton>
</ToggleButtonGroup>
{/* Chart */}
{currentData.length > 0 ? (
<Box sx={{ display: "flex", alignItems: "flex-end", height: 220, mt: 4 }}>
{currentData.map((point) => {
@@ -195,21 +195,15 @@ export default function HistoryChart({
height: "100%"
}}
>
{/* Values */}
<Typography variant="caption" sx={{ mb: 1, fontSize: "0.65rem" }}>
{formatAmount(point.amount)}
</Typography>
{/* Bars */}
<Box
sx={{
display: "flex",
alignItems: "flex-end",
gap: comparison ? 0.5 : 0,
height: "100%"
height: "100%",
position: "relative"
}}
>
{/* Compare */}
{comparison && (
<Box
sx={{
@@ -221,30 +215,68 @@ export default function HistoryChart({
/>
)}
{/* Current */}
<Box
sx={{
width: 10,
height: `${currentHeight}%`,
bgcolor: point.highlighted ? "error.main" : "primary.main",
borderRadius: 2
borderRadius: 2,
position: "relative"
}}
/>
>
<Typography
variant="caption"
sx={{
position: "absolute",
top: -18,
left: "50%",
transform: "translateX(-50%)",
fontSize: "0.65rem",
whiteSpace: "nowrap"
}}
>
{formatDisplay(point, activeTab.toLowerCase(), comparison)}
</Typography>
</Box>
</Box>
{/* Label */}
<Typography
variant="caption"
color="text.secondary"
<Box
sx={{
mt: 1,
fontSize: "0.7rem",
textAlign: "center",
whiteSpace: "pre-line"
display: "flex",
flexDirection: "column",
alignItems: "center",
lineHeight: 1.1
}}
>
{point.id}
</Typography>
<Typography
variant="caption"
sx={{
fontSize: "0.7rem",
opacity: 0.7,
color: "text.primary",
}}
>
{formatLabel(point.id, activeDataKey)}
</Typography>
<Typography
variant="caption"
sx={{
fontSize: "0.65rem",
color: "grey.400",
visibility:
comparison && point.compareLabel && activeDataKey !== "daily"
? "visible"
: "hidden"
}}
>
{point.compareLabel
? formatLabel(point.compareLabel, activeDataKey)
: "placeholder"}
</Typography>
</Box>
</Box>
);
})}

View File

@@ -2,40 +2,21 @@ import { api } from "../../react-openapi";
import { LatestItem } from "../components/LatestItemsList";
import { ChartDataPoint } from "../components/HistoryChart";
import * as React from "react";
import { format } from "./dateUtils";
import MonetizationOnIcon from "@mui/icons-material/MonetizationOn";
// ---------------- ICON ----------------
import {
buildDailyBuckets,
buildWeeklyRolling,
buildWeeklyCalendar,
buildMonthlyRolling,
buildMonthlyCalendar
} from "./periodBuilders";
const DEFAULT_ICON = React.createElement(MonetizationOnIcon, {
sx: { color: "#388e3c" }
});
// ---------------- HELPERS ----------------
const format = (d: Date) =>
`${d.getDate()} ${d.toLocaleString("default", { month: "short" })}`;
const startOfDay = (d: Date) => {
const x = new Date(d);
x.setHours(0, 0, 0, 0);
return x;
};
const endOfDay = (d: Date) => {
const x = new Date(d);
x.setHours(23, 59, 59, 999);
return x;
};
const getStartOfWeek = (d: Date) => {
const date = new Date(d);
const day = date.getDay() || 7;
if (day !== 1) date.setDate(date.getDate() - (day - 1));
return startOfDay(date);
};
const shiftDate = (d: Date, days: number) =>
new Date(d.getTime() + days * 86400000);
// ---------------- LATEST ----------------
export async function fetchLatestTransactions(
type: "expense" | "income"
): Promise<LatestItem[]> {
@@ -66,35 +47,17 @@ export async function fetchLatestTransactions(
iconBgColor:
type === "expense" ? "#ffebee" : "#e8f5e9",
title: exp.payee?.name || exp.payee || "Unknown Payee",
subtitle: exp.category?.name || exp.account?.name || "Transaction",
subtitle:
exp.category?.name || exp.account?.name || "Transaction",
amount: `Rs ${Math.abs(exp.amount || 0)}`,
timeAgo: diffDays === 0 ? "Today" : `${diffDays} days ago`
};
});
}
// ---------------- TYPES ----------------
export interface ChartSeries {
rolling: ChartDataPoint[];
calendar: ChartDataPoint[];
}
export interface ChartData {
daily: ChartDataPoint[];
weekly: ChartSeries;
monthly: ChartSeries;
}
export interface AggregatedDashboardData {
chartData: ChartData;
totalAmount: number;
topPayees: Array<{ payeeName: string; amount: number }>;
}
// ---------------- AGGREGATION ----------------
export async function fetchAggregatedData(
type: "expense" | "income"
): Promise<AggregatedDashboardData> {
) {
const res = await api.get("/expenses", { params: { limit: 0 } });
const all: any[] = res.data?.items || res.data || [];
@@ -108,126 +71,19 @@ export async function fetchAggregatedData(
const normalize = (amt: number) => Math.abs(amt);
// ---------------- DAILY ----------------
const dailyBuckets: Record<string, any> = {
Mon: { amount: 0, compare: 0 },
Tue: { amount: 0, compare: 0 },
Wed: { amount: 0, compare: 0 },
Thu: { amount: 0, compare: 0 },
Fri: { amount: 0, compare: 0 },
Sat: { amount: 0, compare: 0 },
Sun: { amount: 0, compare: 0 }
};
const {
buckets: dailyBuckets,
weekStart,
weekEnd,
prevWeekStart,
prevWeekEnd
} = buildDailyBuckets(now);
const weekStart = getStartOfWeek(now);
const weekEnd = endOfDay(new Date(weekStart.getTime() + 6 * 86400000));
const prevWeekStart = shiftDate(weekStart, -7);
const prevWeekEnd = shiftDate(weekEnd, -7);
const weeklyRolling = buildWeeklyRolling(now);
const weeklyCalendar = buildWeeklyCalendar(now);
const monthlyRolling = buildMonthlyRolling(now);
const monthlyCalendar = buildMonthlyCalendar(now);
// ---------------- WEEKLY ----------------
const weeklyRolling: any[] = [];
const weeklyCalendar: any[] = [];
const currentWeekStart = getStartOfWeek(now);
// rolling (last 5 weeks)
for (let i = 4; i >= 0; i--) {
const start = new Date(currentWeekStart.getTime() - i * 7 * 86400000);
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
weeklyRolling.push({
label: `${format(start)} - ${format(end)}`,
start,
end,
amount: 0,
compare: 0,
prevStart: shiftDate(start, -7),
prevEnd: shiftDate(end, -7)
});
}
// calendar weeks
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
const firstWeekStart = getStartOfWeek(startOfMonth);
const totalWeeks = Math.ceil(
(endOfMonth.getTime() - firstWeekStart.getTime()) / (7 * 86400000)
) + 1;
for (let i = 0; i < totalWeeks; i++) {
const start = new Date(firstWeekStart.getTime() + i * 7 * 86400000);
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
weeklyCalendar.push({
label: `${format(start)} - ${format(end)}`,
start,
end,
amount: 0,
compare: 0,
prevStart: shiftDate(start, -7),
prevEnd: shiftDate(end, -7)
});
}
// ---------------- MONTHLY ----------------
const monthlyRolling: any[] = [];
const monthlyCalendar: any[] = [];
// rolling (last 12 months)
for (let i = 11; i >= 0; i--) {
const d = new Date(now);
d.setMonth(d.getMonth() - i);
const start = new Date(d.getFullYear(), d.getMonth(), 1);
const end =
i === 0
? endOfDay(now)
: endOfDay(new Date(d.getFullYear(), d.getMonth() + 1, 0));
const prevStart = new Date(start);
prevStart.setMonth(prevStart.getMonth() - 1);
const prevEnd = new Date(end);
prevEnd.setMonth(prevEnd.getMonth() - 1);
monthlyRolling.push({
label: `${d.toLocaleString("default", { month: "short" })}-${String(
d.getFullYear()
).slice(2)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
// calendar (JanDec)
for (let i = 0; i < 12; i++) {
const start = new Date(now.getFullYear(), i, 1);
const end = endOfDay(new Date(now.getFullYear(), i + 1, 0));
const prevStart = new Date(start);
prevStart.setFullYear(prevStart.getFullYear() - 1);
const prevEnd = new Date(end);
prevEnd.setFullYear(prevEnd.getFullYear() - 1);
monthlyCalendar.push({
label: `${start.toLocaleString("default", { month: "short" })}-${String(
start.getFullYear()
).slice(2)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
// ---------------- LOOP ----------------
for (const item of all) {
const d = new Date(
item.occurred_at || item.created_at || Date.now()
@@ -237,56 +93,50 @@ export async function fetchAggregatedData(
if (!isValid(amtRaw)) continue;
const amt = normalize(amtRaw);
totalAmount += amt;
const payee = item.payee?.name || item.payee || "Unknown";
payeeMap[payee] = (payeeMap[payee] || 0) + amt;
// DAILY
const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()];
if (d >= weekStart && d <= weekEnd) {
const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()];
if (dailyBuckets[day]) {
dailyBuckets[day].amount += amt;
}
}
if (d >= prevWeekStart && d <= prevWeekEnd) {
const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()];
if (dailyBuckets[day]) {
dailyBuckets[day].compare += amt;
}
}
// WEEKLY
for (const b of weeklyRolling) {
if (d >= b.start && d <= b.end) b.amount += amt;
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
}
for (const b of weeklyCalendar) {
if (d >= b.start && d <= b.end) b.amount += amt;
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
}
const apply = (arr: any[]) => {
for (const b of arr) {
if (d >= b.start && d <= b.end) b.amount += amt;
if (d >= b.prevStart && d <= b.prevEnd)
b.compare += amt;
}
};
// MONTHLY
for (const b of monthlyRolling) {
if (d >= b.start && d <= b.end) b.amount += amt;
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
}
for (const b of monthlyCalendar) {
if (d >= b.start && d <= b.end) b.amount += amt;
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
}
apply(weeklyRolling);
apply(weeklyCalendar);
apply(monthlyRolling);
apply(monthlyCalendar);
}
const toPoints = (arr: any[]): ChartDataPoint[] =>
arr.map((x) => ({
id: x.label,
amount: x.amount,
compareAmount: x.compare
compareAmount: x.compare,
compareLabel: x.prevStart && x.prevEnd
? `${format(x.prevStart)} - ${format(x.prevEnd)}`
: undefined
}));
const chartData: ChartData = {
const chartData = {
daily: Object.entries(dailyBuckets).map(([k, v]: any) => ({
id: k,
amount: v.amount,
@@ -302,7 +152,6 @@ export async function fetchAggregatedData(
}
};
// highlight max (current only)
Object.values(chartData).forEach((group: any) => {
const arr = Array.isArray(group) ? group : group.rolling;
if (!arr?.length) return;
@@ -322,7 +171,6 @@ export async function fetchAggregatedData(
return { chartData, totalAmount, topPayees };
}
// ---------------- EXPORTS ----------------
export const fetchAggregatedExpenses = () =>
fetchAggregatedData("expense");

33
src/utils/dateUtils.ts Normal file
View File

@@ -0,0 +1,33 @@
export const format = (d: Date) =>
`${d.getDate()} ${d.toLocaleString("default", { month: "short" })}`;
export const startOfDay = (d: Date) => {
const x = new Date(d);
x.setHours(0, 0, 0, 0);
return x;
};
export const endOfDay = (d: Date) => {
const x = new Date(d);
x.setHours(23, 59, 59, 999);
return x;
};
export const getStartOfWeek = (d: Date) => {
const date = new Date(d);
const day = date.getDay() || 7;
if (day !== 1) date.setDate(date.getDate() - (day - 1));
return startOfDay(date);
};
export const shiftDate = (d: Date, days: number) =>
new Date(d.getTime() + days * 86400000);
export const getWeekIndex = (date: Date) => {
const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
const firstWeekStart = getStartOfWeek(firstDay);
return Math.floor(
(startOfDay(date).getTime() - firstWeekStart.getTime()) /
(7 * 86400000)
);
};

178
src/utils/periodBuilders.ts Normal file
View File

@@ -0,0 +1,178 @@
import {
format,
endOfDay,
getStartOfWeek,
shiftDate,
getWeekIndex
} from "./dateUtils";
export const buildDailyBuckets = (now: Date) => {
const buckets: Record<string, any> = {
Mon: { amount: 0, compare: 0 },
Tue: { amount: 0, compare: 0 },
Wed: { amount: 0, compare: 0 },
Thu: { amount: 0, compare: 0 },
Fri: { amount: 0, compare: 0 },
Sat: { amount: 0, compare: 0 },
Sun: { amount: 0, compare: 0 }
};
const weekStart = getStartOfWeek(now);
const weekEnd = endOfDay(new Date(weekStart.getTime() + 6 * 86400000));
const prevWeekStart = shiftDate(weekStart, -7);
const prevWeekEnd = shiftDate(weekEnd, -7);
return { buckets, weekStart, weekEnd, prevWeekStart, prevWeekEnd };
};
const getPrevMonthWeek = (start: Date) => {
const prevMonthDate = new Date(start);
prevMonthDate.setMonth(prevMonthDate.getMonth() - 1);
const prevMonthFirst = new Date(
prevMonthDate.getFullYear(),
prevMonthDate.getMonth(),
1
);
const prevFirstWeekStart = getStartOfWeek(prevMonthFirst);
const weekIndex = getWeekIndex(start);
const prevStart = new Date(
prevFirstWeekStart.getTime() + weekIndex * 7 * 86400000
);
const prevEnd = endOfDay(new Date(prevStart.getTime() + 6 * 86400000));
return { prevStart, prevEnd };
};
export const buildWeeklyRolling = (now: Date) => {
const arr: any[] = [];
const currentWeekStart = getStartOfWeek(now);
for (let i = 4; i >= 0; i--) {
const start = new Date(
currentWeekStart.getTime() - i * 7 * 86400000
);
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
const { prevStart, prevEnd } = getPrevMonthWeek(start);
arr.push({
label: `${format(start)} - ${format(end)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
return arr;
};
export const buildWeeklyCalendar = (now: Date) => {
const arr: any[] = [];
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
const firstWeekStart = getStartOfWeek(startOfMonth);
const totalWeeks =
Math.ceil(
(endOfMonth.getTime() - firstWeekStart.getTime()) /
(7 * 86400000)
) + 1;
for (let i = 0; i < totalWeeks; i++) {
const start = new Date(
firstWeekStart.getTime() + i * 7 * 86400000
);
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
const { prevStart, prevEnd } = getPrevMonthWeek(start);
arr.push({
label: `${format(start)} - ${format(end)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
return arr;
};
export const buildMonthlyRolling = (now: Date) => {
const arr: any[] = [];
for (let i = 11; i >= 0; i--) {
const d = new Date(now);
d.setMonth(d.getMonth() - i);
const start = new Date(d.getFullYear(), d.getMonth(), 1);
const end =
i === 0
? endOfDay(now)
: endOfDay(new Date(d.getFullYear(), d.getMonth() + 1, 0));
const prevStart = new Date(start);
prevStart.setFullYear(prevStart.getFullYear() - 1);
let prevEnd = new Date(end);
prevEnd.setFullYear(prevEnd.getFullYear() - 1);
if (i === 0) {
prevEnd = new Date(prevStart);
prevEnd.setDate(now.getDate());
prevEnd = endOfDay(prevEnd);
}
arr.push({
label: `${d.toLocaleString("default", {
month: "short"
})}-${String(d.getFullYear()).slice(2)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
return arr;
};
export const buildMonthlyCalendar = (now: Date) => {
const arr: any[] = [];
for (let i = 0; i < 12; i++) {
const start = new Date(now.getFullYear(), i, 1);
const end = endOfDay(new Date(now.getFullYear(), i + 1, 0));
const prevStart = new Date(start);
prevStart.setFullYear(prevStart.getFullYear() - 1);
const prevEnd = new Date(end);
prevEnd.setFullYear(prevEnd.getFullYear() - 1);
arr.push({
label: `${start.toLocaleString("default", {
month: "short"
})}-${String(start.getFullYear()).slice(2)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
return arr;
};