@open-mercato/core 0.4.2-canary-ccd610ad18 → 0.4.2-canary-cae9dafa24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/api_docs/backend/docs/page.js +4 -1
- package/dist/modules/api_docs/backend/docs/page.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +4 -0
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/sales/acl.js +3 -1
- package/dist/modules/sales/acl.js.map +2 -2
- package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js +163 -0
- package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js.map +7 -0
- package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js +165 -0
- package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js.map +7 -0
- package/dist/modules/sales/api/dashboard/widgets/utils.js +38 -0
- package/dist/modules/sales/api/dashboard/widgets/utils.js.map +7 -0
- package/dist/modules/sales/lib/customerSnapshot.js +21 -0
- package/dist/modules/sales/lib/customerSnapshot.js.map +7 -0
- package/dist/modules/sales/lib/dateRange.js +39 -0
- package/dist/modules/sales/lib/dateRange.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/new-orders/config.js +32 -0
- package/dist/modules/sales/widgets/dashboard/new-orders/config.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +252 -0
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.js +33 -0
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/new-quotes/config.js +32 -0
- package/dist/modules/sales/widgets/dashboard/new-quotes/config.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +272 -0
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.js +33 -0
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.js.map +7 -0
- package/package.json +2 -2
- package/src/modules/api_docs/backend/docs/page.tsx +2 -1
- package/src/modules/auth/lib/setup-app.ts +4 -0
- package/src/modules/customers/README.md +2 -1
- package/src/modules/entities/README.md +1 -1
- package/src/modules/sales/acl.ts +2 -0
- package/src/modules/sales/api/dashboard/widgets/new-orders/__tests__/route.test.ts +60 -0
- package/src/modules/sales/api/dashboard/widgets/new-orders/route.ts +192 -0
- package/src/modules/sales/api/dashboard/widgets/new-quotes/__tests__/route.test.ts +61 -0
- package/src/modules/sales/api/dashboard/widgets/new-quotes/route.ts +194 -0
- package/src/modules/sales/api/dashboard/widgets/utils.ts +53 -0
- package/src/modules/sales/i18n/de.json +32 -1
- package/src/modules/sales/i18n/en.json +32 -1
- package/src/modules/sales/i18n/es.json +32 -1
- package/src/modules/sales/i18n/pl.json +32 -1
- package/src/modules/sales/lib/__tests__/dateRange.test.ts +26 -0
- package/src/modules/sales/lib/customerSnapshot.ts +17 -0
- package/src/modules/sales/lib/dateRange.ts +42 -0
- package/src/modules/sales/widgets/dashboard/new-orders/__tests__/config.test.ts +28 -0
- package/src/modules/sales/widgets/dashboard/new-orders/config.ts +48 -0
- package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +295 -0
- package/src/modules/sales/widgets/dashboard/new-orders/widget.ts +33 -0
- package/src/modules/sales/widgets/dashboard/new-quotes/__tests__/config.test.ts +28 -0
- package/src/modules/sales/widgets/dashboard/new-quotes/config.ts +48 -0
- package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +322 -0
- package/src/modules/sales/widgets/dashboard/new-quotes/widget.ts +33 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/sales/api/dashboard/widgets/new-quotes/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { SalesQuote } from '../../../../data/entities'\nimport { resolveWidgetScope, type WidgetScopeContext } from '../utils'\nimport { extractCustomerName } from '../../../../lib/customerSnapshot'\nimport { parseDateInput, resolveDateRange, type DatePeriodOption } from '../../../../lib/dateRange'\n\nconst querySchema = z.object({\n limit: z.coerce.number().min(1).max(20).default(5),\n datePeriod: z.enum(['last24h', 'last7d', 'last30d', 'custom']).default('last24h'),\n customFrom: z.string().optional(),\n customTo: z.string().optional(),\n tenantId: z.string().uuid().optional(),\n organizationId: z.string().uuid().optional(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dashboards.view', 'sales.widgets.new-quotes'] },\n}\n\ntype WidgetContext = WidgetScopeContext & {\n limit: number\n datePeriod: DatePeriodOption\n customFrom?: string\n customTo?: string\n}\n\nasync function resolveContext(\n req: Request,\n translate: (key: string, fallback?: string) => string,\n): Promise<WidgetContext> {\n const url = new URL(req.url)\n const rawQuery: Record<string, string> = {}\n for (const [key, value] of url.searchParams.entries()) {\n rawQuery[key] = value\n }\n const parsed = querySchema.safeParse(rawQuery)\n if (!parsed.success) {\n throw new CrudHttpError(400, { error: translate('sales.errors.invalid_query', 'Invalid query parameters') })\n }\n\n const { container, em, tenantId, organizationIds } = await resolveWidgetScope(req, translate, {\n tenantId: parsed.data.tenantId ?? null,\n organizationId: parsed.data.organizationId ?? null,\n })\n\n return {\n container,\n em,\n tenantId,\n organizationIds,\n limit: parsed.data.limit,\n datePeriod: parsed.data.datePeriod,\n customFrom: parsed.data.customFrom,\n customTo: parsed.data.customTo,\n }\n}\n\nfunction resolveDateRangeOrThrow(\n period: DatePeriodOption,\n customFrom: string | undefined,\n customTo: string | undefined,\n translate: (key: string, fallback?: string) => string,\n): { from: Date; to: Date } {\n const parsedFrom = parseDateInput(customFrom)\n const parsedTo = parseDateInput(customTo)\n if (customFrom && !parsedFrom) {\n throw new CrudHttpError(400, { error: translate('sales.errors.invalid_date', 'Invalid date range') })\n }\n if (customTo && !parsedTo) {\n throw new CrudHttpError(400, { error: translate('sales.errors.invalid_date', 'Invalid date range') })\n }\n return resolveDateRange(period, parsedFrom, parsedTo)\n}\n\nexport async function GET(req: Request) {\n const { translate } = await resolveTranslations()\n try {\n const { em, tenantId, organizationIds, limit, datePeriod, customFrom, customTo } = await resolveContext(\n req,\n translate,\n )\n\n const { from, to } = resolveDateRangeOrThrow(datePeriod, customFrom, customTo, translate)\n\n const where: FilterQuery<SalesQuote> = {\n tenantId,\n deletedAt: null,\n createdAt: { $gte: from, $lte: to },\n }\n\n if (Array.isArray(organizationIds)) {\n where.organizationId =\n organizationIds.length === 1 ? organizationIds[0] : { $in: Array.from(new Set(organizationIds)) }\n }\n\n const [items, total] = await findAndCountWithDecryption(\n em,\n SalesQuote,\n where,\n {\n limit,\n orderBy: { createdAt: 'desc' as const },\n },\n { tenantId },\n )\n\n const responseItems = items.map((quote) => ({\n id: quote.id,\n quoteNumber: quote.quoteNumber,\n status: quote.status ?? null,\n customerName: extractCustomerName(quote.customerSnapshot) ?? null,\n customerEntityId: quote.customerEntityId ?? null,\n validFrom: quote.validFrom ? quote.validFrom.toISOString() : null,\n validUntil: quote.validUntil ? quote.validUntil.toISOString() : null,\n netAmount: quote.grandTotalNetAmount,\n grossAmount: quote.grandTotalGrossAmount,\n currency: quote.currencyCode ?? null,\n createdAt: quote.createdAt.toISOString(),\n convertedOrderId: quote.convertedOrderId ?? null,\n }))\n\n return NextResponse.json({\n items: responseItems,\n total,\n dateRange: {\n from: from.toISOString(),\n to: to.toISOString(),\n },\n })\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('sales.widgets.newQuotes failed', err)\n return NextResponse.json(\n { error: translate('sales.widgets.newQuotes.error', 'Failed to load recent quotes') },\n { status: 500 },\n )\n }\n}\n\nconst quoteItemSchema = z.object({\n id: z.string().uuid(),\n quoteNumber: z.string(),\n status: z.string().nullable(),\n customerName: z.string().nullable(),\n customerEntityId: z.string().uuid().nullable(),\n validFrom: z.string().nullable(),\n validUntil: z.string().nullable(),\n netAmount: z.string(),\n grossAmount: z.string(),\n currency: z.string().nullable(),\n createdAt: z.string(),\n convertedOrderId: z.string().uuid().nullable(),\n})\n\nconst responseSchema = z.object({\n items: z.array(quoteItemSchema),\n total: z.number(),\n dateRange: z.object({\n from: z.string(),\n to: z.string(),\n }),\n})\n\nconst widgetErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'New quotes widget',\n methods: {\n GET: {\n summary: 'Fetch recently created sales quotes',\n description: 'Returns the most recent sales quotes within the scoped tenant/organization.',\n query: querySchema,\n responses: [\n { status: 200, description: 'Widget payload', schema: responseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: widgetErrorSchema },\n { status: 401, description: 'Unauthorized', schema: widgetErrorSchema },\n { status: 500, description: 'Widget failed to load', schema: widgetErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
2
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
3
|
+
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
5
|
+
async function resolveWidgetScope(req, translate, overrides) {
|
|
6
|
+
const auth = await getAuthFromRequest(req);
|
|
7
|
+
if (!auth) {
|
|
8
|
+
throw new CrudHttpError(401, { error: translate("sales.errors.unauthorized", "Unauthorized") });
|
|
9
|
+
}
|
|
10
|
+
const container = await createRequestContainer();
|
|
11
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
|
|
12
|
+
const tenantId = overrides?.tenantId ?? auth.tenantId ?? null;
|
|
13
|
+
if (!tenantId) {
|
|
14
|
+
throw new CrudHttpError(400, { error: translate("sales.errors.tenant_required", "Tenant context is required") });
|
|
15
|
+
}
|
|
16
|
+
const organizationIds = (() => {
|
|
17
|
+
if (overrides?.organizationId) return [overrides.organizationId];
|
|
18
|
+
if (scope?.selectedId) return [scope.selectedId];
|
|
19
|
+
if (Array.isArray(scope?.filterIds) && scope.filterIds.length > 0) return scope.filterIds;
|
|
20
|
+
if (scope?.allowedIds === null) return null;
|
|
21
|
+
if (auth.orgId) return [auth.orgId];
|
|
22
|
+
return [];
|
|
23
|
+
})();
|
|
24
|
+
if (organizationIds !== null && organizationIds.length === 0) {
|
|
25
|
+
throw new CrudHttpError(400, { error: translate("sales.errors.organization_required", "Organization context is required") });
|
|
26
|
+
}
|
|
27
|
+
const em = container.resolve("em");
|
|
28
|
+
return {
|
|
29
|
+
container,
|
|
30
|
+
em,
|
|
31
|
+
tenantId,
|
|
32
|
+
organizationIds
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
resolveWidgetScope
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/sales/api/dashboard/widgets/utils.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer, type AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\n\nexport type WidgetScopeContext = {\n container: AppContainer\n em: EntityManager\n tenantId: string\n organizationIds: string[] | null\n}\n\nexport async function resolveWidgetScope(\n req: Request,\n translate: (key: string, fallback?: string) => string,\n overrides?: { tenantId?: string | null; organizationId?: string | null },\n): Promise<WidgetScopeContext> {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n throw new CrudHttpError(401, { error: translate('sales.errors.unauthorized', 'Unauthorized') })\n }\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n\n const tenantId = overrides?.tenantId ?? auth.tenantId ?? null\n if (!tenantId) {\n throw new CrudHttpError(400, { error: translate('sales.errors.tenant_required', 'Tenant context is required') })\n }\n\n const organizationIds = (() => {\n if (overrides?.organizationId) return [overrides.organizationId]\n if (scope?.selectedId) return [scope.selectedId]\n if (Array.isArray(scope?.filterIds) && scope.filterIds.length > 0) return scope.filterIds\n if (scope?.allowedIds === null) return null\n if (auth.orgId) return [auth.orgId]\n return []\n })()\n\n if (organizationIds !== null && organizationIds.length === 0) {\n throw new CrudHttpError(400, { error: translate('sales.errors.organization_required', 'Organization context is required') })\n }\n\n const em = container.resolve('em') as EntityManager\n\n return {\n container,\n em,\n tenantId,\n organizationIds,\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,8BAAiD;AAC1D,SAAS,0BAA0B;AACnC,SAAS,qBAAqB;AAC9B,SAAS,0CAA0C;AASnD,eAAsB,mBACpB,KACA,WACA,WAC6B;AAC7B,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,6BAA6B,cAAc,EAAE,CAAC;AAAA,EAChG;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AAExF,QAAM,WAAW,WAAW,YAAY,KAAK,YAAY;AACzD,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,gCAAgC,4BAA4B,EAAE,CAAC;AAAA,EACjH;AAEA,QAAM,mBAAmB,MAAM;AAC7B,QAAI,WAAW,eAAgB,QAAO,CAAC,UAAU,cAAc;AAC/D,QAAI,OAAO,WAAY,QAAO,CAAC,MAAM,UAAU;AAC/C,QAAI,MAAM,QAAQ,OAAO,SAAS,KAAK,MAAM,UAAU,SAAS,EAAG,QAAO,MAAM;AAChF,QAAI,OAAO,eAAe,KAAM,QAAO;AACvC,QAAI,KAAK,MAAO,QAAO,CAAC,KAAK,KAAK;AAClC,WAAO,CAAC;AAAA,EACV,GAAG;AAEH,MAAI,oBAAoB,QAAQ,gBAAgB,WAAW,GAAG;AAC5D,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,sCAAsC,kCAAkC,EAAE,CAAC;AAAA,EAC7H;AAEA,QAAM,KAAK,UAAU,QAAQ,IAAI;AAEjC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function extractCustomerName(snapshot) {
|
|
2
|
+
if (!snapshot || typeof snapshot !== "object") return null;
|
|
3
|
+
const data = snapshot;
|
|
4
|
+
const candidates = [
|
|
5
|
+
data.display_name,
|
|
6
|
+
data.displayName,
|
|
7
|
+
data.name,
|
|
8
|
+
data.company_name,
|
|
9
|
+
data.companyName,
|
|
10
|
+
data.full_name,
|
|
11
|
+
data.fullName
|
|
12
|
+
];
|
|
13
|
+
for (const candidate of candidates) {
|
|
14
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) return candidate;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
extractCustomerName
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=customerSnapshot.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sales/lib/customerSnapshot.ts"],
|
|
4
|
+
"sourcesContent": ["export function extractCustomerName(snapshot: unknown): string | null {\n if (!snapshot || typeof snapshot !== 'object') return null\n const data = snapshot as Record<string, unknown>\n const candidates = [\n data.display_name,\n data.displayName,\n data.name,\n data.company_name,\n data.companyName,\n data.full_name,\n data.fullName,\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) return candidate\n }\n return null\n}\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,oBAAoB,UAAkC;AACpE,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,QAAM,OAAO;AACb,QAAM,aAAa;AAAA,IACjB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACA,aAAW,aAAa,YAAY;AAClC,QAAI,OAAO,cAAc,YAAY,UAAU,KAAK,EAAE,SAAS,EAAG,QAAO;AAAA,EAC3E;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const DATE_PERIOD_OPTIONS = ["last24h", "last7d", "last30d", "custom"];
|
|
2
|
+
function parseDateInput(value) {
|
|
3
|
+
if (!value || typeof value !== "string") return null;
|
|
4
|
+
const date = new Date(value);
|
|
5
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
6
|
+
return date;
|
|
7
|
+
}
|
|
8
|
+
function resolveDateRange(period, customFrom, customTo, now = /* @__PURE__ */ new Date()) {
|
|
9
|
+
const to = now;
|
|
10
|
+
switch (period) {
|
|
11
|
+
case "last24h": {
|
|
12
|
+
const from = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
|
|
13
|
+
return { from, to };
|
|
14
|
+
}
|
|
15
|
+
case "last7d": {
|
|
16
|
+
const from = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3);
|
|
17
|
+
return { from, to };
|
|
18
|
+
}
|
|
19
|
+
case "last30d": {
|
|
20
|
+
const from = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3);
|
|
21
|
+
return { from, to };
|
|
22
|
+
}
|
|
23
|
+
case "custom": {
|
|
24
|
+
const from = customFrom ?? /* @__PURE__ */ new Date(0);
|
|
25
|
+
const boundedTo = customTo ?? now;
|
|
26
|
+
return { from, to: boundedTo };
|
|
27
|
+
}
|
|
28
|
+
default: {
|
|
29
|
+
const from = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
|
|
30
|
+
return { from, to };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
DATE_PERIOD_OPTIONS,
|
|
36
|
+
parseDateInput,
|
|
37
|
+
resolveDateRange
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=dateRange.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sales/lib/dateRange.ts"],
|
|
4
|
+
"sourcesContent": ["export type DatePeriodOption = 'last24h' | 'last7d' | 'last30d' | 'custom'\n\nexport const DATE_PERIOD_OPTIONS: DatePeriodOption[] = ['last24h', 'last7d', 'last30d', 'custom']\n\nexport function parseDateInput(value?: string | null): Date | null {\n if (!value || typeof value !== 'string') return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date\n}\n\nexport function resolveDateRange(\n period: DatePeriodOption,\n customFrom?: Date | null,\n customTo?: Date | null,\n now: Date = new Date(),\n): { from: Date; to: Date } {\n const to = now\n switch (period) {\n case 'last24h': {\n const from = new Date(now.getTime() - 24 * 60 * 60 * 1000)\n return { from, to }\n }\n case 'last7d': {\n const from = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)\n return { from, to }\n }\n case 'last30d': {\n const from = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)\n return { from, to }\n }\n case 'custom': {\n const from = customFrom ?? new Date(0)\n const boundedTo = customTo ?? now\n return { from, to: boundedTo }\n }\n default: {\n const from = new Date(now.getTime() - 24 * 60 * 60 * 1000)\n return { from, to }\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,sBAA0C,CAAC,WAAW,UAAU,WAAW,QAAQ;AAEzF,SAAS,eAAe,OAAoC;AACjE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO;AACzC,SAAO;AACT;AAEO,SAAS,iBACd,QACA,YACA,UACA,MAAY,oBAAI,KAAK,GACK;AAC1B,QAAM,KAAK;AACX,UAAQ,QAAQ;AAAA,IACd,KAAK,WAAW;AACd,YAAM,OAAO,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,KAAK,KAAK,GAAI;AACzD,aAAO,EAAE,MAAM,GAAG;AAAA,IACpB;AAAA,IACA,KAAK,UAAU;AACb,YAAM,OAAO,IAAI,KAAK,IAAI,QAAQ,IAAI,IAAI,KAAK,KAAK,KAAK,GAAI;AAC7D,aAAO,EAAE,MAAM,GAAG;AAAA,IACpB;AAAA,IACA,KAAK,WAAW;AACd,YAAM,OAAO,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,KAAK,KAAK,KAAK,GAAI;AAC9D,aAAO,EAAE,MAAM,GAAG;AAAA,IACpB;AAAA,IACA,KAAK,UAAU;AACb,YAAM,OAAO,cAAc,oBAAI,KAAK,CAAC;AACrC,YAAM,YAAY,YAAY;AAC9B,aAAO,EAAE,MAAM,IAAI,UAAU;AAAA,IAC/B;AAAA,IACA,SAAS;AACP,YAAM,OAAO,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,KAAK,KAAK,GAAI;AACzD,aAAO,EAAE,MAAM,GAAG;AAAA,IACpB;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const DEFAULT_SETTINGS = {
|
|
2
|
+
pageSize: 5,
|
|
3
|
+
datePeriod: "last24h"
|
|
4
|
+
};
|
|
5
|
+
function hydrateSalesNewOrdersSettings(raw) {
|
|
6
|
+
if (!raw || typeof raw !== "object") return { ...DEFAULT_SETTINGS };
|
|
7
|
+
const input = raw;
|
|
8
|
+
const parsedPageSize = Number(input.pageSize);
|
|
9
|
+
const pageSize = Number.isFinite(parsedPageSize) ? Math.min(20, Math.max(1, Math.floor(parsedPageSize))) : DEFAULT_SETTINGS.pageSize;
|
|
10
|
+
const datePeriod = input.datePeriod === "last24h" || input.datePeriod === "last7d" || input.datePeriod === "last30d" || input.datePeriod === "custom" ? input.datePeriod : DEFAULT_SETTINGS.datePeriod;
|
|
11
|
+
let customFrom;
|
|
12
|
+
let customTo;
|
|
13
|
+
if (datePeriod === "custom") {
|
|
14
|
+
if (typeof input.customFrom === "string" && !Number.isNaN(new Date(input.customFrom).getTime())) {
|
|
15
|
+
customFrom = input.customFrom;
|
|
16
|
+
}
|
|
17
|
+
if (typeof input.customTo === "string" && !Number.isNaN(new Date(input.customTo).getTime())) {
|
|
18
|
+
customTo = input.customTo;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
pageSize,
|
|
23
|
+
datePeriod,
|
|
24
|
+
customFrom,
|
|
25
|
+
customTo
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
DEFAULT_SETTINGS,
|
|
30
|
+
hydrateSalesNewOrdersSettings
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/sales/widgets/dashboard/new-orders/config.ts"],
|
|
4
|
+
"sourcesContent": ["import type { DatePeriodOption } from '../../../lib/dateRange'\n\nexport type SalesNewOrdersSettings = {\n pageSize: number\n datePeriod: DatePeriodOption\n customFrom?: string\n customTo?: string\n}\n\nexport const DEFAULT_SETTINGS: SalesNewOrdersSettings = {\n pageSize: 5,\n datePeriod: 'last24h',\n}\n\nexport function hydrateSalesNewOrdersSettings(raw: unknown): SalesNewOrdersSettings {\n if (!raw || typeof raw !== 'object') return { ...DEFAULT_SETTINGS }\n const input = raw as Partial<SalesNewOrdersSettings>\n const parsedPageSize = Number(input.pageSize)\n const pageSize = Number.isFinite(parsedPageSize)\n ? Math.min(20, Math.max(1, Math.floor(parsedPageSize)))\n : DEFAULT_SETTINGS.pageSize\n\n const datePeriod: DatePeriodOption =\n input.datePeriod === 'last24h' ||\n input.datePeriod === 'last7d' ||\n input.datePeriod === 'last30d' ||\n input.datePeriod === 'custom'\n ? input.datePeriod\n : DEFAULT_SETTINGS.datePeriod\n\n let customFrom: string | undefined\n let customTo: string | undefined\n if (datePeriod === 'custom') {\n if (typeof input.customFrom === 'string' && !Number.isNaN(new Date(input.customFrom).getTime())) {\n customFrom = input.customFrom\n }\n if (typeof input.customTo === 'string' && !Number.isNaN(new Date(input.customTo).getTime())) {\n customTo = input.customTo\n }\n }\n\n return {\n pageSize,\n datePeriod,\n customFrom,\n customTo,\n }\n}\n"],
|
|
5
|
+
"mappings": "AASO,MAAM,mBAA2C;AAAA,EACtD,UAAU;AAAA,EACV,YAAY;AACd;AAEO,SAAS,8BAA8B,KAAsC;AAClF,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO,EAAE,GAAG,iBAAiB;AAClE,QAAM,QAAQ;AACd,QAAM,iBAAiB,OAAO,MAAM,QAAQ;AAC5C,QAAM,WAAW,OAAO,SAAS,cAAc,IAC3C,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,cAAc,CAAC,CAAC,IACpD,iBAAiB;AAErB,QAAM,aACJ,MAAM,eAAe,aACrB,MAAM,eAAe,YACrB,MAAM,eAAe,aACrB,MAAM,eAAe,WACjB,MAAM,aACN,iBAAiB;AAEvB,MAAI;AACJ,MAAI;AACJ,MAAI,eAAe,UAAU;AAC3B,QAAI,OAAO,MAAM,eAAe,YAAY,CAAC,OAAO,MAAM,IAAI,KAAK,MAAM,UAAU,EAAE,QAAQ,CAAC,GAAG;AAC/F,mBAAa,MAAM;AAAA,IACrB;AACA,QAAI,OAAO,MAAM,aAAa,YAAY,CAAC,OAAO,MAAM,IAAI,KAAK,MAAM,QAAQ,EAAE,QAAQ,CAAC,GAAG;AAC3F,iBAAW,MAAM;AAAA,IACnB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
6
|
+
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
7
|
+
import { Badge } from "@open-mercato/ui/primitives/badge";
|
|
8
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_SETTINGS,
|
|
11
|
+
hydrateSalesNewOrdersSettings
|
|
12
|
+
} from "./config.js";
|
|
13
|
+
function formatCurrency(value, currency, locale) {
|
|
14
|
+
const amount = typeof value === "string" ? Number(value) : Number.NaN;
|
|
15
|
+
if (!Number.isFinite(amount)) return "\u2014";
|
|
16
|
+
const code = currency || "USD";
|
|
17
|
+
try {
|
|
18
|
+
return new Intl.NumberFormat(locale ?? void 0, { style: "currency", currency: code }).format(amount);
|
|
19
|
+
} catch {
|
|
20
|
+
return `${amount.toFixed(2)} ${code}`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function formatRelativeDate(value, locale) {
|
|
24
|
+
const date = new Date(value);
|
|
25
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
26
|
+
const now = /* @__PURE__ */ new Date();
|
|
27
|
+
const diffMs = date.getTime() - now.getTime();
|
|
28
|
+
const absMs = Math.abs(diffMs);
|
|
29
|
+
const rtf = new Intl.RelativeTimeFormat(locale ?? void 0, { numeric: "auto" });
|
|
30
|
+
if (absMs < 60 * 1e3) {
|
|
31
|
+
return rtf.format(Math.round(diffMs / 1e3), "second");
|
|
32
|
+
}
|
|
33
|
+
if (absMs < 60 * 60 * 1e3) {
|
|
34
|
+
return rtf.format(Math.round(diffMs / (60 * 1e3)), "minute");
|
|
35
|
+
}
|
|
36
|
+
if (absMs < 24 * 60 * 60 * 1e3) {
|
|
37
|
+
return rtf.format(Math.round(diffMs / (60 * 60 * 1e3)), "hour");
|
|
38
|
+
}
|
|
39
|
+
return rtf.format(Math.round(diffMs / (24 * 60 * 60 * 1e3)), "day");
|
|
40
|
+
}
|
|
41
|
+
async function loadOrders(settings) {
|
|
42
|
+
const params = new URLSearchParams({
|
|
43
|
+
limit: String(settings.pageSize),
|
|
44
|
+
datePeriod: settings.datePeriod
|
|
45
|
+
});
|
|
46
|
+
if (settings.datePeriod === "custom") {
|
|
47
|
+
if (settings.customFrom) params.set("customFrom", settings.customFrom);
|
|
48
|
+
if (settings.customTo) params.set("customTo", settings.customTo);
|
|
49
|
+
}
|
|
50
|
+
const call = await apiCall(
|
|
51
|
+
`/api/sales/dashboard/widgets/new-orders?${params.toString()}`
|
|
52
|
+
);
|
|
53
|
+
if (!call.ok) {
|
|
54
|
+
const message = typeof call.result?.error === "string" ? call.result.error : `Request failed with status ${call.status}`;
|
|
55
|
+
throw new Error(message);
|
|
56
|
+
}
|
|
57
|
+
const payload = call.result ?? {};
|
|
58
|
+
const rawItems = Array.isArray(payload.items) ? payload.items : [];
|
|
59
|
+
return rawItems.map((item) => {
|
|
60
|
+
if (!item || typeof item !== "object") return null;
|
|
61
|
+
const data = item;
|
|
62
|
+
return {
|
|
63
|
+
id: typeof data.id === "string" ? data.id : null,
|
|
64
|
+
orderNumber: typeof data.orderNumber === "string" ? data.orderNumber : "",
|
|
65
|
+
status: typeof data.status === "string" ? data.status : null,
|
|
66
|
+
fulfillmentStatus: typeof data.fulfillmentStatus === "string" ? data.fulfillmentStatus : null,
|
|
67
|
+
paymentStatus: typeof data.paymentStatus === "string" ? data.paymentStatus : null,
|
|
68
|
+
customerName: typeof data.customerName === "string" ? data.customerName : null,
|
|
69
|
+
customerEntityId: typeof data.customerEntityId === "string" ? data.customerEntityId : null,
|
|
70
|
+
netAmount: typeof data.netAmount === "string" ? data.netAmount : "0",
|
|
71
|
+
grossAmount: typeof data.grossAmount === "string" ? data.grossAmount : "0",
|
|
72
|
+
currency: typeof data.currency === "string" ? data.currency : null,
|
|
73
|
+
createdAt: typeof data.createdAt === "string" ? data.createdAt : ""
|
|
74
|
+
};
|
|
75
|
+
}).filter((item) => !!item && !!item.id && !!item.createdAt);
|
|
76
|
+
}
|
|
77
|
+
const SalesNewOrdersWidget = ({
|
|
78
|
+
mode,
|
|
79
|
+
settings = DEFAULT_SETTINGS,
|
|
80
|
+
onSettingsChange,
|
|
81
|
+
refreshToken,
|
|
82
|
+
onRefreshStateChange
|
|
83
|
+
}) => {
|
|
84
|
+
const t = useT();
|
|
85
|
+
const hydrated = React.useMemo(() => hydrateSalesNewOrdersSettings(settings), [settings]);
|
|
86
|
+
const [items, setItems] = React.useState([]);
|
|
87
|
+
const [loading, setLoading] = React.useState(true);
|
|
88
|
+
const [error, setError] = React.useState(null);
|
|
89
|
+
const [locale, setLocale] = React.useState(void 0);
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
if (typeof navigator !== "undefined") {
|
|
92
|
+
setLocale(navigator.language);
|
|
93
|
+
}
|
|
94
|
+
}, []);
|
|
95
|
+
const refresh = React.useCallback(async () => {
|
|
96
|
+
onRefreshStateChange?.(true);
|
|
97
|
+
setLoading(true);
|
|
98
|
+
setError(null);
|
|
99
|
+
try {
|
|
100
|
+
const data = await loadOrders(hydrated);
|
|
101
|
+
setItems(data);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error("Failed to load new orders widget data", err);
|
|
104
|
+
setError(t("sales.widgets.newOrders.error", "Failed to load orders"));
|
|
105
|
+
} finally {
|
|
106
|
+
setLoading(false);
|
|
107
|
+
onRefreshStateChange?.(false);
|
|
108
|
+
}
|
|
109
|
+
}, [hydrated, onRefreshStateChange, t]);
|
|
110
|
+
React.useEffect(() => {
|
|
111
|
+
refresh().catch(() => {
|
|
112
|
+
});
|
|
113
|
+
}, [refresh, refreshToken]);
|
|
114
|
+
if (mode === "settings") {
|
|
115
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 text-sm", children: [
|
|
116
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
117
|
+
/* @__PURE__ */ jsx(
|
|
118
|
+
"label",
|
|
119
|
+
{
|
|
120
|
+
htmlFor: "sales-new-orders-page-size",
|
|
121
|
+
className: "text-xs font-semibold uppercase text-muted-foreground",
|
|
122
|
+
children: t("sales.widgets.newOrders.settings.pageSize", "Number of Orders")
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
/* @__PURE__ */ jsx(
|
|
126
|
+
"input",
|
|
127
|
+
{
|
|
128
|
+
id: "sales-new-orders-page-size",
|
|
129
|
+
type: "number",
|
|
130
|
+
min: 1,
|
|
131
|
+
max: 20,
|
|
132
|
+
className: "w-24 rounded-md border px-2 py-1 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary",
|
|
133
|
+
value: hydrated.pageSize,
|
|
134
|
+
onChange: (event) => {
|
|
135
|
+
const next = Number(event.target.value);
|
|
136
|
+
const value = Number.isFinite(next) ? Math.min(20, Math.max(1, Math.floor(next))) : hydrated.pageSize;
|
|
137
|
+
onSettingsChange({ ...hydrated, pageSize: value });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
"label",
|
|
145
|
+
{
|
|
146
|
+
htmlFor: "sales-new-orders-date-period",
|
|
147
|
+
className: "text-xs font-semibold uppercase text-muted-foreground",
|
|
148
|
+
children: t("sales.widgets.newOrders.settings.datePeriod", "Date Period")
|
|
149
|
+
}
|
|
150
|
+
),
|
|
151
|
+
/* @__PURE__ */ jsxs(
|
|
152
|
+
"select",
|
|
153
|
+
{
|
|
154
|
+
id: "sales-new-orders-date-period",
|
|
155
|
+
className: "w-full rounded-md border px-2 py-1 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary",
|
|
156
|
+
value: hydrated.datePeriod,
|
|
157
|
+
onChange: (event) => {
|
|
158
|
+
onSettingsChange({ ...hydrated, datePeriod: event.target.value });
|
|
159
|
+
},
|
|
160
|
+
children: [
|
|
161
|
+
/* @__PURE__ */ jsx("option", { value: "last24h", children: t("sales.widgets.newOrders.settings.last24h", "Last 24 hours") }),
|
|
162
|
+
/* @__PURE__ */ jsx("option", { value: "last7d", children: t("sales.widgets.newOrders.settings.last7d", "Last 7 days") }),
|
|
163
|
+
/* @__PURE__ */ jsx("option", { value: "last30d", children: t("sales.widgets.newOrders.settings.last30d", "Last 30 days") }),
|
|
164
|
+
/* @__PURE__ */ jsx("option", { value: "custom", children: t("sales.widgets.newOrders.settings.custom", "Custom range") })
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
] }),
|
|
169
|
+
hydrated.datePeriod === "custom" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
170
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
171
|
+
/* @__PURE__ */ jsx(
|
|
172
|
+
"label",
|
|
173
|
+
{
|
|
174
|
+
htmlFor: "sales-new-orders-custom-from",
|
|
175
|
+
className: "text-xs font-semibold uppercase text-muted-foreground",
|
|
176
|
+
children: t("sales.widgets.newOrders.settings.customFrom", "From")
|
|
177
|
+
}
|
|
178
|
+
),
|
|
179
|
+
/* @__PURE__ */ jsx(
|
|
180
|
+
"input",
|
|
181
|
+
{
|
|
182
|
+
id: "sales-new-orders-custom-from",
|
|
183
|
+
type: "date",
|
|
184
|
+
className: "w-full rounded-md border px-2 py-1 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary",
|
|
185
|
+
value: hydrated.customFrom ?? "",
|
|
186
|
+
onChange: (event) => {
|
|
187
|
+
onSettingsChange({ ...hydrated, customFrom: event.target.value });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
] }),
|
|
192
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
193
|
+
/* @__PURE__ */ jsx(
|
|
194
|
+
"label",
|
|
195
|
+
{
|
|
196
|
+
htmlFor: "sales-new-orders-custom-to",
|
|
197
|
+
className: "text-xs font-semibold uppercase text-muted-foreground",
|
|
198
|
+
children: t("sales.widgets.newOrders.settings.customTo", "To")
|
|
199
|
+
}
|
|
200
|
+
),
|
|
201
|
+
/* @__PURE__ */ jsx(
|
|
202
|
+
"input",
|
|
203
|
+
{
|
|
204
|
+
id: "sales-new-orders-custom-to",
|
|
205
|
+
type: "date",
|
|
206
|
+
className: "w-full rounded-md border px-2 py-1 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary",
|
|
207
|
+
value: hydrated.customTo ?? "",
|
|
208
|
+
onChange: (event) => {
|
|
209
|
+
onSettingsChange({ ...hydrated, customTo: event.target.value });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
] })
|
|
214
|
+
] }) : null
|
|
215
|
+
] });
|
|
216
|
+
}
|
|
217
|
+
if (error) {
|
|
218
|
+
return /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error });
|
|
219
|
+
}
|
|
220
|
+
if (loading) {
|
|
221
|
+
return /* @__PURE__ */ jsx("div", { className: "flex h-32 items-center justify-center", children: /* @__PURE__ */ jsx(Spinner, { className: "h-6 w-6 text-muted-foreground" }) });
|
|
222
|
+
}
|
|
223
|
+
if (items.length === 0) {
|
|
224
|
+
return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("sales.widgets.newOrders.empty", "No orders found in this period") });
|
|
225
|
+
}
|
|
226
|
+
return /* @__PURE__ */ jsx("ul", { className: "space-y-3", children: items.map((order) => {
|
|
227
|
+
const createdLabel = formatRelativeDate(order.createdAt, locale);
|
|
228
|
+
return /* @__PURE__ */ jsx("li", { className: "rounded-md border p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
229
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
230
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
231
|
+
/* @__PURE__ */ jsx(
|
|
232
|
+
Link,
|
|
233
|
+
{
|
|
234
|
+
className: "text-sm font-medium text-foreground hover:underline",
|
|
235
|
+
href: `/backend/sales/orders/${encodeURIComponent(order.id)}`,
|
|
236
|
+
children: order.orderNumber
|
|
237
|
+
}
|
|
238
|
+
),
|
|
239
|
+
order.status ? /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-[11px]", children: order.status }) : null
|
|
240
|
+
] }),
|
|
241
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: order.customerName ?? t("sales.widgets.newOrders.noCustomer", "No customer") }),
|
|
242
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: createdLabel || t("sales.widgets.newOrders.unknownDate", "Unknown date") })
|
|
243
|
+
] }),
|
|
244
|
+
/* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: formatCurrency(order.grossAmount, order.currency, locale) }) })
|
|
245
|
+
] }) }, order.id);
|
|
246
|
+
}) });
|
|
247
|
+
};
|
|
248
|
+
var widget_client_default = SalesNewOrdersWidget;
|
|
249
|
+
export {
|
|
250
|
+
widget_client_default as default
|
|
251
|
+
};
|
|
252
|
+
//# sourceMappingURL=widget.client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n DEFAULT_SETTINGS,\n hydrateSalesNewOrdersSettings,\n type SalesNewOrdersSettings,\n} from './config'\nimport type { DatePeriodOption } from '../../../lib/dateRange'\n\ntype OrderItem = {\n id: string\n orderNumber: string\n status: string | null\n fulfillmentStatus: string | null\n paymentStatus: string | null\n customerName: string | null\n customerEntityId: string | null\n netAmount: string\n grossAmount: string\n currency: string | null\n createdAt: string\n}\n\nfunction formatCurrency(value: string | null, currency: string | null, locale?: string): string {\n const amount = typeof value === 'string' ? Number(value) : Number.NaN\n if (!Number.isFinite(amount)) return '\u2014'\n const code = currency || 'USD'\n try {\n return new Intl.NumberFormat(locale ?? undefined, { style: 'currency', currency: code }).format(amount)\n } catch {\n return `${amount.toFixed(2)} ${code}`\n }\n}\n\nfunction formatRelativeDate(value: string, locale?: string): string {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n const now = new Date()\n const diffMs = date.getTime() - now.getTime()\n const absMs = Math.abs(diffMs)\n const rtf = new Intl.RelativeTimeFormat(locale ?? undefined, { numeric: 'auto' })\n if (absMs < 60 * 1000) {\n return rtf.format(Math.round(diffMs / 1000), 'second')\n }\n if (absMs < 60 * 60 * 1000) {\n return rtf.format(Math.round(diffMs / (60 * 1000)), 'minute')\n }\n if (absMs < 24 * 60 * 60 * 1000) {\n return rtf.format(Math.round(diffMs / (60 * 60 * 1000)), 'hour')\n }\n return rtf.format(Math.round(diffMs / (24 * 60 * 60 * 1000)), 'day')\n}\n\nasync function loadOrders(settings: SalesNewOrdersSettings): Promise<OrderItem[]> {\n const params = new URLSearchParams({\n limit: String(settings.pageSize),\n datePeriod: settings.datePeriod,\n })\n if (settings.datePeriod === 'custom') {\n if (settings.customFrom) params.set('customFrom', settings.customFrom)\n if (settings.customTo) params.set('customTo', settings.customTo)\n }\n const call = await apiCall<{ items?: unknown[]; error?: string }>(\n `/api/sales/dashboard/widgets/new-orders?${params.toString()}`,\n )\n if (!call.ok) {\n const message =\n typeof (call.result as Record<string, unknown> | null)?.error === 'string'\n ? ((call.result as Record<string, unknown>).error as string)\n : `Request failed with status ${call.status}`\n throw new Error(message)\n }\n const payload = call.result ?? {}\n const rawItems = Array.isArray((payload as { items?: unknown[] }).items)\n ? (payload as { items: unknown[] }).items\n : []\n return rawItems\n .map((item: unknown): OrderItem | null => {\n if (!item || typeof item !== 'object') return null\n const data = item as any\n return {\n id: typeof data.id === 'string' ? data.id : null,\n orderNumber: typeof data.orderNumber === 'string' ? data.orderNumber : '',\n status: typeof data.status === 'string' ? data.status : null,\n fulfillmentStatus: typeof data.fulfillmentStatus === 'string' ? data.fulfillmentStatus : null,\n paymentStatus: typeof data.paymentStatus === 'string' ? data.paymentStatus : null,\n customerName: typeof data.customerName === 'string' ? data.customerName : null,\n customerEntityId: typeof data.customerEntityId === 'string' ? data.customerEntityId : null,\n netAmount: typeof data.netAmount === 'string' ? data.netAmount : '0',\n grossAmount: typeof data.grossAmount === 'string' ? data.grossAmount : '0',\n currency: typeof data.currency === 'string' ? data.currency : null,\n createdAt: typeof data.createdAt === 'string' ? data.createdAt : '',\n }\n })\n .filter((item: OrderItem | null): item is OrderItem => !!item && !!item.id && !!item.createdAt)\n}\n\nconst SalesNewOrdersWidget: React.FC<DashboardWidgetComponentProps<SalesNewOrdersSettings>> = ({\n mode,\n settings = DEFAULT_SETTINGS,\n onSettingsChange,\n refreshToken,\n onRefreshStateChange,\n}) => {\n const t = useT()\n const hydrated = React.useMemo(() => hydrateSalesNewOrdersSettings(settings), [settings])\n const [items, setItems] = React.useState<OrderItem[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [locale, setLocale] = React.useState<string | undefined>(undefined)\n\n React.useEffect(() => {\n if (typeof navigator !== 'undefined') {\n setLocale(navigator.language)\n }\n }, [])\n\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const data = await loadOrders(hydrated)\n setItems(data)\n } catch (err) {\n console.error('Failed to load new orders widget data', err)\n setError(t('sales.widgets.newOrders.error', 'Failed to load orders'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, onRefreshStateChange, t])\n\n React.useEffect(() => {\n refresh().catch(() => {})\n }, [refresh, refreshToken])\n\n if (mode === 'settings') {\n return (\n <div className=\"space-y-4 text-sm\">\n <div className=\"space-y-1.5\">\n <label\n htmlFor=\"sales-new-orders-page-size\"\n className=\"text-xs font-semibold uppercase text-muted-foreground\"\n >\n {t('sales.widgets.newOrders.settings.pageSize', 'Number of Orders')}\n </label>\n <input\n id=\"sales-new-orders-page-size\"\n type=\"number\"\n min={1}\n max={20}\n className=\"w-24 rounded-md border px-2 py-1 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n value={hydrated.pageSize}\n onChange={(event) => {\n const next = Number(event.target.value)\n const value = Number.isFinite(next)\n ? Math.min(20, Math.max(1, Math.floor(next)))\n : hydrated.pageSize\n onSettingsChange({ ...hydrated, pageSize: value })\n }}\n />\n </div>\n <div className=\"space-y-1.5\">\n <label\n htmlFor=\"sales-new-orders-date-period\"\n className=\"text-xs font-semibold uppercase text-muted-foreground\"\n >\n {t('sales.widgets.newOrders.settings.datePeriod', 'Date Period')}\n </label>\n <select\n id=\"sales-new-orders-date-period\"\n className=\"w-full rounded-md border px-2 py-1 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n value={hydrated.datePeriod}\n onChange={(event) => {\n onSettingsChange({ ...hydrated, datePeriod: event.target.value as DatePeriodOption })\n }}\n >\n <option value=\"last24h\">{t('sales.widgets.newOrders.settings.last24h', 'Last 24 hours')}</option>\n <option value=\"last7d\">{t('sales.widgets.newOrders.settings.last7d', 'Last 7 days')}</option>\n <option value=\"last30d\">{t('sales.widgets.newOrders.settings.last30d', 'Last 30 days')}</option>\n <option value=\"custom\">{t('sales.widgets.newOrders.settings.custom', 'Custom range')}</option>\n </select>\n </div>\n {hydrated.datePeriod === 'custom' ? (\n <>\n <div className=\"space-y-1.5\">\n <label\n htmlFor=\"sales-new-orders-custom-from\"\n className=\"text-xs font-semibold uppercase text-muted-foreground\"\n >\n {t('sales.widgets.newOrders.settings.customFrom', 'From')}\n </label>\n <input\n id=\"sales-new-orders-custom-from\"\n type=\"date\"\n className=\"w-full rounded-md border px-2 py-1 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n value={hydrated.customFrom ?? ''}\n onChange={(event) => {\n onSettingsChange({ ...hydrated, customFrom: event.target.value })\n }}\n />\n </div>\n <div className=\"space-y-1.5\">\n <label\n htmlFor=\"sales-new-orders-custom-to\"\n className=\"text-xs font-semibold uppercase text-muted-foreground\"\n >\n {t('sales.widgets.newOrders.settings.customTo', 'To')}\n </label>\n <input\n id=\"sales-new-orders-custom-to\"\n type=\"date\"\n className=\"w-full rounded-md border px-2 py-1 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n value={hydrated.customTo ?? ''}\n onChange={(event) => {\n onSettingsChange({ ...hydrated, customTo: event.target.value })\n }}\n />\n </div>\n </>\n ) : null}\n </div>\n )\n }\n\n if (error) {\n return <p className=\"text-sm text-destructive\">{error}</p>\n }\n\n if (loading) {\n return (\n <div className=\"flex h-32 items-center justify-center\">\n <Spinner className=\"h-6 w-6 text-muted-foreground\" />\n </div>\n )\n }\n\n if (items.length === 0) {\n return (\n <p className=\"text-sm text-muted-foreground\">\n {t('sales.widgets.newOrders.empty', 'No orders found in this period')}\n </p>\n )\n }\n\n return (\n <ul className=\"space-y-3\">\n {items.map((order) => {\n const createdLabel = formatRelativeDate(order.createdAt, locale)\n return (\n <li key={order.id} className=\"rounded-md border p-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <Link\n className=\"text-sm font-medium text-foreground hover:underline\"\n href={`/backend/sales/orders/${encodeURIComponent(order.id)}`}\n >\n {order.orderNumber}\n </Link>\n {order.status ? (\n <Badge variant=\"outline\" className=\"text-[11px]\">\n {order.status}\n </Badge>\n ) : null}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {order.customerName ?? t('sales.widgets.newOrders.noCustomer', 'No customer')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {createdLabel || t('sales.widgets.newOrders.unknownDate', 'Unknown date')}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold\">\n {formatCurrency(order.grossAmount, order.currency, locale)}\n </p>\n </div>\n </div>\n </li>\n )\n })}\n </ul>\n )\n}\n\nexport default SalesNewOrdersWidget\n"],
|
|
5
|
+
"mappings": ";AAmJQ,SA6CE,UA5CA,KADF;AAjJR,YAAY,WAAW;AACvB,OAAO,UAAU;AAEjB,SAAS,eAAe;AACxB,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAiBP,SAAS,eAAe,OAAsB,UAAyB,QAAyB;AAC9F,QAAM,SAAS,OAAO,UAAU,WAAW,OAAO,KAAK,IAAI,OAAO;AAClE,MAAI,CAAC,OAAO,SAAS,MAAM,EAAG,QAAO;AACrC,QAAM,OAAO,YAAY;AACzB,MAAI;AACF,WAAO,IAAI,KAAK,aAAa,UAAU,QAAW,EAAE,OAAO,YAAY,UAAU,KAAK,CAAC,EAAE,OAAO,MAAM;AAAA,EACxG,QAAQ;AACN,WAAO,GAAG,OAAO,QAAQ,CAAC,CAAC,IAAI,IAAI;AAAA,EACrC;AACF;AAEA,SAAS,mBAAmB,OAAe,QAAyB;AAClE,QAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO;AACzC,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,SAAS,KAAK,QAAQ,IAAI,IAAI,QAAQ;AAC5C,QAAM,QAAQ,KAAK,IAAI,MAAM;AAC7B,QAAM,MAAM,IAAI,KAAK,mBAAmB,UAAU,QAAW,EAAE,SAAS,OAAO,CAAC;AAChF,MAAI,QAAQ,KAAK,KAAM;AACrB,WAAO,IAAI,OAAO,KAAK,MAAM,SAAS,GAAI,GAAG,QAAQ;AAAA,EACvD;AACA,MAAI,QAAQ,KAAK,KAAK,KAAM;AAC1B,WAAO,IAAI,OAAO,KAAK,MAAM,UAAU,KAAK,IAAK,GAAG,QAAQ;AAAA,EAC9D;AACA,MAAI,QAAQ,KAAK,KAAK,KAAK,KAAM;AAC/B,WAAO,IAAI,OAAO,KAAK,MAAM,UAAU,KAAK,KAAK,IAAK,GAAG,MAAM;AAAA,EACjE;AACA,SAAO,IAAI,OAAO,KAAK,MAAM,UAAU,KAAK,KAAK,KAAK,IAAK,GAAG,KAAK;AACrE;AAEA,eAAe,WAAW,UAAwD;AAChF,QAAM,SAAS,IAAI,gBAAgB;AAAA,IACjC,OAAO,OAAO,SAAS,QAAQ;AAAA,IAC/B,YAAY,SAAS;AAAA,EACvB,CAAC;AACD,MAAI,SAAS,eAAe,UAAU;AACpC,QAAI,SAAS,WAAY,QAAO,IAAI,cAAc,SAAS,UAAU;AACrE,QAAI,SAAS,SAAU,QAAO,IAAI,YAAY,SAAS,QAAQ;AAAA,EACjE;AACA,QAAM,OAAO,MAAM;AAAA,IACjB,2CAA2C,OAAO,SAAS,CAAC;AAAA,EAC9D;AACA,MAAI,CAAC,KAAK,IAAI;AACZ,UAAM,UACJ,OAAQ,KAAK,QAA2C,UAAU,WAC5D,KAAK,OAAmC,QAC1C,8BAA8B,KAAK,MAAM;AAC/C,UAAM,IAAI,MAAM,OAAO;AAAA,EACzB;AACA,QAAM,UAAU,KAAK,UAAU,CAAC;AAChC,QAAM,WAAW,MAAM,QAAS,QAAkC,KAAK,IAClE,QAAiC,QAClC,CAAC;AACL,SAAO,SACJ,IAAI,CAAC,SAAoC;AACxC,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,UAAM,OAAO;AACb,WAAO;AAAA,MACL,IAAI,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK;AAAA,MAC5C,aAAa,OAAO,KAAK,gBAAgB,WAAW,KAAK,cAAc;AAAA,MACvE,QAAQ,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS;AAAA,MACxD,mBAAmB,OAAO,KAAK,sBAAsB,WAAW,KAAK,oBAAoB;AAAA,MACzF,eAAe,OAAO,KAAK,kBAAkB,WAAW,KAAK,gBAAgB;AAAA,MAC7E,cAAc,OAAO,KAAK,iBAAiB,WAAW,KAAK,eAAe;AAAA,MAC1E,kBAAkB,OAAO,KAAK,qBAAqB,WAAW,KAAK,mBAAmB;AAAA,MACtF,WAAW,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;AAAA,MACjE,aAAa,OAAO,KAAK,gBAAgB,WAAW,KAAK,cAAc;AAAA,MACvE,UAAU,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AAAA,MAC9D,WAAW,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;AAAA,IACnE;AAAA,EACF,CAAC,EACA,OAAO,CAAC,SAA8C,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,KAAK,SAAS;AAClG;AAEA,MAAM,uBAAwF,CAAC;AAAA,EAC7F;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,IAAI,KAAK;AACf,QAAM,WAAW,MAAM,QAAQ,MAAM,8BAA8B,QAAQ,GAAG,CAAC,QAAQ,CAAC;AACxF,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAsB,CAAC,CAAC;AACxD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAA6B,MAAS;AAExE,QAAM,UAAU,MAAM;AACpB,QAAI,OAAO,cAAc,aAAa;AACpC,gBAAU,UAAU,QAAQ;AAAA,IAC9B;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,MAAM,YAAY,YAAY;AAC5C,2BAAuB,IAAI;AAC3B,eAAW,IAAI;AACf,aAAS,IAAI;AACb,QAAI;AACF,YAAM,OAAO,MAAM,WAAW,QAAQ;AACtC,eAAS,IAAI;AAAA,IACf,SAAS,KAAK;AACZ,cAAQ,MAAM,yCAAyC,GAAG;AAC1D,eAAS,EAAE,iCAAiC,uBAAuB,CAAC;AAAA,IACtE,UAAE;AACA,iBAAW,KAAK;AAChB,6BAAuB,KAAK;AAAA,IAC9B;AAAA,EACF,GAAG,CAAC,UAAU,sBAAsB,CAAC,CAAC;AAEtC,QAAM,UAAU,MAAM;AACpB,YAAQ,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC1B,GAAG,CAAC,SAAS,YAAY,CAAC;AAE1B,MAAI,SAAS,YAAY;AACvB,WACE,qBAAC,SAAI,WAAU,qBACb;AAAA,2BAAC,SAAI,WAAU,eACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,WAAU;AAAA,YAET,YAAE,6CAA6C,kBAAkB;AAAA;AAAA,QACpE;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,KAAK;AAAA,YACL,KAAK;AAAA,YACL,WAAU;AAAA,YACV,OAAO,SAAS;AAAA,YAChB,UAAU,CAAC,UAAU;AACnB,oBAAM,OAAO,OAAO,MAAM,OAAO,KAAK;AACtC,oBAAM,QAAQ,OAAO,SAAS,IAAI,IAC9B,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC,IAC1C,SAAS;AACb,+BAAiB,EAAE,GAAG,UAAU,UAAU,MAAM,CAAC;AAAA,YACnD;AAAA;AAAA,QACF;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,eACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,WAAU;AAAA,YAET,YAAE,+CAA+C,aAAa;AAAA;AAAA,QACjE;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,WAAU;AAAA,YACV,OAAO,SAAS;AAAA,YAChB,UAAU,CAAC,UAAU;AACnB,+BAAiB,EAAE,GAAG,UAAU,YAAY,MAAM,OAAO,MAA0B,CAAC;AAAA,YACtF;AAAA,YAEA;AAAA,kCAAC,YAAO,OAAM,WAAW,YAAE,4CAA4C,eAAe,GAAE;AAAA,cACxF,oBAAC,YAAO,OAAM,UAAU,YAAE,2CAA2C,aAAa,GAAE;AAAA,cACpF,oBAAC,YAAO,OAAM,WAAW,YAAE,4CAA4C,cAAc,GAAE;AAAA,cACvF,oBAAC,YAAO,OAAM,UAAU,YAAE,2CAA2C,cAAc,GAAE;AAAA;AAAA;AAAA,QACvF;AAAA,SACF;AAAA,MACC,SAAS,eAAe,WACvB,iCACE;AAAA,6BAAC,SAAI,WAAU,eACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,WAAU;AAAA,cAET,YAAE,+CAA+C,MAAM;AAAA;AAAA,UAC1D;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,MAAK;AAAA,cACL,WAAU;AAAA,cACV,OAAO,SAAS,cAAc;AAAA,cAC9B,UAAU,CAAC,UAAU;AACnB,iCAAiB,EAAE,GAAG,UAAU,YAAY,MAAM,OAAO,MAAM,CAAC;AAAA,cAClE;AAAA;AAAA,UACF;AAAA,WACF;AAAA,QACA,qBAAC,SAAI,WAAU,eACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,WAAU;AAAA,cAET,YAAE,6CAA6C,IAAI;AAAA;AAAA,UACtD;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,MAAK;AAAA,cACL,WAAU;AAAA,cACV,OAAO,SAAS,YAAY;AAAA,cAC5B,UAAU,CAAC,UAAU;AACnB,iCAAiB,EAAE,GAAG,UAAU,UAAU,MAAM,OAAO,MAAM,CAAC;AAAA,cAChE;AAAA;AAAA,UACF;AAAA,WACF;AAAA,SACF,IACE;AAAA,OACN;AAAA,EAEJ;AAEA,MAAI,OAAO;AACT,WAAO,oBAAC,OAAE,WAAU,4BAA4B,iBAAM;AAAA,EACxD;AAEA,MAAI,SAAS;AACX,WACE,oBAAC,SAAI,WAAU,yCACb,8BAAC,WAAQ,WAAU,iCAAgC,GACrD;AAAA,EAEJ;AAEA,MAAI,MAAM,WAAW,GAAG;AACtB,WACE,oBAAC,OAAE,WAAU,iCACV,YAAE,iCAAiC,gCAAgC,GACtE;AAAA,EAEJ;AAEA,SACE,oBAAC,QAAG,WAAU,aACX,gBAAM,IAAI,CAAC,UAAU;AACpB,UAAM,eAAe,mBAAmB,MAAM,WAAW,MAAM;AAC/D,WACE,oBAAC,QAAkB,WAAU,yBAC3B,+BAAC,SAAI,WAAU,0CACb;AAAA,2BAAC,SAAI,WAAU,aACb;AAAA,6BAAC,SAAI,WAAU,2BACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,MAAM,yBAAyB,mBAAmB,MAAM,EAAE,CAAC;AAAA,cAE1D,gBAAM;AAAA;AAAA,UACT;AAAA,UACC,MAAM,SACL,oBAAC,SAAM,SAAQ,WAAU,WAAU,eAChC,gBAAM,QACT,IACE;AAAA,WACN;AAAA,QACA,oBAAC,OAAE,WAAU,iCACV,gBAAM,gBAAgB,EAAE,sCAAsC,aAAa,GAC9E;AAAA,QACA,oBAAC,OAAE,WAAU,iCACV,0BAAgB,EAAE,uCAAuC,cAAc,GAC1E;AAAA,SACF;AAAA,MACA,oBAAC,SAAI,WAAU,cACb,8BAAC,OAAE,WAAU,yBACV,yBAAe,MAAM,aAAa,MAAM,UAAU,MAAM,GAC3D,GACF;AAAA,OACF,KA5BO,MAAM,EA6Bf;AAAA,EAEJ,CAAC,GACH;AAEJ;AAEA,IAAO,wBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import SalesNewOrdersWidget from "./widget.client.js";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_SETTINGS,
|
|
4
|
+
hydrateSalesNewOrdersSettings
|
|
5
|
+
} from "./config.js";
|
|
6
|
+
const widget = {
|
|
7
|
+
metadata: {
|
|
8
|
+
id: "sales.dashboard.newOrders",
|
|
9
|
+
title: "New Orders",
|
|
10
|
+
description: "Displays recently created sales orders.",
|
|
11
|
+
features: ["dashboards.view", "sales.widgets.new-orders"],
|
|
12
|
+
defaultSize: "md",
|
|
13
|
+
defaultEnabled: true,
|
|
14
|
+
defaultSettings: DEFAULT_SETTINGS,
|
|
15
|
+
tags: ["sales", "orders"],
|
|
16
|
+
category: "sales",
|
|
17
|
+
icon: "lucide:shopping-cart",
|
|
18
|
+
supportsRefresh: true
|
|
19
|
+
},
|
|
20
|
+
Widget: SalesNewOrdersWidget,
|
|
21
|
+
hydrateSettings: hydrateSalesNewOrdersSettings,
|
|
22
|
+
dehydrateSettings: (settings) => ({
|
|
23
|
+
pageSize: settings.pageSize,
|
|
24
|
+
datePeriod: settings.datePeriod,
|
|
25
|
+
customFrom: settings.customFrom,
|
|
26
|
+
customTo: settings.customTo
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
var widget_default = widget;
|
|
30
|
+
export {
|
|
31
|
+
widget_default as default
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=widget.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/sales/widgets/dashboard/new-orders/widget.ts"],
|
|
4
|
+
"sourcesContent": ["import type { DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport SalesNewOrdersWidget from './widget.client'\nimport {\n DEFAULT_SETTINGS,\n hydrateSalesNewOrdersSettings,\n type SalesNewOrdersSettings,\n} from './config'\n\nconst widget: DashboardWidgetModule<SalesNewOrdersSettings> = {\n metadata: {\n id: 'sales.dashboard.newOrders',\n title: 'New Orders',\n description: 'Displays recently created sales orders.',\n features: ['dashboards.view', 'sales.widgets.new-orders'],\n defaultSize: 'md',\n defaultEnabled: true,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['sales', 'orders'],\n category: 'sales',\n icon: 'lucide:shopping-cart',\n supportsRefresh: true,\n },\n Widget: SalesNewOrdersWidget,\n hydrateSettings: hydrateSalesNewOrdersSettings,\n dehydrateSettings: (settings) => ({\n pageSize: settings.pageSize,\n datePeriod: settings.datePeriod,\n customFrom: settings.customFrom,\n customTo: settings.customTo,\n }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AACA,OAAO,0BAA0B;AACjC;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAEP,MAAM,SAAwD;AAAA,EAC5D,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,mBAAmB,0BAA0B;AAAA,IACxD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,SAAS,QAAQ;AAAA,IACxB,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,mBAAmB,CAAC,cAAc;AAAA,IAChC,UAAU,SAAS;AAAA,IACnB,YAAY,SAAS;AAAA,IACrB,YAAY,SAAS;AAAA,IACrB,UAAU,SAAS;AAAA,EACrB;AACF;AAEA,IAAO,iBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const DEFAULT_SETTINGS = {
|
|
2
|
+
pageSize: 5,
|
|
3
|
+
datePeriod: "last24h"
|
|
4
|
+
};
|
|
5
|
+
function hydrateSalesNewQuotesSettings(raw) {
|
|
6
|
+
if (!raw || typeof raw !== "object") return { ...DEFAULT_SETTINGS };
|
|
7
|
+
const input = raw;
|
|
8
|
+
const parsedPageSize = Number(input.pageSize);
|
|
9
|
+
const pageSize = Number.isFinite(parsedPageSize) ? Math.min(20, Math.max(1, Math.floor(parsedPageSize))) : DEFAULT_SETTINGS.pageSize;
|
|
10
|
+
const datePeriod = input.datePeriod === "last24h" || input.datePeriod === "last7d" || input.datePeriod === "last30d" || input.datePeriod === "custom" ? input.datePeriod : DEFAULT_SETTINGS.datePeriod;
|
|
11
|
+
let customFrom;
|
|
12
|
+
let customTo;
|
|
13
|
+
if (datePeriod === "custom") {
|
|
14
|
+
if (typeof input.customFrom === "string" && !Number.isNaN(new Date(input.customFrom).getTime())) {
|
|
15
|
+
customFrom = input.customFrom;
|
|
16
|
+
}
|
|
17
|
+
if (typeof input.customTo === "string" && !Number.isNaN(new Date(input.customTo).getTime())) {
|
|
18
|
+
customTo = input.customTo;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
pageSize,
|
|
23
|
+
datePeriod,
|
|
24
|
+
customFrom,
|
|
25
|
+
customTo
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
DEFAULT_SETTINGS,
|
|
30
|
+
hydrateSalesNewQuotesSettings
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/sales/widgets/dashboard/new-quotes/config.ts"],
|
|
4
|
+
"sourcesContent": ["import type { DatePeriodOption } from '../../../lib/dateRange'\n\nexport type SalesNewQuotesSettings = {\n pageSize: number\n datePeriod: DatePeriodOption\n customFrom?: string\n customTo?: string\n}\n\nexport const DEFAULT_SETTINGS: SalesNewQuotesSettings = {\n pageSize: 5,\n datePeriod: 'last24h',\n}\n\nexport function hydrateSalesNewQuotesSettings(raw: unknown): SalesNewQuotesSettings {\n if (!raw || typeof raw !== 'object') return { ...DEFAULT_SETTINGS }\n const input = raw as Partial<SalesNewQuotesSettings>\n const parsedPageSize = Number(input.pageSize)\n const pageSize = Number.isFinite(parsedPageSize)\n ? Math.min(20, Math.max(1, Math.floor(parsedPageSize)))\n : DEFAULT_SETTINGS.pageSize\n\n const datePeriod: DatePeriodOption =\n input.datePeriod === 'last24h' ||\n input.datePeriod === 'last7d' ||\n input.datePeriod === 'last30d' ||\n input.datePeriod === 'custom'\n ? input.datePeriod\n : DEFAULT_SETTINGS.datePeriod\n\n let customFrom: string | undefined\n let customTo: string | undefined\n if (datePeriod === 'custom') {\n if (typeof input.customFrom === 'string' && !Number.isNaN(new Date(input.customFrom).getTime())) {\n customFrom = input.customFrom\n }\n if (typeof input.customTo === 'string' && !Number.isNaN(new Date(input.customTo).getTime())) {\n customTo = input.customTo\n }\n }\n\n return {\n pageSize,\n datePeriod,\n customFrom,\n customTo,\n }\n}\n"],
|
|
5
|
+
"mappings": "AASO,MAAM,mBAA2C;AAAA,EACtD,UAAU;AAAA,EACV,YAAY;AACd;AAEO,SAAS,8BAA8B,KAAsC;AAClF,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO,EAAE,GAAG,iBAAiB;AAClE,QAAM,QAAQ;AACd,QAAM,iBAAiB,OAAO,MAAM,QAAQ;AAC5C,QAAM,WAAW,OAAO,SAAS,cAAc,IAC3C,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,cAAc,CAAC,CAAC,IACpD,iBAAiB;AAErB,QAAM,aACJ,MAAM,eAAe,aACrB,MAAM,eAAe,YACrB,MAAM,eAAe,aACrB,MAAM,eAAe,WACjB,MAAM,aACN,iBAAiB;AAEvB,MAAI;AACJ,MAAI;AACJ,MAAI,eAAe,UAAU;AAC3B,QAAI,OAAO,MAAM,eAAe,YAAY,CAAC,OAAO,MAAM,IAAI,KAAK,MAAM,UAAU,EAAE,QAAQ,CAAC,GAAG;AAC/F,mBAAa,MAAM;AAAA,IACrB;AACA,QAAI,OAAO,MAAM,aAAa,YAAY,CAAC,OAAO,MAAM,IAAI,KAAK,MAAM,QAAQ,EAAE,QAAQ,CAAC,GAAG;AAC3F,iBAAW,MAAM;AAAA,IACnB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|