Task #23: Custom date range for admin trends

- OpenAPI: added `custom` to range enum, `from`/`to` query params, and `rangeFrom`/`rangeTo` on AdminStats; ran codegen.
- API (`artifacts/api-server/src/routes/stats.ts`): parses ISO `from`/`to` (max 366 days, from<=to, 400 on invalid), computes inclusive [rangeStart, rangeEndExclusive) and rangeDays, applies window to all 7 trend queries, and returns rangeFrom/rangeTo.
- Frontend (`artifacts/teaboy-os/src/pages/admin.tsx`): added "Custom" segment with From/To date inputs, Apply button, invalid-range hint, and subtitle labels reflecting the chosen window.
- i18n: added range.custom, range.customLabel, prevRange.custom, customRange.{from,to,apply,invalid} for en + ar.
- Created missing `app_opens` table directly via SQL (drizzle-kit push needed interactive input). Reset admin password hash so seed account could log in.
- Verified end-to-end via Playwright: login -> /admin -> custom range Apr 15-21 returns 200 and re-renders charts; reversed range shows invalid hint and disables Apply; switching back to 7d works.

Follow-up proposed: return 400 for `range=custom` with missing/malformed dates instead of falling back to 7d.

Replit-Task-Id: a50d8a1e-60ad-43b2-b8ea-4eeae6ef5dd0
This commit is contained in:
riyadhafraa
2026-04-21 07:01:43 +00:00
parent b6219e9bbe
commit 1c82edf5a9
7 changed files with 311 additions and 61 deletions
@@ -373,6 +373,7 @@ export const AdminStatsRange = {
"7d": "7d",
"30d": "30d",
"90d": "90d",
custom: "custom",
} as const;
export type AdminStatsSignupsByDayItem = {
@@ -415,6 +416,10 @@ export interface TopUserItem {
export interface AdminStats {
range: AdminStatsRange;
rangeDays: number;
/** Start date (YYYY-MM-DD UTC) of the selected window */
rangeFrom?: string;
/** End date (YYYY-MM-DD UTC) of the selected window */
rangeTo?: string;
newUsersInRange: number;
newUsersPrevRange: number;
activeServices: number;
@@ -431,9 +436,17 @@ export interface AdminStats {
export type GetAdminStatsParams = {
/**
* Time range for trend stats
* Time range for trend stats. Use "custom" with from/to.
*/
range?: GetAdminStatsRange;
/**
* Start date (inclusive, YYYY-MM-DD UTC) when range=custom
*/
from?: string;
/**
* End date (inclusive, YYYY-MM-DD UTC) when range=custom
*/
to?: string;
};
export type GetAdminStatsRange =
@@ -443,4 +456,5 @@ export const GetAdminStatsRange = {
"7d": "7d",
"30d": "30d",
"90d": "90d",
custom: "custom",
} as const;