feat: paginate customer orders and polish the dashboard hero

Add paged orders API and infinite scroll, short order numbers, and align the guest invite card with the logged-in hero shell.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
s.esmaeili
2026-10-17 13:11:42 +03:30
co-authored by Cursor
parent f550172567
commit afb068c643
89 changed files with 4518 additions and 1325 deletions
+16
View File
@@ -20,3 +20,19 @@ export function jdate(val: string | undefined | null): string {
}
return val as any
}
/** Jalali date + time for status timelines and similar detail surfaces. */
export function jdatetime(val: string | undefined | null): string {
if (val) {
return moment(val).format('jYYYY/jMM/jDD HH:mm')
}
return val as any
}
/** Compact timeline stamp — e.g. «25 مهر، 11:19» (reference card style). */
export function jtimelineWhen(val: string | undefined | null): string {
if (val) {
return moment(val).format('jD jMMMM، HH:mm')
}
return val as any
}
+80 -43
View File
@@ -1,59 +1,96 @@
// Persian labels ported from the legacy Laravel Order::$STS enum (henza_server
// app/Models/Order.php) — the authoritative source, not henza_pwa's own
// Orders.vue filter-tab labels, which used a slightly different, inconsistent
// set (its key=0 was labeled "لغو شده"/cancelled, while the actual backend
// model's sts=0 means "معلق"/suspended — legacy itself was inconsistent here).
// Our OrderStatus enum cleanly separates SUSPENDED and CANCELLED, so mapped
// directly, one label per real status.
// Persian labels for the real OrderStatus enum (apps/api prisma schema).
// One label per status — shared by filter chips, list cards, and detail.
export const ORDER_STATUS_LABELS: Record<string, string> = {
SUSPENDED: 'معلق',
PLACED: 'ثبت سفارش',
ADMIN_APPROVED: 'تایید مدیر سیستم',
SOURCING: 'درحال تامین',
PREPARING: 'درحال آماده سازی',
DELIVERED_TO_AGENT: 'تحویل مسئول فروش',
ADMIN_APPROVED: 'تایید مدیر',
SOURCING: 'در حال تامین',
PREPARING: 'آماده‌سازی',
DELIVERED_TO_AGENT: 'تحویل نماینده',
DELIVERED_TO_CUSTOMER: 'تحویل مشتری',
CANCELLED: 'لغو شده',
};
}
/** Short blurbs under timeline titles (reference card style). */
export const ORDER_STATUS_BLURBS: Record<string, string> = {
SUSPENDED: 'سفارش به‌دلیل سقف اعتبار معلق شد',
PLACED: 'سفارش ثبت و دریافت شد',
ADMIN_APPROVED: 'سفارش توسط مدیر تایید شد',
SOURCING: 'کالا در حال تامین است',
PREPARING: 'سفارش در حال آماده‌سازی است',
DELIVERED_TO_AGENT: 'تحویل به نماینده فروش انجام شد',
DELIVERED_TO_CUSTOMER: 'سفارش به مشتری تحویل شد',
CANCELLED: 'سفارش لغو شده است',
}
/** Line icons for the timeline nodes (vuesax-linear set). */
export const ORDER_STATUS_ICONS: Record<string, string> = {
SUSPENDED: 'vuesax-linear:bag-timer',
PLACED: 'vuesax-linear:bag-tick',
ADMIN_APPROVED: 'vuesax-linear:verify',
SOURCING: 'vuesax-linear:box-search',
PREPARING: 'vuesax-linear:box-1',
DELIVERED_TO_AGENT: 'vuesax-linear:truck-time',
DELIVERED_TO_CUSTOMER: 'vuesax-linear:location-tick',
CANCELLED: 'vuesax-linear:bag-cross',
}
/** Happy-path pipeline shown in the detail stepper (excludes terminal SUSPENDED / CANCELLED). */
export const ORDER_PIPELINE = [
'PLACED',
'ADMIN_APPROVED',
'SOURCING',
'PREPARING',
'DELIVERED_TO_AGENT',
'DELIVERED_TO_CUSTOMER',
] as const
export type OrderPipelineStatus = (typeof ORDER_PIPELINE)[number]
/** Step titles = system status labels, in pipeline order. */
export const ORDER_STEPS = ORDER_PIPELINE.map((status) => ({
status,
title: ORDER_STATUS_LABELS[status]!,
}))
// Ported from legacy order.vue/Orders.vue's setColor() — legacy only ever
// colored sts 1,3,4,5,6 (never sts=0 or sts=2), an existing gap preserved here
// for the directly-analogous statuses (ADMIN_APPROVED, SUSPENDED, CANCELLED
// stay uncolored, same as legacy's untouched sts=0/2).
//
// Backgrounds are the font colour at 12% alpha (the trailing 1F), not the
// opaque pale tints they used to be: a translucent wash reads the same on a
// white card and stays a soft tint on a dark one, where #EFF6FF & co. were
// glaring light patches.
export const ORDER_STATUS_COLORS: Record<string, { font: string; background: string }> = {
SUSPENDED: { font: '#A16207', background: '#A162071F' },
PLACED: { font: '#3B82F6', background: '#3B82F61F' },
ADMIN_APPROVED: { font: '#6366F1', background: '#6366F11F' },
SOURCING: { font: '#EAB308', background: '#EAB3081F' },
PREPARING: { font: '#F97316', background: '#F973161F' },
DELIVERED_TO_AGENT: { font: '#06B6D4', background: '#06B6D41F' },
DELIVERED_TO_CUSTOMER: { font: '#22C55E', background: '#22C55E1F' },
};
CANCELLED: { font: '#DC2626', background: '#DC26261F' },
}
// Ported from legacy Orders.vue's 4-step tracker (`steps`/`setCurrentStep`).
// Legacy's ambiguous sts=0 (suspended vs cancelled) is resolved here since our
// enum keeps them distinct; SUSPENDED is grouped with the first step (not yet
// progressing), matching the spirit of legacy's grouping of early statuses.
export const ORDER_STEPS = ['ثبت سفارش', 'آماده سازی', 'تحویل مشتری', 'لغو شده'];
const FALLBACK_COLOR = { font: '#64748B', background: '#64748B1F' }
const FALLBACK_ICON = 'vuesax-linear:receipt-text'
export function orderStatusColor(status: string) {
return ORDER_STATUS_COLORS[status] ?? FALLBACK_COLOR
}
export function orderStatusLabel(status: string) {
return ORDER_STATUS_LABELS[status] ?? status
}
export function orderStatusBlurb(status: string) {
return ORDER_STATUS_BLURBS[status] ?? ''
}
export function orderStatusIcon(status: string) {
return ORDER_STATUS_ICONS[status] ?? FALLBACK_ICON
}
/**
* Index into ORDER_PIPELINE for the active step.
* CANCELLED → -1 (no pipeline progress).
* SUSPENDED → 0 (stuck at placement / first step).
*/
export function orderStepIndex(status: string): number {
switch (status) {
case 'PLACED':
case 'ADMIN_APPROVED':
case 'SUSPENDED':
return 0;
case 'SOURCING':
case 'PREPARING':
return 1;
case 'DELIVERED_TO_AGENT':
case 'DELIVERED_TO_CUSTOMER':
return 2;
case 'CANCELLED':
return 3;
default:
return 0;
}
if (status === 'CANCELLED') return -1
if (status === 'SUSPENDED') return 0
const i = (ORDER_PIPELINE as readonly string[]).indexOf(status)
return i >= 0 ? i : 0
}