Add defaultLocale, EN names, and dashboard activity endpoints.
Expose branding.defaultLocale, optional user EN name fields, and lightweight dual daily activity for orders/customers; update project context and rules. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
4598add88c
commit
43e3912662
@@ -0,0 +1,80 @@
|
||||
export type DailyCountRow = { day: Date | string; count: number };
|
||||
|
||||
export type DailyActivityPoint = {
|
||||
date: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export type DailyActivityResponse = {
|
||||
days: number;
|
||||
total: number;
|
||||
items: DailyActivityPoint[];
|
||||
};
|
||||
|
||||
/** Local calendar day at 00:00:00, `offsetDays` before today (0 = today). */
|
||||
export function startOfLocalDay(offsetDays = 0): Date {
|
||||
const date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
date.setDate(date.getDate() - offsetDays);
|
||||
return date;
|
||||
}
|
||||
|
||||
function pad2(n: number) {
|
||||
return String(n).padStart(2, '0');
|
||||
}
|
||||
|
||||
/** Calendar date key from a PG `date` (UTC midnight) or ISO string. */
|
||||
function toDateKey(value: Date | string): string {
|
||||
if (typeof value === 'string') {
|
||||
return value.slice(0, 10);
|
||||
}
|
||||
return `${value.getUTCFullYear()}-${pad2(value.getUTCMonth() + 1)}-${pad2(value.getUTCDate())}`;
|
||||
}
|
||||
|
||||
function toLocalDateKey(date: Date): string {
|
||||
return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
|
||||
}
|
||||
|
||||
/** Fill a continuous series of the last `days` local calendar days. */
|
||||
export function buildDailyActivitySeries(
|
||||
days: number,
|
||||
rows: DailyCountRow[],
|
||||
): DailyActivityResponse {
|
||||
const byDay = new Map<string, number>();
|
||||
for (const row of rows) {
|
||||
byDay.set(toDateKey(row.day), Number(row.count) || 0);
|
||||
}
|
||||
|
||||
const items: DailyActivityPoint[] = [];
|
||||
let total = 0;
|
||||
const end = startOfLocalDay(0);
|
||||
|
||||
for (let i = days - 1; i >= 0; i -= 1) {
|
||||
const date = new Date(end);
|
||||
date.setDate(end.getDate() - i);
|
||||
const key = toLocalDateKey(date);
|
||||
const count = byDay.get(key) ?? 0;
|
||||
total += count;
|
||||
items.push({ date: key, count });
|
||||
}
|
||||
|
||||
return { days, total, items };
|
||||
}
|
||||
|
||||
export type DualDailyActivityResponse = {
|
||||
days: number;
|
||||
primary: DailyActivityResponse;
|
||||
secondary: DailyActivityResponse;
|
||||
};
|
||||
|
||||
export function buildDualDailyActivitySeries(
|
||||
days: number,
|
||||
primaryRows: DailyCountRow[],
|
||||
secondaryRows: DailyCountRow[],
|
||||
): DualDailyActivityResponse {
|
||||
return {
|
||||
days,
|
||||
primary: buildDailyActivitySeries(days, primaryRows),
|
||||
secondary: buildDailyActivitySeries(days, secondaryRows),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsInt, IsOptional, Max, Min } from 'class-validator';
|
||||
|
||||
export class DailyActivityQueryDto {
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(90)
|
||||
days?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user