feat(F-147): completed feature
This commit is contained in:
@@ -23,7 +23,8 @@ export type Permission =
|
||||
| 'cms.write'
|
||||
| 'admin-users.read'
|
||||
| 'admin-users.write'
|
||||
| 'audit.read';
|
||||
| 'audit.read'
|
||||
| 'reporting.read';
|
||||
|
||||
export function can(role: Role, permission: Permission): boolean {
|
||||
if (role === 'admin') return true;
|
||||
@@ -41,6 +42,7 @@ export interface NavItem {
|
||||
|
||||
export const NAV_ITEMS: NavItem[] = [
|
||||
{ href: '/', label: 'Dashboard', icon: '📊', permission: 'dashboard' },
|
||||
{ href: '/reporting', label: 'Reporting', icon: '📈', permission: 'reporting.read' },
|
||||
{ href: '/products', label: 'Productos', icon: '📦', permission: 'products.read' },
|
||||
{ href: '/orders', label: 'Pedidos', icon: '🧾', permission: 'orders.read' },
|
||||
{ href: '/payments', label: 'Pagos', icon: '💳', permission: 'orders.read' },
|
||||
|
||||
155
project/apps/admin/src/lib/reporting-client.ts
Normal file
155
project/apps/admin/src/lib/reporting-client.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* F-147 — Reporting API client (frontend admin).
|
||||
*
|
||||
* Wraps the backend reporting endpoints (F-143, F-146).
|
||||
* All paths go through /api/* (Next.js proxy).
|
||||
*/
|
||||
|
||||
import { api } from './api-client';
|
||||
|
||||
export type ReportingChannel = 'all' | 'ecommerce' | 'pos' | 'admin';
|
||||
export type ComparisonMode = 'none' | 'previous_equal' | 'previous_calendar';
|
||||
export type GroupBy = 'day' | 'week' | 'month' | 'hour' | 'store' | 'channel' | 'terminal' | 'cashier' | 'payment';
|
||||
|
||||
export interface DateRange {
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
export interface ReportingFilters {
|
||||
from: string;
|
||||
to: string;
|
||||
compare?: ComparisonMode;
|
||||
channel?: ReportingChannel;
|
||||
storeId?: string | string[];
|
||||
terminalId?: string | string[];
|
||||
state?: string | string[];
|
||||
groupBy?: GroupBy;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export type Availability = 'available' | 'unavailable';
|
||||
|
||||
export interface DataAvailability {
|
||||
grossSales: Availability;
|
||||
netSales: Availability;
|
||||
discounts: Availability;
|
||||
tax: Availability;
|
||||
unitsSold: Availability;
|
||||
orders: Availability;
|
||||
customers: Availability;
|
||||
margin: Availability;
|
||||
paymentMethod: Availability;
|
||||
refunds: Availability;
|
||||
shipping: Availability;
|
||||
}
|
||||
|
||||
export interface Metrics {
|
||||
orders: number;
|
||||
customers: number;
|
||||
grossSalesCents: number;
|
||||
discountsCents: number;
|
||||
taxCents: number;
|
||||
unitsSold: number;
|
||||
shippingCents: number;
|
||||
}
|
||||
|
||||
export interface Comparison {
|
||||
previous: Metrics;
|
||||
variation?: number;
|
||||
}
|
||||
|
||||
export interface SummaryResponse {
|
||||
range: DateRange;
|
||||
filters: {
|
||||
channel: ReportingChannel;
|
||||
storeIds: string[];
|
||||
terminalIds: string[];
|
||||
};
|
||||
comparison: Comparison | null;
|
||||
dataAvailability: DataAvailability;
|
||||
totals: Metrics;
|
||||
updatedAt: string;
|
||||
cache: { hit: boolean; maxAgeSeconds: number };
|
||||
}
|
||||
|
||||
export interface SalesRow {
|
||||
period: string | null;
|
||||
channel: ReportingChannel | null;
|
||||
storeId: string | null;
|
||||
terminalId: string | null;
|
||||
metrics: Metrics;
|
||||
}
|
||||
|
||||
export interface SalesResponse extends SummaryResponse {
|
||||
filters: {
|
||||
channel: ReportingChannel;
|
||||
storeIds: string[];
|
||||
terminalIds: string[];
|
||||
groupBy: GroupBy | null;
|
||||
};
|
||||
items: SalesRow[];
|
||||
totals: Metrics;
|
||||
pagination: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalRows: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FilterSchema {
|
||||
filterSchema: {
|
||||
filters: Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
repeatable: boolean;
|
||||
options?: readonly string[];
|
||||
description: string;
|
||||
}>;
|
||||
comparison: { modes: ComparisonMode[]; rangeBounds: string };
|
||||
groupBy: GroupBy[];
|
||||
pagination: { pageMin: number; pageSizeMin: number; pageSizeMax: number };
|
||||
dataAvailability: DataAvailability;
|
||||
};
|
||||
permissions: { role: string; grants: string[] };
|
||||
}
|
||||
|
||||
// ── API calls ───────────────────────────────────────────────────────────────
|
||||
|
||||
function filtersToQueryString(filters: ReportingFilters): string {
|
||||
const params = new URLSearchParams();
|
||||
params.set('from', filters.from);
|
||||
params.set('to', filters.to);
|
||||
if (filters.compare && filters.compare !== 'none') params.set('compare', filters.compare);
|
||||
if (filters.channel && filters.channel !== 'all') params.set('channel', filters.channel);
|
||||
if (filters.groupBy) params.set('groupBy', filters.groupBy);
|
||||
if (filters.page) params.set('page', String(filters.page));
|
||||
if (filters.pageSize) params.set('pageSize', String(filters.pageSize));
|
||||
if (filters.storeId) {
|
||||
const ids = Array.isArray(filters.storeId) ? filters.storeId : [filters.storeId];
|
||||
ids.forEach((id) => params.append('storeId', id));
|
||||
}
|
||||
if (filters.terminalId) {
|
||||
const ids = Array.isArray(filters.terminalId) ? filters.terminalId : [filters.terminalId];
|
||||
ids.forEach((id) => params.append('terminalId', id));
|
||||
}
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
export const reportingClient = {
|
||||
async fetchSchema(): Promise<FilterSchema> {
|
||||
return api.get<FilterSchema>('/api/reporting/filters/schema');
|
||||
},
|
||||
|
||||
async fetchSummary(filters: ReportingFilters): Promise<SummaryResponse> {
|
||||
const qs = filtersToQueryString(filters);
|
||||
return api.get<SummaryResponse>(`/api/reporting/summary?${qs}`);
|
||||
},
|
||||
|
||||
async fetchSales(filters: ReportingFilters): Promise<SalesResponse> {
|
||||
const qs = filtersToQueryString(filters);
|
||||
return api.get<SalesResponse>(`/api/reporting/sales?${qs}`);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user