Add special orders admin UI and product specials tools.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
b103e330ef
commit
d63e3a83d5
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { apiUpload } from './api'
|
||||
|
||||
export type MediaKind = 'main' | 'gallery' | 'temp'
|
||||
export type MediaKind = 'main' | 'gallery' | 'temp' | 'specialOrder'
|
||||
|
||||
export type MediaUploadResult = {
|
||||
url: string
|
||||
|
||||
+23
-1
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
CakeSlice,
|
||||
ClipboardPen,
|
||||
FolderTree,
|
||||
MapPin,
|
||||
Package,
|
||||
Percent,
|
||||
PlusCircle,
|
||||
Settings,
|
||||
Sparkles,
|
||||
UserRound,
|
||||
Users,
|
||||
type LucideIcon,
|
||||
@@ -60,6 +62,14 @@ export const productTiles: ProductTile[] = [
|
||||
href: '/products/categories',
|
||||
icon: FolderTree,
|
||||
},
|
||||
{
|
||||
id: 'products-specials',
|
||||
title: 'محصولات ویژه',
|
||||
titleEn: 'Special Products',
|
||||
description: 'مشاهده و مدیریت محصولات ویژه فروشگاه',
|
||||
href: '/products/specials',
|
||||
icon: Sparkles,
|
||||
},
|
||||
]
|
||||
|
||||
export const navSections: NavSection[] = [
|
||||
@@ -96,6 +106,15 @@ export const navSections: NavSection[] = [
|
||||
icon: Package,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
id: 'special-orders',
|
||||
title: 'سفارشات ویژه',
|
||||
titleEn: 'Special Orders',
|
||||
description: 'درخواستهای سفارشی کیک و شیرینی بدون پرداخت',
|
||||
href: '/special-orders',
|
||||
icon: ClipboardPen,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
id: 'discounts',
|
||||
title: 'کدهای تخفیف',
|
||||
@@ -118,7 +137,10 @@ export const navSections: NavSection[] = [
|
||||
|
||||
/** Header links — settings & discounts stay on home tiles only. */
|
||||
export const adminHeaderSections: NavSection[] = navSections.filter(
|
||||
(section) => section.id !== 'settings' && section.id !== 'discounts',
|
||||
(section) =>
|
||||
section.id !== 'settings' &&
|
||||
section.id !== 'discounts' &&
|
||||
section.id !== 'special-orders',
|
||||
)
|
||||
|
||||
export const customerNavSections: NavSection[] = [
|
||||
|
||||
@@ -18,6 +18,7 @@ export type ListProductsParams = {
|
||||
categoryId?: string
|
||||
minPrice?: number
|
||||
maxPrice?: number
|
||||
tag?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}
|
||||
@@ -41,6 +42,8 @@ export type CreateProductPayload = {
|
||||
sellUnit: SellUnit
|
||||
intro?: string
|
||||
description?: string
|
||||
/** null = ready immediately (no prep time) */
|
||||
preparationHours?: number | null
|
||||
tags?: string[]
|
||||
mainImageUrl?: string
|
||||
mainImageKey?: string
|
||||
@@ -54,6 +57,7 @@ function buildQuery(params: ListProductsParams) {
|
||||
const search = new URLSearchParams()
|
||||
if (params.q?.trim()) search.set('q', params.q.trim())
|
||||
if (params.categoryId) search.set('categoryId', params.categoryId)
|
||||
if (params.tag?.trim()) search.set('tag', params.tag.trim())
|
||||
if (params.minPrice !== undefined) search.set('minPrice', String(params.minPrice))
|
||||
if (params.maxPrice !== undefined) search.set('maxPrice', String(params.maxPrice))
|
||||
if (params.page) search.set('page', String(params.page))
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { apiRequest } from './api'
|
||||
|
||||
export type SpecialOrderCategory = 'specialCake' | 'specialPastry'
|
||||
|
||||
export type SpecialOrderImage = {
|
||||
id?: string
|
||||
url: string
|
||||
storageKey: string
|
||||
sortOrder?: number
|
||||
}
|
||||
|
||||
export type SpecialOrderUser = {
|
||||
id: string
|
||||
title: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
cellNumber: string
|
||||
role: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type SpecialOrder = {
|
||||
id: string
|
||||
userId: string
|
||||
category: SpecialOrderCategory
|
||||
name: string
|
||||
description: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
images: SpecialOrderImage[]
|
||||
user: SpecialOrderUser
|
||||
}
|
||||
|
||||
export type SpecialOrderListResponse = {
|
||||
items: SpecialOrder[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export type ListSpecialOrdersParams = {
|
||||
q?: string
|
||||
userId?: string
|
||||
category?: SpecialOrderCategory
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export type SpecialOrderPayload = {
|
||||
userId: string
|
||||
category: SpecialOrderCategory
|
||||
name: string
|
||||
description?: string
|
||||
images?: Array<{ url: string; storageKey: string }>
|
||||
}
|
||||
|
||||
export type UpdateSpecialOrderPayload = Partial<SpecialOrderPayload>
|
||||
|
||||
export const specialOrderCategoryLabel: Record<SpecialOrderCategory, string> = {
|
||||
specialCake: 'کیک ویژه',
|
||||
specialPastry: 'شیرینی ویژه',
|
||||
}
|
||||
|
||||
export const specialOrderCategories: SpecialOrderCategory[] = [
|
||||
'specialCake',
|
||||
'specialPastry',
|
||||
]
|
||||
|
||||
function buildQuery(params: ListSpecialOrdersParams) {
|
||||
const search = new URLSearchParams()
|
||||
if (params.q?.trim()) search.set('q', params.q.trim())
|
||||
if (params.userId) search.set('userId', params.userId)
|
||||
if (params.category) search.set('category', params.category)
|
||||
if (params.page) search.set('page', String(params.page))
|
||||
if (params.pageSize) search.set('pageSize', String(params.pageSize))
|
||||
const qs = search.toString()
|
||||
return qs ? `?${qs}` : ''
|
||||
}
|
||||
|
||||
export function listSpecialOrders(params: ListSpecialOrdersParams = {}) {
|
||||
return apiRequest<SpecialOrderListResponse>(
|
||||
`/special-orders${buildQuery(params)}`,
|
||||
)
|
||||
}
|
||||
|
||||
export function getSpecialOrder(id: string) {
|
||||
return apiRequest<SpecialOrder>(`/special-orders/${id}`)
|
||||
}
|
||||
|
||||
export function createSpecialOrder(payload: SpecialOrderPayload) {
|
||||
return apiRequest<SpecialOrder>('/special-orders', {
|
||||
method: 'POST',
|
||||
body: payload,
|
||||
})
|
||||
}
|
||||
|
||||
export function updateSpecialOrder(
|
||||
id: string,
|
||||
payload: UpdateSpecialOrderPayload,
|
||||
) {
|
||||
return apiRequest<SpecialOrder>(`/special-orders/${id}`, {
|
||||
method: 'PATCH',
|
||||
body: payload,
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteSpecialOrder(id: string) {
|
||||
return apiRequest<{ ok: true }>(`/special-orders/${id}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
+1
-1
@@ -21,7 +21,7 @@ export type ListUsersParams = {
|
||||
}
|
||||
|
||||
export type CreateUserPayload = {
|
||||
title: UserTitle
|
||||
title?: UserTitle
|
||||
firstName: string
|
||||
lastName: string
|
||||
category?: UserCategory | null
|
||||
|
||||
Reference in New Issue
Block a user