base commit
This commit is contained in:
@@ -3,6 +3,7 @@ import { apiClient } from '@/shared/api/client'
|
||||
export type AdminOrderListItem = {
|
||||
id: string
|
||||
status: string
|
||||
deliveryType: 'delivery' | 'pickup'
|
||||
totalCents: number
|
||||
currency: string
|
||||
createdAt: string
|
||||
@@ -22,9 +23,12 @@ export type AdminOrderDetailResponse = {
|
||||
item: {
|
||||
id: string
|
||||
status: string
|
||||
deliveryType: 'delivery' | 'pickup'
|
||||
itemsSubtotalCents: number
|
||||
deliveryFeeCents: number
|
||||
totalCents: number
|
||||
currency: string
|
||||
addressSnapshotJson: string
|
||||
addressSnapshotJson: string | null
|
||||
comment: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
@@ -45,9 +49,16 @@ export type AdminOrderDetailResponse = {
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAdminOrdersSummary(token: string): Promise<{ attentionCount: number }> {
|
||||
const { data } = await apiClient.get<{ attentionCount: number }>('admin/orders/summary', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export async function fetchAdminOrders(
|
||||
token: string,
|
||||
params?: { status?: string; q?: string; page?: number; pageSize?: number },
|
||||
params?: { status?: string; deliveryType?: 'delivery' | 'pickup'; q?: string; page?: number; pageSize?: number },
|
||||
): Promise<AdminOrdersListResponse> {
|
||||
const { data } = await apiClient.get<AdminOrdersListResponse>('admin/orders', {
|
||||
params,
|
||||
|
||||
@@ -16,9 +16,12 @@ export type OrderDetailResponse = {
|
||||
item: {
|
||||
id: string
|
||||
status: string
|
||||
deliveryType: 'delivery' | 'pickup'
|
||||
itemsSubtotalCents: number
|
||||
deliveryFeeCents: number
|
||||
totalCents: number
|
||||
currency: string
|
||||
addressSnapshotJson: string
|
||||
addressSnapshotJson: string | null
|
||||
comment: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
@@ -38,7 +41,11 @@ export type OrderDetailResponse = {
|
||||
}
|
||||
}
|
||||
|
||||
export async function createOrder(body: { addressId: string; comment?: string | null }): Promise<{ orderId: string }> {
|
||||
export async function createOrder(body: {
|
||||
deliveryType: 'delivery' | 'pickup'
|
||||
addressId?: string | null
|
||||
comment?: string | null
|
||||
}): Promise<{ orderId: string }> {
|
||||
const { data } = await apiClient.post<{ orderId: string }>('me/orders', body)
|
||||
return data
|
||||
}
|
||||
@@ -57,6 +64,24 @@ export async function postOrderMessage(id: string, text: string): Promise<void>
|
||||
await apiClient.post(`me/orders/${id}/messages`, { text })
|
||||
}
|
||||
|
||||
export async function payOrderStub(id: string): Promise<void> {
|
||||
await apiClient.post(`me/orders/${id}/pay`)
|
||||
export async function payOrderStub(id: string): Promise<{ ok: boolean; status: string }> {
|
||||
const { data } = await apiClient.post<{ ok: boolean; status: string }>(`me/orders/${id}/pay`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function confirmOrderReceived(id: string): Promise<{ ok: boolean; status: string }> {
|
||||
const { data } = await apiClient.post<{ ok: boolean; status: string }>(`me/orders/${id}/confirm-received`)
|
||||
return data
|
||||
}
|
||||
|
||||
export type ReviewEligibilityItem = { productId: string; title: string; hasReview: boolean }
|
||||
|
||||
export async function fetchOrderReviewEligibility(orderId: string): Promise<{
|
||||
canReview: boolean
|
||||
items: ReviewEligibilityItem[]
|
||||
}> {
|
||||
const { data } = await apiClient.get<{ canReview: boolean; items: ReviewEligibilityItem[] }>(
|
||||
`me/orders/${orderId}/review-eligibility`,
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export async function fetchPublicProducts(params?: {
|
||||
categorySlug?: string
|
||||
q?: string
|
||||
sort?: 'price_asc' | 'price_desc' | ''
|
||||
availability?: 'all' | 'in_stock' | 'made_to_order'
|
||||
page?: number
|
||||
pageSize?: number
|
||||
priceMinCents?: number
|
||||
@@ -22,6 +23,7 @@ export async function fetchPublicProducts(params?: {
|
||||
categorySlug: params?.categorySlug || undefined,
|
||||
q: params?.q || undefined,
|
||||
sort: params?.sort || undefined,
|
||||
availability: params?.availability || undefined,
|
||||
page: params?.page || undefined,
|
||||
pageSize: params?.pageSize || undefined,
|
||||
priceMin: params?.priceMinCents ?? undefined,
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { apiClient } from '@/shared/api/client'
|
||||
|
||||
export async function postProductReview(
|
||||
productId: string,
|
||||
body: { rating: number; text?: string | null },
|
||||
): Promise<void> {
|
||||
await apiClient.post(`products/${productId}/reviews`, body)
|
||||
}
|
||||
|
||||
export type PublicReviewFeedItem = {
|
||||
id: string
|
||||
rating: number
|
||||
text: string | null
|
||||
createdAt: string
|
||||
authorDisplay: string
|
||||
productId: string
|
||||
productTitle: string
|
||||
}
|
||||
|
||||
export type PublicReviewsLatestResponse = {
|
||||
items: PublicReviewFeedItem[]
|
||||
}
|
||||
|
||||
export async function fetchLatestApprovedReviews(limit = 5): Promise<PublicReviewsLatestResponse> {
|
||||
const { data } = await apiClient.get<PublicReviewsLatestResponse>('reviews/latest', {
|
||||
params: { limit },
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export type PublicProductReviewItem = {
|
||||
id: string
|
||||
rating: number
|
||||
text: string | null
|
||||
createdAt: string
|
||||
authorDisplay: string
|
||||
}
|
||||
|
||||
export type PublicProductReviewsResponse = {
|
||||
items: PublicProductReviewItem[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export async function fetchPublicProductReviews(
|
||||
productId: string,
|
||||
params?: { page?: number; pageSize?: number },
|
||||
): Promise<PublicProductReviewsResponse> {
|
||||
const { data } = await apiClient.get<PublicProductReviewsResponse>(`products/${productId}/reviews`, {
|
||||
params: { page: params?.page, pageSize: params?.pageSize },
|
||||
})
|
||||
return data
|
||||
}
|
||||
@@ -5,6 +5,12 @@ export type Category = {
|
||||
sort: number
|
||||
}
|
||||
|
||||
export type ProductReviewsSummary = {
|
||||
approvedReviewCount: number
|
||||
avgRating: number | null
|
||||
latestApprovedText: string | null
|
||||
}
|
||||
|
||||
export type Product = {
|
||||
id: string
|
||||
title: string
|
||||
@@ -24,4 +30,6 @@ export type Product = {
|
||||
updatedAt: string
|
||||
category?: Category
|
||||
images?: { id: string; url: string; sort: number }[]
|
||||
/** Для опубликованных товаров с публичного API. */
|
||||
reviewsSummary?: ProductReviewsSummary | null
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import CardContent from '@mui/material/CardContent'
|
||||
import CardMedia from '@mui/material/CardMedia'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Link from '@mui/material/Link'
|
||||
import Rating from '@mui/material/Rating'
|
||||
import Stack from '@mui/material/Stack'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { Link as RouterLink } from 'react-router-dom'
|
||||
@@ -13,6 +14,7 @@ import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
import 'swiper/css'
|
||||
import type { Product } from '@/entities/product/model/types'
|
||||
import { formatPriceRub } from '@/shared/lib/format-price'
|
||||
import { reviewsCountRu } from '@/shared/lib/reviews-count-ru'
|
||||
import type { Swiper as SwiperType } from 'swiper/types'
|
||||
|
||||
type Props = { product: Product; mediaHeight?: number; actions?: ReactNode }
|
||||
@@ -117,6 +119,7 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
<CardContent sx={{ flexGrow: 1 }}>
|
||||
<Stack spacing={1}>
|
||||
{product.category && <Chip label={product.category.name} size="small" />}
|
||||
{product.inStock && product.quantity === 0 && <Chip label="Нет в наличии" size="small" color="default" />}
|
||||
{!product.inStock && (
|
||||
<Chip
|
||||
label={`Под заказ · ${product.leadTimeDays ?? '—'} дн.`}
|
||||
@@ -157,6 +160,38 @@ export function ProductCard({ product, mediaHeight = 200, actions }: Props) {
|
||||
<Typography variant="h6" color="primary">
|
||||
{formatPriceRub(product.priceCents)}
|
||||
</Typography>
|
||||
{product.reviewsSummary && product.reviewsSummary.approvedReviewCount > 0 && (
|
||||
<Stack spacing={0.5}>
|
||||
<Stack direction="row" spacing={0.75} sx={{ alignItems: 'center' }}>
|
||||
<Rating
|
||||
size="small"
|
||||
value={product.reviewsSummary.avgRating ?? 0}
|
||||
precision={0.25}
|
||||
readOnly
|
||||
sx={{ color: 'warning.main', fontSize: 18 }}
|
||||
/>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{reviewsCountRu(product.reviewsSummary.approvedReviewCount)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
{product.reviewsSummary.latestApprovedText ? (
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
fontStyle: 'italic',
|
||||
overflow: 'hidden',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
}}
|
||||
title={product.reviewsSummary.latestApprovedText}
|
||||
>
|
||||
«{product.reviewsSummary.latestApprovedText}»
|
||||
</Typography>
|
||||
) : null}
|
||||
</Stack>
|
||||
)}
|
||||
{actions}
|
||||
</Stack>
|
||||
</CardContent>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { apiClient } from '@/shared/api/client'
|
||||
|
||||
export async function fetchUnreadMessageCount(): Promise<{ count: number }> {
|
||||
const { data } = await apiClient.get<{ count: number }>('me/messages/unread-count')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function markOrderMessagesRead(orderId: string): Promise<void> {
|
||||
await apiClient.post(`me/orders/${orderId}/messages/read`)
|
||||
}
|
||||
|
||||
export type ConversationSummary = {
|
||||
orderId: string
|
||||
status: string
|
||||
deliveryType: 'delivery' | 'pickup'
|
||||
lastMessageAt: string
|
||||
preview: string
|
||||
unreadCount: number
|
||||
}
|
||||
|
||||
export async function fetchMyConversations(): Promise<{ items: ConversationSummary[] }> {
|
||||
const { data } = await apiClient.get<{ items: ConversationSummary[] }>('me/conversations')
|
||||
return data
|
||||
}
|
||||
Reference in New Issue
Block a user