Admins create product-based pre-orders, price real line items, reach final approval after cash or checks, and only ship eligible orders. Co-authored-by: Cursor <cursoragent@cursor.com>
633 lines
20 KiB
TypeScript
633 lines
20 KiB
TypeScript
import { apiRequest } from '@/services/http'
|
|
import type {
|
|
AdminNotification,
|
|
AdminUser,
|
|
BankAccount,
|
|
CashPayment,
|
|
Catalog,
|
|
Check,
|
|
CheckDeadline,
|
|
Company,
|
|
Customer,
|
|
InternalMessage,
|
|
Order,
|
|
OrderItem,
|
|
OrderStatus,
|
|
OrderStatusHistoryItem,
|
|
Product,
|
|
RawOrderStatus,
|
|
SettlementMethod,
|
|
Shipment,
|
|
ShipmentStatus,
|
|
UserRole,
|
|
} from '@/types'
|
|
|
|
/** Normalize admin list order status badges */
|
|
export function mapAdminOrder<
|
|
T extends {
|
|
status: string
|
|
adminStatus?: string
|
|
allowedTransitions?: string[]
|
|
settlementMethod?: string | null
|
|
settlementType?: string | null
|
|
meterage?: number
|
|
totalMeterage?: number
|
|
},
|
|
>(
|
|
order: T,
|
|
): T & {
|
|
status: OrderStatus
|
|
rawStatus: RawOrderStatus
|
|
allowedTransitions: RawOrderStatus[]
|
|
settlementMethod: SettlementMethod | null
|
|
meterage: number
|
|
} {
|
|
return {
|
|
...order,
|
|
status: (order.adminStatus ?? order.status) as OrderStatus,
|
|
rawStatus: order.status as RawOrderStatus,
|
|
allowedTransitions: (order.allowedTransitions ?? []) as RawOrderStatus[],
|
|
settlementMethod: (order.settlementMethod ??
|
|
order.settlementType ??
|
|
null) as SettlementMethod | null,
|
|
meterage: order.meterage ?? order.totalMeterage ?? 0,
|
|
}
|
|
}
|
|
|
|
export function slugifyCompanyName(name: string): string {
|
|
const slug = name
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/\s+/g, '-')
|
|
.replace(/[^a-z0-9-]/g, '')
|
|
.replace(/-+/g, '-')
|
|
.replace(/^-|-$/g, '')
|
|
return slug || `c-${Date.now()}`
|
|
}
|
|
|
|
/* ── Companies ─────────────────────────────────────────────── */
|
|
|
|
export type CreateCompanyPayload = {
|
|
name: string
|
|
slug: string
|
|
description?: string
|
|
logoUrl?: string | null
|
|
displayOrder?: number
|
|
isActive?: boolean
|
|
}
|
|
|
|
export type UpdateCompanyPayload = Partial<Omit<CreateCompanyPayload, 'slug'>> & {
|
|
slug?: string
|
|
}
|
|
|
|
export const companiesApi = {
|
|
list: (activeOnly?: boolean) =>
|
|
apiRequest<Company[]>('/companies', { query: { activeOnly: activeOnly ? 'true' : undefined } }),
|
|
get: (id: string) => apiRequest<Company>(`/companies/${id}`),
|
|
create: (body: CreateCompanyPayload) => apiRequest<Company>('/companies', { body }),
|
|
update: (id: string, body: UpdateCompanyPayload) =>
|
|
apiRequest<Company>(`/companies/${id}`, { method: 'PATCH', body }),
|
|
setActive: (id: string, isActive: boolean) =>
|
|
apiRequest<Company>(`/companies/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
|
}
|
|
|
|
/* ── Bank accounts ─────────────────────────────────────────── */
|
|
|
|
export type CreateBankAccountPayload = {
|
|
bankName: string
|
|
accountHolder: string
|
|
accountNumber: string
|
|
cardNumber: string
|
|
iban: string
|
|
displayOrder?: number
|
|
isActive?: boolean
|
|
notes?: string
|
|
}
|
|
|
|
export type UpdateBankAccountPayload = Partial<CreateBankAccountPayload>
|
|
|
|
export const bankAccountsApi = {
|
|
listByCompany: (companyId: string) =>
|
|
apiRequest<BankAccount[]>(`/companies/${companyId}/bank-accounts`),
|
|
create: (companyId: string, body: CreateBankAccountPayload) =>
|
|
apiRequest<BankAccount>(`/companies/${companyId}/bank-accounts`, { body }),
|
|
update: (id: string, body: UpdateBankAccountPayload) =>
|
|
apiRequest<BankAccount>(`/bank-accounts/${id}`, { method: 'PATCH', body }),
|
|
setActive: (id: string, isActive: boolean) =>
|
|
apiRequest<BankAccount>(`/bank-accounts/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
|
}
|
|
|
|
/* ── Catalogs ──────────────────────────────────────────────── */
|
|
|
|
export type CreateCatalogPayload = {
|
|
companyId: string
|
|
name: string
|
|
code: string
|
|
description?: string
|
|
displayOrder?: number
|
|
isActive?: boolean
|
|
}
|
|
|
|
export type UpdateCatalogPayload = Partial<Omit<CreateCatalogPayload, 'companyId'>>
|
|
|
|
export const catalogsApi = {
|
|
list: (params?: { companyId?: string; activeOnly?: boolean }) =>
|
|
apiRequest<Catalog[]>('/catalogs', {
|
|
query: {
|
|
companyId: params?.companyId,
|
|
activeOnly: params?.activeOnly ? 'true' : undefined,
|
|
},
|
|
}),
|
|
create: (body: CreateCatalogPayload) => apiRequest<Catalog>('/catalogs', { body }),
|
|
update: (id: string, body: UpdateCatalogPayload) =>
|
|
apiRequest<Catalog>(`/catalogs/${id}`, { method: 'PATCH', body }),
|
|
setActive: (id: string, isActive: boolean) =>
|
|
apiRequest<Catalog>(`/catalogs/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
|
}
|
|
|
|
/* ── Products ──────────────────────────────────────────────── */
|
|
|
|
export type CreateProductPayload = {
|
|
companyId: string
|
|
catalogId: string
|
|
name: string
|
|
code?: string
|
|
productCode?: string
|
|
color: string
|
|
minMeterage?: number
|
|
orderStep?: number
|
|
orderMultiplier?: number
|
|
price?: number | null
|
|
basePrice?: number | null
|
|
internalStock?: number | null
|
|
stock?: number | null
|
|
isActive?: boolean
|
|
description?: string
|
|
usageCategory?: string
|
|
season?: string
|
|
composition?: string | null
|
|
weaveType?: string | null
|
|
subtitle?: string | null
|
|
rollMeters?: number
|
|
widthCm?: number | null
|
|
weightGrams?: number | null
|
|
isNew?: boolean
|
|
colorsJson?: string[]
|
|
imageUrlsJson?: string[]
|
|
tagsJson?: string[]
|
|
}
|
|
|
|
export type UpdateProductPayload = Partial<Omit<CreateProductPayload, 'companyId'>>
|
|
|
|
export const productsApi = {
|
|
list: (params?: { catalogId?: string; companyId?: string; activeOnly?: boolean }) =>
|
|
apiRequest<Product[]>('/products', {
|
|
query: {
|
|
catalogId: params?.catalogId,
|
|
companyId: params?.companyId,
|
|
activeOnly: params?.activeOnly ? 'true' : undefined,
|
|
},
|
|
}),
|
|
create: (body: CreateProductPayload) => apiRequest<Product>('/products', { body }),
|
|
update: (id: string, body: UpdateProductPayload) =>
|
|
apiRequest<Product>(`/products/${id}`, { method: 'PATCH', body }),
|
|
setActive: (id: string, isActive: boolean) =>
|
|
apiRequest<Product>(`/products/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
|
}
|
|
|
|
/* ── Customers ─────────────────────────────────────────────── */
|
|
|
|
export type CreateCustomerPayload = {
|
|
name?: string
|
|
firstName?: string
|
|
lastName?: string
|
|
phone: string
|
|
nationalId?: string
|
|
customerType?: Customer['customerType']
|
|
companyId?: string | null
|
|
isActive?: boolean
|
|
levelDefId?: string | null
|
|
assignedAgentId?: string | null
|
|
appRole?: 'customer' | 'agent'
|
|
agentCode?: string | null
|
|
brandIds?: string[]
|
|
}
|
|
|
|
export type UpdateCustomerPayload = Partial<CreateCustomerPayload>
|
|
|
|
export const customersApi = {
|
|
list: (q?: string, appRole?: 'customer' | 'agent') =>
|
|
apiRequest<Customer[]>('/admin/customers', { query: { q, appRole } }),
|
|
create: (body: CreateCustomerPayload) => apiRequest<Customer>('/admin/customers', { body }),
|
|
update: (id: string, body: UpdateCustomerPayload) =>
|
|
apiRequest<Customer>(`/admin/customers/${id}`, { method: 'PATCH', body }),
|
|
setActive: (id: string, isActive: boolean) =>
|
|
apiRequest<Customer>(`/admin/customers/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
|
}
|
|
|
|
/* ── Orders ────────────────────────────────────────────────── */
|
|
|
|
export type AdminCreateOrderPayload = {
|
|
companyId?: string
|
|
customerId?: string
|
|
customerName?: string
|
|
customerPhone?: string
|
|
userId?: string
|
|
settlementMethod?: SettlementMethod
|
|
settlementType?: SettlementMethod
|
|
meterage?: number
|
|
notes?: string
|
|
items?: Array<{ productId: string; meterage: number; notes?: string }>
|
|
}
|
|
|
|
export type OrderItemPricingPayload = {
|
|
id?: string
|
|
productId?: string
|
|
requestedMeterage?: number
|
|
confirmedMeterage?: number
|
|
unitPrice?: number
|
|
supplyPrice?: number
|
|
}
|
|
|
|
export type UpdateOrderPricingPayload = {
|
|
items: OrderItemPricingPayload[]
|
|
shippingCost?: number
|
|
note?: string
|
|
deletedItemIds?: string[]
|
|
}
|
|
|
|
export type ApiOrder = Order & {
|
|
adminStatus?: string
|
|
settlementType?: SettlementMethod | null
|
|
totalMeterage?: number
|
|
shippingCost?: number | null
|
|
finalAmount?: number | null
|
|
checkRassDays?: number | null
|
|
checkRassDate?: string
|
|
items?: OrderItem[]
|
|
checks?: Check[]
|
|
statusHistory?: OrderStatusHistoryItem[]
|
|
/** وضعیتهای مجاز بعدی از دید ماشین وضعیت سرور */
|
|
allowedTransitions?: string[]
|
|
cashReceipt?: { id: string } | null
|
|
}
|
|
|
|
export type UpdateOrderStatusPayload = {
|
|
status: string
|
|
note?: string
|
|
reason?: string
|
|
/** گذر خارج از ماشین وضعیت؛ سرور reason را اجباری میکند */
|
|
force?: boolean
|
|
}
|
|
|
|
export const ordersApi = {
|
|
list: (params?: { status?: string; q?: string }) =>
|
|
apiRequest<ApiOrder[]>('/admin/orders', { query: { status: params?.status, q: params?.q } }),
|
|
get: (id: string) => apiRequest<ApiOrder>(`/admin/orders/${id}`),
|
|
create: (body: AdminCreateOrderPayload) => apiRequest<ApiOrder>('/admin/orders', { body }),
|
|
updatePricing: (id: string, body: UpdateOrderPricingPayload) =>
|
|
apiRequest<ApiOrder>(`/admin/orders/${id}/pricing`, { method: 'PATCH', body }),
|
|
updateStatus: (id: string, body: UpdateOrderStatusPayload) =>
|
|
apiRequest<ApiOrder>(`/admin/orders/${id}/status`, { method: 'PATCH', body }),
|
|
}
|
|
|
|
/* ── Checks ────────────────────────────────────────────────── */
|
|
|
|
export const checksApi = {
|
|
list: (status?: Check['status']) =>
|
|
apiRequest<Check[]>('/admin/checks', { query: { status } }),
|
|
listByOrder: (orderId: string) =>
|
|
apiRequest<Check[]>(`/admin/orders/${orderId}/checks`),
|
|
updateStatus: (id: string, body: { status: Check['status']; note?: string }) =>
|
|
apiRequest<Check>(`/admin/checks/${id}/status`, { method: 'PATCH', body }),
|
|
}
|
|
|
|
/* ── Cash payments ─────────────────────────────────────────── */
|
|
|
|
export const cashPaymentsApi = {
|
|
list: (status?: CashPayment['status']) =>
|
|
apiRequest<CashPayment[]>('/admin/cash-payments', { query: { status } }),
|
|
updateStatus: (id: string, body: { status: CashPayment['status']; note?: string }) =>
|
|
apiRequest<CashPayment>(`/admin/cash-payments/${id}/status`, { method: 'PATCH', body }),
|
|
}
|
|
|
|
/* ── Shipments ─────────────────────────────────────────────── */
|
|
|
|
export type CreateShipmentPayload = {
|
|
orderId: string
|
|
carrier: string
|
|
trackingNumber?: string
|
|
meterage?: number
|
|
status?: ShipmentStatus
|
|
}
|
|
|
|
export const shipmentsApi = {
|
|
list: (status?: ShipmentStatus) =>
|
|
apiRequest<Shipment[]>('/admin/shipments', { query: { status } }),
|
|
create: (body: CreateShipmentPayload) => apiRequest<Shipment>('/admin/shipments', { body }),
|
|
update: (id: string, body: Partial<CreateShipmentPayload>) =>
|
|
apiRequest<Shipment>(`/admin/shipments/${id}`, { method: 'PATCH', body }),
|
|
advanceStatus: (id: string, body?: { status?: ShipmentStatus; trackingNumber?: string }) =>
|
|
apiRequest<Shipment>(`/admin/shipments/${id}/status`, { method: 'PATCH', body: body ?? {} }),
|
|
}
|
|
|
|
/* ── Admin users ───────────────────────────────────────────── */
|
|
|
|
export type CreateAdminUserPayload = {
|
|
fullName: string
|
|
phone: string
|
|
email?: string
|
|
password?: string
|
|
role?: UserRole
|
|
isActive?: boolean
|
|
}
|
|
|
|
export type UpdateAdminUserPayload = Partial<CreateAdminUserPayload>
|
|
|
|
export const adminUsersApi = {
|
|
list: () => apiRequest<AdminUser[]>('/admin/users'),
|
|
create: (body: CreateAdminUserPayload) => apiRequest<AdminUser>('/admin/users', { body }),
|
|
update: (id: string, body: UpdateAdminUserPayload) =>
|
|
apiRequest<AdminUser>(`/admin/users/${id}`, { method: 'PATCH', body }),
|
|
setActive: (id: string, isActive: boolean) =>
|
|
apiRequest<AdminUser>(`/admin/users/${id}/active`, { method: 'PATCH', body: { isActive } }),
|
|
}
|
|
|
|
/* ── Dashboard & reports ───────────────────────────────────── */
|
|
|
|
export type DashboardStats = {
|
|
newOrders: number
|
|
pendingReview: number
|
|
pendingCheck: number
|
|
finalApproval: number
|
|
todaySales: number
|
|
todaySalesGrowth: number
|
|
monthSales: number
|
|
}
|
|
|
|
export type DashboardResponse = {
|
|
stats: DashboardStats
|
|
recentOrders: ApiOrder[]
|
|
salesChartData: number[]
|
|
chartAxisLabels: string[]
|
|
checkDeadlines: CheckDeadline[]
|
|
notifications?: AdminNotification[]
|
|
messages?: InternalMessage[]
|
|
}
|
|
|
|
export type ReportsResponse = {
|
|
reportSalesByCompany: {
|
|
companyName: string
|
|
orderCount: number
|
|
totalAmount: number
|
|
meterage: number
|
|
}[]
|
|
reportOrdersByStatus: { status: string; count: number }[]
|
|
reportSalesByMonth?: {
|
|
month: string
|
|
orderCount: number
|
|
totalAmount: number
|
|
meterage: number
|
|
}[]
|
|
reportSalesByProduct?: {
|
|
productId: string
|
|
productName: string
|
|
productCode: string
|
|
orderCount: number
|
|
meterage: number
|
|
totalAmount: number
|
|
}[]
|
|
reportSalesByCustomer?: {
|
|
customerName: string
|
|
orderCount: number
|
|
totalAmount: number
|
|
meterage: number
|
|
}[]
|
|
reportCustomers?: {
|
|
active: number
|
|
inactive: number
|
|
newLast30Days: number
|
|
noPurchase: number
|
|
noPurchase90Days: number
|
|
withAgent: number
|
|
}
|
|
}
|
|
|
|
export const dashboardApi = {
|
|
get: () => apiRequest<DashboardResponse>('/admin/dashboard'),
|
|
reports: () => apiRequest<ReportsResponse>('/admin/reports'),
|
|
}
|
|
|
|
/* ── System settings ──────────────────────────────────────── */
|
|
|
|
export type SystemSettings = {
|
|
id: string
|
|
maxMeteragePerInvoice: number
|
|
cashInvoiceValidityHours: number
|
|
defaultSettlementCheck: boolean
|
|
defaultSettlementCash: boolean
|
|
minOrderRollsPerProduct?: number
|
|
showCatalogPublicPrice?: boolean
|
|
allowAgentPricing?: boolean
|
|
agentMinUnitPrice?: number | null
|
|
smsOrderRegistered: boolean
|
|
smsPricingReady: boolean
|
|
smsPaymentReminder: boolean
|
|
smsCheckReview: boolean
|
|
smsShippingUpdate: boolean
|
|
supportPhone: string | null
|
|
supportEmail: string | null
|
|
categoryImagesJson?: Record<string, string>
|
|
updatedAt: string
|
|
}
|
|
|
|
export type UpdateSettingsPayload = Partial<Omit<SystemSettings, 'id' | 'updatedAt'>>
|
|
|
|
export const settingsApi = {
|
|
get: () => apiRequest<SystemSettings>('/admin/settings'),
|
|
update: (body: UpdateSettingsPayload) =>
|
|
apiRequest<SystemSettings>('/admin/settings', { method: 'PATCH', body }),
|
|
}
|
|
|
|
/* ── Admin notifications ──────────────────────────────────── */
|
|
|
|
export const notificationsApi = {
|
|
markRead: (id: string) =>
|
|
apiRequest<{ ok: boolean }>(`/admin/notifications/${id}/read`, { method: 'PATCH' }),
|
|
markAllRead: () =>
|
|
apiRequest<{ ok: boolean }>('/admin/notifications/read-all', { method: 'PATCH' }),
|
|
broadcast: (body: { title: string; body: string; customerIds?: string[]; tone?: string }) =>
|
|
apiRequest<{ sent: number }>('/admin/notifications/broadcast', { method: 'POST', body }),
|
|
}
|
|
|
|
/* ── Catalog requests (physical) ──────────────────────────── */
|
|
|
|
export type CatalogRequestRow = {
|
|
id: string
|
|
customerId: string
|
|
customerName: string
|
|
customerPhone: string
|
|
status: string
|
|
note?: string
|
|
createdAt: string
|
|
items: {
|
|
id: string
|
|
productId: string
|
|
productName: string
|
|
productCode: string
|
|
quantity: number
|
|
holderType: string
|
|
holderCustomerId?: string | null
|
|
movements?: {
|
|
id: string
|
|
fromHolder: string
|
|
toHolder: string
|
|
note?: string
|
|
movedAt: string
|
|
}[]
|
|
}[]
|
|
}
|
|
|
|
export const catalogRequestsApi = {
|
|
list: (status?: string) =>
|
|
apiRequest<CatalogRequestRow[]>('/admin/catalog-requests', {
|
|
query: { status },
|
|
}),
|
|
updateStatus: (id: string, status: string) =>
|
|
apiRequest<CatalogRequestRow>(`/admin/catalog-requests/${id}/status`, {
|
|
method: 'PATCH',
|
|
body: { status },
|
|
}),
|
|
updateHolder: (
|
|
itemId: string,
|
|
body: { holderType: string; holderCustomerId?: string | null; note?: string },
|
|
) =>
|
|
apiRequest(`/admin/catalog-request-items/${itemId}/holder`, {
|
|
method: 'PATCH',
|
|
body,
|
|
}),
|
|
}
|
|
|
|
/* ── Brands ───────────────────────────────────────────────── */
|
|
|
|
export type Brand = {
|
|
id: string
|
|
name: string
|
|
slug?: string
|
|
description?: string
|
|
isActive: boolean
|
|
displayOrder: number
|
|
customersCount?: number
|
|
}
|
|
|
|
export type AppBanner = {
|
|
id: string
|
|
slot: string
|
|
title: string
|
|
subtitle: string
|
|
ctaLabel: string
|
|
ctaLink: string
|
|
imageUrl: string
|
|
displayOrder: number
|
|
isActive: boolean
|
|
}
|
|
|
|
export type BannerPayload = {
|
|
slot: string
|
|
title: string
|
|
subtitle?: string | null
|
|
ctaLabel?: string | null
|
|
ctaLink?: string | null
|
|
imageUrl?: string | null
|
|
displayOrder?: number
|
|
isActive?: boolean
|
|
}
|
|
|
|
export const bannersApi = {
|
|
list: (slot?: string) =>
|
|
apiRequest<AppBanner[]>('/admin/banners', { query: { slot } }),
|
|
create: (body: BannerPayload) =>
|
|
apiRequest<AppBanner>('/admin/banners', { body }),
|
|
update: (id: string, body: Partial<BannerPayload>) =>
|
|
apiRequest<AppBanner>(`/admin/banners/${id}`, { method: 'PATCH', body }),
|
|
remove: (id: string) =>
|
|
apiRequest<{ ok: boolean }>(`/admin/banners/${id}`, { method: 'DELETE' }),
|
|
}
|
|
|
|
export const brandsApi = {
|
|
list: () => apiRequest<Brand[]>('/admin/brands'),
|
|
create: (body: Partial<Brand> & { name: string }) =>
|
|
apiRequest<Brand>('/admin/brands', { body }),
|
|
update: (id: string, body: Partial<Brand>) =>
|
|
apiRequest<Brand>(`/admin/brands/${id}`, { method: 'PATCH', body }),
|
|
}
|
|
|
|
/* ── Levels ───────────────────────────────────────────────── */
|
|
|
|
export type CustomerLevel = {
|
|
id: string
|
|
code: string
|
|
label: string
|
|
sortOrder: number
|
|
isActive: boolean
|
|
customersCount: number
|
|
benefits: { id?: string; title: string; description?: string }[]
|
|
}
|
|
|
|
export const levelsApi = {
|
|
list: () => apiRequest<CustomerLevel[]>('/admin/levels'),
|
|
create: (body: {
|
|
code: string
|
|
label: string
|
|
sortOrder?: number
|
|
benefits?: { title: string; description?: string }[]
|
|
}) => apiRequest<CustomerLevel>('/admin/levels', { body }),
|
|
update: (id: string, body: Partial<CustomerLevel> & { benefits?: { title: string; description?: string }[] }) =>
|
|
apiRequest<CustomerLevel>(`/admin/levels/${id}`, { method: 'PATCH', body }),
|
|
}
|
|
|
|
/* ── Suggestions ──────────────────────────────────────────── */
|
|
|
|
export type ProductSuggestion = {
|
|
id: string
|
|
customerId: string
|
|
customerName: string
|
|
productId: string
|
|
productName: string
|
|
productCode: string
|
|
note?: string
|
|
isActive: boolean
|
|
createdAt: string
|
|
}
|
|
|
|
export const suggestionsApi = {
|
|
list: (customerId?: string) =>
|
|
apiRequest<ProductSuggestion[]>('/admin/suggestions', { query: { customerId } }),
|
|
create: (body: { customerId: string; productId: string; note?: string }) =>
|
|
apiRequest<ProductSuggestion>('/admin/suggestions', { body }),
|
|
setActive: (id: string, isActive: boolean) =>
|
|
apiRequest<ProductSuggestion>(`/admin/suggestions/${id}/active`, {
|
|
method: 'PATCH',
|
|
body: { isActive },
|
|
}),
|
|
}
|
|
|
|
/* ── Stock alerts ─────────────────────────────────────────── */
|
|
|
|
export type StockAlert = {
|
|
id: string
|
|
customerName: string
|
|
customerPhone: string
|
|
productName: string
|
|
productCode: string
|
|
stock: number | null
|
|
active: boolean
|
|
createdAt: string
|
|
}
|
|
|
|
export const stockAlertsApi = {
|
|
list: () => apiRequest<StockAlert[]>('/admin/stock-alerts'),
|
|
notify: (id: string) =>
|
|
apiRequest<StockAlert>(`/admin/stock-alerts/${id}/notify`, { method: 'PATCH' }),
|
|
}
|