From 1dfcd3927d77e016f35fefc36274c574f4e4afd6 Mon Sep 17 00:00:00 2001 From: zetaloop Date: Sun, 22 Feb 2026 17:14:52 +0800 Subject: [PATCH] fix(dashboard): scope owner and service views by resolved shop --- .../dashboard/services/new/page.tsx | 33 ++++++++- app/(dashboard)/dashboard/services/page.tsx | 38 +++++++++- .../dashboard/shop/employees/page.tsx | 31 +++++---- .../dashboard/shop/income/page.tsx | 16 ++++- .../dashboard/shop/orders/page.tsx | 23 +++---- app/(dashboard)/dashboard/shop/page.tsx | 41 +++++++++-- app/(dashboard)/dashboard/shop/rules/page.tsx | 20 +++++- .../dashboard/shop/templates/page.tsx | 29 +++++++- app/(order)/orders/page.tsx | 69 +++++++++++-------- lib/domain/order-filters.ts | 33 +++++++++ lib/domain/resolve-current-shop.ts | 6 ++ 11 files changed, 269 insertions(+), 70 deletions(-) create mode 100644 lib/domain/order-filters.ts create mode 100644 lib/domain/resolve-current-shop.ts diff --git a/app/(dashboard)/dashboard/services/new/page.tsx b/app/(dashboard)/dashboard/services/new/page.tsx index 1cf9305..1dde312 100644 --- a/app/(dashboard)/dashboard/services/new/page.tsx +++ b/app/(dashboard)/dashboard/services/new/page.tsx @@ -20,10 +20,13 @@ import { } from "@/components/ui/select" import { Textarea } from "@/components/ui/textarea" import { getGameById, listGames } from "@/lib/api" +import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" import { GameIcon } from "@/lib/game-icons" import type { PlayerService } from "@/lib/types" import { useAuthStore } from "@/store/auth" +import { usePlayerStore } from "@/store/players" import { useServiceStore } from "@/store/services" +import { useShopStore } from "@/store/shops" const serviceSchema = z.object({ gameId: z.string().min(1, "请选择游戏"), @@ -40,10 +43,28 @@ export default function NewServicePage() { const searchParams = useSearchParams() const serviceId = searchParams.get("serviceId") const userId = useAuthStore((state) => state.user?.id) + const currentRole = useAuthStore((state) => state.currentRole) + const shops = useShopStore((state) => state.shops) + const players = usePlayerStore((state) => state.players) const services = useServiceStore((state) => state.services) const createService = useServiceStore((state) => state.createService) const updateService = useServiceStore((state) => state.updateService) - const editingService = services.find((service) => service.id === serviceId) + const ownerShop = resolveOwnerShop(userId, shops) + const scopedPlayerIds = + currentRole === "player" + ? userId + ? [userId] + : [] + : currentRole === "owner" + ? ownerShop + ? players.filter((player) => player.shopId === ownerShop.id).map((player) => player.id) + : [] + : [] + const scopedPlayerIdSet = new Set(scopedPlayerIds) + const editingService = services.find( + (service) => service.id === serviceId && scopedPlayerIdSet.has(service.playerId), + ) + const targetPlayerId = editingService?.playerId ?? scopedPlayerIds[0] const { register, handleSubmit, @@ -78,12 +99,20 @@ export default function NewServicePage() { const selectedUnit = useWatch({ control, name: "unit" }) const games = listGames() + if (serviceId && !editingService) { + return
服务不存在或当前身份不可编辑
+ } + + if (!targetPlayerId) { + return
当前身份下没有可管理的服务范围
+ } + const onSubmit = async (data: z.infer) => { const game = getGameById(data.gameId) if (!game) return const payload: Omit = { - playerId: editingService?.playerId ?? userId ?? "u5", + playerId: targetPlayerId, gameId: game.id, gameName: game.name, title: data.title, diff --git a/app/(dashboard)/dashboard/services/page.tsx b/app/(dashboard)/dashboard/services/page.tsx index dd87fa4..48d158b 100644 --- a/app/(dashboard)/dashboard/services/page.tsx +++ b/app/(dashboard)/dashboard/services/page.tsx @@ -13,11 +13,38 @@ import { TableHeader, TableRow, } from "@/components/ui/table" +import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" +import { useAuthStore } from "@/store/auth" +import { usePlayerStore } from "@/store/players" import { useServiceStore } from "@/store/services" +import { useShopStore } from "@/store/shops" export default function ServicesPage() { + const userId = useAuthStore((state) => state.user?.id) + const currentRole = useAuthStore((state) => state.currentRole) + const shops = useShopStore((state) => state.shops) + const players = usePlayerStore((state) => state.players) const services = useServiceStore((state) => state.services) const deleteService = useServiceStore((state) => state.deleteService) + const ownerShop = resolveOwnerShop(userId, shops) + + const scopedPlayerIds = + currentRole === "player" + ? userId + ? [userId] + : [] + : currentRole === "owner" + ? ownerShop + ? players.filter((player) => player.shopId === ownerShop.id).map((player) => player.id) + : [] + : [] + + const scopedPlayerIdSet = new Set(scopedPlayerIds) + const scopedServices = services.filter((service) => scopedPlayerIdSet.has(service.playerId)) + + if (currentRole !== "player" && currentRole !== "owner") { + return
当前身份不可管理服务
+ } return (
@@ -48,7 +75,7 @@ export default function ServicesPage() { - {services.map((service) => ( + {scopedServices.map((service) => ( {service.title} @@ -72,7 +99,14 @@ export default function ServicesPage() { -
diff --git a/app/(dashboard)/dashboard/shop/employees/page.tsx b/app/(dashboard)/dashboard/shop/employees/page.tsx index b316a23..ba3e690 100644 --- a/app/(dashboard)/dashboard/shop/employees/page.tsx +++ b/app/(dashboard)/dashboard/shop/employees/page.tsx @@ -22,6 +22,8 @@ import { TableHeader, TableRow, } from "@/components/ui/table" +import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" +import { useAuthStore } from "@/store/auth" import { usePlayerStore } from "@/store/players" import { useShopStore } from "@/store/shops" @@ -39,27 +41,32 @@ const statusVariants: Record = { export default function EmployeesPage() { const [search, setSearch] = useState("") + const userId = useAuthStore((state) => state.user?.id) + const shops = useShopStore((state) => state.shops) const players = usePlayerStore((state) => state.players) const assignToShop = usePlayerStore((state) => state.assignToShop) const removeFromShop = usePlayerStore((state) => state.removeFromShop) - const shop = useShopStore((state) => state.shops[0]) + const shop = resolveOwnerShop(userId, shops) const updateShop = useShopStore((state) => state.updateShop) - const shopPlayers = useMemo( - () => - players.filter( - (player) => - player.shopId === shop.id && - player.user.nickname.toLowerCase().includes(search.trim().toLowerCase()), - ), - [players, shop.id, search], - ) + const shopPlayers = useMemo(() => { + if (!shop) return [] + return players.filter( + (player) => + player.shopId === shop.id && + player.user.nickname.toLowerCase().includes(search.trim().toLowerCase()), + ) + }, [players, shop, search]) const inviteCandidate = useMemo( - () => players.find((player) => player.shopId !== shop.id), - [players, shop.id], + () => (shop ? players.find((player) => player.shopId !== shop.id) : undefined), + [players, shop], ) + if (!shop) { + return
当前账号没有可管理的店铺
+ } + return (
diff --git a/app/(dashboard)/dashboard/shop/income/page.tsx b/app/(dashboard)/dashboard/shop/income/page.tsx index 9251089..6712a60 100644 --- a/app/(dashboard)/dashboard/shop/income/page.tsx +++ b/app/(dashboard)/dashboard/shop/income/page.tsx @@ -12,6 +12,8 @@ import { TableRow, } from "@/components/ui/table" import { listTransactions } from "@/lib/api" +import { isActiveOrder, isCompletedOrder } from "@/lib/domain/order-filters" +import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" import { useAuthStore } from "@/store/auth" import { useOrderStore } from "@/store/orders" import { useShopStore } from "@/store/shops" @@ -20,9 +22,14 @@ export default function ShopIncomePage() { const userId = useAuthStore((state) => state.user?.id) const shops = useShopStore((state) => state.shops) const orders = useOrderStore((state) => state.orders) - const shop = shops.find((item) => item.owner.id === userId) ?? shops[0] + const shop = resolveOwnerShop(userId, shops) + + if (!shop) { + return
当前账号没有可管理的店铺
+ } + const shopOrders = orders.filter((order) => order.shopId === shop?.id) - const completedOrders = shopOrders.filter((o) => o.status === "completed") + const completedOrders = shopOrders.filter((o) => isCompletedOrder(o.status)) const totalIncome = completedOrders.reduce((acc, order) => acc + order.totalPrice, 0) const currentMonth = new Date().getMonth() @@ -31,7 +38,10 @@ export default function ShopIncomePage() { .reduce((acc, order) => acc + order.totalPrice, 0) const pendingSettlement = shopOrders - .filter((o) => ["in_progress", "pending_close", "pending_review"].includes(o.status)) + .filter( + (o) => + isActiveOrder(o.status) && o.status !== "pending_payment" && o.status !== "pending_accept", + ) .reduce((acc, order) => acc + order.totalPrice, 0) const shopOrderIds = new Set(shopOrders.map((order) => order.id)) diff --git a/app/(dashboard)/dashboard/shop/orders/page.tsx b/app/(dashboard)/dashboard/shop/orders/page.tsx index f24710e..9ada3f0 100644 --- a/app/(dashboard)/dashboard/shop/orders/page.tsx +++ b/app/(dashboard)/dashboard/shop/orders/page.tsx @@ -14,6 +14,8 @@ import { TableRow, } from "@/components/ui/table" import { statusLabels } from "@/lib/constants" +import { isActiveOrder, isCompletedOrder, isDisputedOrder } from "@/lib/domain/order-filters" +import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" import { useAuthStore } from "@/store/auth" import { useOrderStore } from "@/store/orders" import { useShopStore } from "@/store/shops" @@ -22,21 +24,18 @@ export default function ShopOrdersPage() { const userId = useAuthStore((state) => state.user?.id) const shops = useShopStore((state) => state.shops) const orders = useOrderStore((state) => state.orders) - const shop = shops.find((item) => item.owner.id === userId) ?? shops[0] + const shop = resolveOwnerShop(userId, shops) + + if (!shop) { + return
当前账号没有可管理的店铺
+ } + const shopOrders = orders.filter((order) => order.shopId === shop?.id) const totalOrders = shopOrders.length - const activeOrders = shopOrders.filter((o) => - [ - "pending_payment", - "pending_accept", - "in_progress", - "pending_close", - "pending_review", - ].includes(o.status), - ).length - const completedOrders = shopOrders.filter((o) => o.status === "completed").length - const disputedOrders = shopOrders.filter((o) => o.status === "disputed").length + const activeOrders = shopOrders.filter((o) => isActiveOrder(o.status)).length + const completedOrders = shopOrders.filter((o) => isCompletedOrder(o.status)).length + const disputedOrders = shopOrders.filter((o) => isDisputedOrder(o.status)).length return (
diff --git a/app/(dashboard)/dashboard/shop/page.tsx b/app/(dashboard)/dashboard/shop/page.tsx index 959c303..e5b5159 100644 --- a/app/(dashboard)/dashboard/shop/page.tsx +++ b/app/(dashboard)/dashboard/shop/page.tsx @@ -2,7 +2,7 @@ import { DollarSign, Edit, ExternalLink, ListOrdered, Star, Users } from "lucide-react" import Link from "next/link" -import { useEffect, useState } from "react" +import { useState } from "react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" @@ -10,21 +10,48 @@ import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Separator } from "@/components/ui/separator" import { Textarea } from "@/components/ui/textarea" +import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" +import type { Shop } from "@/lib/types" +import { useAuthStore } from "@/store/auth" import { useShopStore } from "@/store/shops" export default function ShopManagementPage() { - const shop = useShopStore((state) => state.shops[0]) + const userId = useAuthStore((state) => state.user?.id) + const shops = useShopStore((state) => state.shops) + const shop = resolveOwnerShop(userId, shops) const updateShop = useShopStore((state) => state.updateShop) const updateAnnouncement = useShopStore((state) => state.updateAnnouncement) const addAnnouncement = useShopStore((state) => state.addAnnouncement) + + if (!shop) { + return
当前账号没有可管理的店铺
+ } + + return ( + + ) +} + +function ShopManagementContent({ + shop, + updateShop, + updateAnnouncement, + addAnnouncement, +}: { + shop: Shop + updateShop: (shopId: string, patch: Partial>) => void + updateAnnouncement: (shopId: string, index: number, announcement: string) => void + addAnnouncement: (shopId: string, announcement: string) => void +}) { const [name, setName] = useState(shop.name) const [description, setDescription] = useState(shop.description) - useEffect(() => { - setName(shop.name) - setDescription(shop.description) - }, [shop]) - return (
diff --git a/app/(dashboard)/dashboard/shop/rules/page.tsx b/app/(dashboard)/dashboard/shop/rules/page.tsx index 42abfc1..160181d 100644 --- a/app/(dashboard)/dashboard/shop/rules/page.tsx +++ b/app/(dashboard)/dashboard/shop/rules/page.tsx @@ -14,13 +14,31 @@ import { SelectValue, } from "@/components/ui/select" import { Switch } from "@/components/ui/switch" +import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" import type { Shop } from "@/lib/types" +import { useAuthStore } from "@/store/auth" import { useShopStore } from "@/store/shops" export default function ShopRulesPage() { - const shop = useShopStore((state) => state.shops[0]) + const userId = useAuthStore((state) => state.user?.id) + const shops = useShopStore((state) => state.shops) + const shop = resolveOwnerShop(userId, shops) const updateShop = useShopStore((state) => state.updateShop) + if (!shop) { + return
当前账号没有可管理的店铺
+ } + + return +} + +function ShopRulesForm({ + shop, + updateShop, +}: { + shop: Shop + updateShop: (shopId: string, patch: Partial>) => void +}) { const [allowMultiShop, setAllowMultiShop] = useState(shop.allowMultiShop) const [allowIndependentOrders, setAllowIndependentOrders] = useState(shop.allowIndependentOrders) const [dispatchMode, setDispatchMode] = useState(shop.dispatchMode) diff --git a/app/(dashboard)/dashboard/shop/templates/page.tsx b/app/(dashboard)/dashboard/shop/templates/page.tsx index 52bafa0..7e983c6 100644 --- a/app/(dashboard)/dashboard/shop/templates/page.tsx +++ b/app/(dashboard)/dashboard/shop/templates/page.tsx @@ -6,7 +6,9 @@ import { type DragEvent, useEffect, useState } from "react" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Switch } from "@/components/ui/switch" -import type { ShopSection } from "@/lib/types" +import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" +import type { Shop, ShopSection } from "@/lib/types" +import { useAuthStore } from "@/store/auth" import { useShopStore } from "@/store/shops" const sectionLabels: Record = { @@ -28,8 +30,31 @@ const sectionDescriptions: Record = { } export default function ShopTemplatesPage() { - const shop = useShopStore((state) => state.shops[0]) + const userId = useAuthStore((state) => state.user?.id) + const shops = useShopStore((state) => state.shops) + const shop = resolveOwnerShop(userId, shops) const updateTemplateSections = useShopStore((state) => state.updateTemplateSections) + + if (!shop) { + return
当前账号没有可管理的店铺
+ } + + return ( + + ) +} + +function ShopTemplatesEditor({ + shop, + updateTemplateSections, +}: { + shop: Shop + updateTemplateSections: (shopId: string, sections: ShopSection[]) => void +}) { const [sections, setSections] = useState( [...shop.templateConfig.sections].sort((a, b) => a.order - b.order), ) diff --git a/app/(order)/orders/page.tsx b/app/(order)/orders/page.tsx index fffa06e..417f2ad 100644 --- a/app/(order)/orders/page.tsx +++ b/app/(order)/orders/page.tsx @@ -2,17 +2,25 @@ import { Clock, MessageSquare, RefreshCw } from "lucide-react" import Link from "next/link" -import { useEffect, useState } from "react" +import { useState } from "react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { statusLabels } from "@/lib/constants" +import { + isActiveOrder, + isCompletedOrder, + isDisputedOrder, + isPendingDispatch, +} from "@/lib/domain/order-filters" +import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" import type { OrderStatus, UserRole } from "@/lib/types" import { cn } from "@/lib/utils" import { useAuthStore } from "@/store/auth" import { useChatStore } from "@/store/chat" import { useOrderStore } from "@/store/orders" +import { useShopStore } from "@/store/shops" const statusColors: Record = { pending_payment: "bg-yellow-100 text-yellow-800", @@ -51,47 +59,46 @@ const ownerTabs = [ export default function OrderListPage() { const { currentRole, user } = useAuthStore() - const userId = user?.id ?? "u1" + const shops = useShopStore((state) => state.shops) + const ownerShop = resolveOwnerShop(user?.id, shops) - return + return ( + + ) } -function OrderListContent({ currentRole, userId }: { currentRole: UserRole; userId: string }) { +function OrderListContent({ + currentRole, + userId, + ownerShopId, +}: { + currentRole: UserRole + userId?: string + ownerShopId?: string +}) { const [tab, setTab] = useState("all") const orders = useOrderStore((state) => state.orders) const sessions = useChatStore((state) => state.sessions) - const ensureOrderSession = useChatStore((state) => state.ensureOrderSession) - const ownerShopId = "shop1" const tabs = currentRole === "consumer" ? consumerTabs : currentRole === "player" ? playerTabs : ownerTabs - useEffect(() => { - orders.forEach((order) => { - if (order.status === "pending_payment" || order.status === "cancelled") return - ensureOrderSession(order) - }) - }, [orders, ensureOrderSession]) - const roleFiltered = orders.filter((order) => { - if (currentRole === "consumer") return order.consumerId === userId - if (currentRole === "player") return order.playerId === userId - return order.shopId === ownerShopId + if (currentRole === "consumer") return userId ? order.consumerId === userId : false + if (currentRole === "player") return userId ? order.playerId === userId : false + return ownerShopId ? order.shopId === ownerShopId : false }) const filtered = roleFiltered.filter((order) => { - if (tab === "pending") return order.status === "pending_accept" - if (tab === "active") { - return [ - "pending_payment", - "pending_accept", - "in_progress", - "pending_close", - "pending_review", - ].includes(order.status) - } - if (tab === "completed") return order.status === "completed" || order.status === "cancelled" - if (tab === "disputed") return order.status === "disputed" + if (tab === "pending") return isPendingDispatch(order.status) + if (tab === "active") return isActiveOrder(order.status) + if (tab === "completed") return isCompletedOrder(order.status, { includeCancelled: true }) + if (tab === "disputed") return isDisputedOrder(order.status) return true }) @@ -119,7 +126,11 @@ function OrderListContent({ currentRole, userId }: { currentRole: UserRole; user {filtered.length === 0 ? ( -
暂无订单
+ + + 暂无订单 + + ) : ( filtered.map((order) => ( diff --git a/lib/domain/order-filters.ts b/lib/domain/order-filters.ts new file mode 100644 index 0000000..4e40dff --- /dev/null +++ b/lib/domain/order-filters.ts @@ -0,0 +1,33 @@ +import type { OrderStatus } from "@/lib/types" + +const ACTIVE_STATUSES: ReadonlySet = new Set([ + "pending_payment", + "pending_accept", + "in_progress", + "pending_close", + "pending_review", +]) + +const COMPLETED_STATUSES: ReadonlySet = new Set(["completed"]) +const DISPUTED_STATUSES: ReadonlySet = new Set(["disputed"]) +const PENDING_DISPATCH_STATUSES: ReadonlySet = new Set(["pending_accept"]) + +export function isActiveOrder(status: OrderStatus) { + return ACTIVE_STATUSES.has(status) +} + +export function isCompletedOrder(status: OrderStatus, options?: { includeCancelled?: boolean }) { + if (options?.includeCancelled && status === "cancelled") { + return true + } + + return COMPLETED_STATUSES.has(status) +} + +export function isDisputedOrder(status: OrderStatus) { + return DISPUTED_STATUSES.has(status) +} + +export function isPendingDispatch(status: OrderStatus) { + return PENDING_DISPATCH_STATUSES.has(status) +} diff --git a/lib/domain/resolve-current-shop.ts b/lib/domain/resolve-current-shop.ts new file mode 100644 index 0000000..bb75a8d --- /dev/null +++ b/lib/domain/resolve-current-shop.ts @@ -0,0 +1,6 @@ +import type { Shop } from "@/lib/types" + +export function resolveOwnerShop(userId: string | undefined, shops: Shop[]): Shop | null { + if (!userId) return null + return shops.find((shop) => shop.owner.id === userId) ?? null +}