fix: sync notification and shop dashboard state

This commit is contained in:
zetaloop
2026-02-22 06:43:24 +08:00
parent 02269dd9c3
commit 5f25043923
6 changed files with 152 additions and 74 deletions
+12 -8
View File
@@ -1,11 +1,13 @@
"use client"
import { Bell, CheckCheck, MessageSquare, ShoppingBag } from "lucide-react"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { mockNotifications } from "@/lib/mock"
import type { Notification } from "@/lib/types"
import { useNotificationStore } from "@/store/notifications"
const typeIcons: Record<Notification["type"], typeof Bell> = {
order: ShoppingBag,
@@ -46,10 +48,12 @@ function NotificationItem({ notification }: { notification: Notification }) {
}
export default function NotificationsPage() {
const unreadCount = mockNotifications.filter((n) => !n.read).length
const orderNotifs = mockNotifications.filter((n) => n.type === "order")
const communityNotifs = mockNotifications.filter((n) => n.type === "community")
const systemNotifs = mockNotifications.filter((n) => n.type === "system")
const notifications = useNotificationStore((state) => state.notifications)
const markAllAsRead = useNotificationStore((state) => state.markAllAsRead)
const unreadCount = notifications.filter((notification) => !notification.read).length
const orderNotifs = notifications.filter((notification) => notification.type === "order")
const communityNotifs = notifications.filter((notification) => notification.type === "community")
const systemNotifs = notifications.filter((notification) => notification.type === "system")
return (
<div className="max-w-2xl space-y-6">
@@ -58,7 +62,7 @@ export default function NotificationsPage() {
<h1 className="text-2xl font-bold"></h1>
{unreadCount > 0 && <Badge>{unreadCount} </Badge>}
</div>
<Button variant="outline" size="sm">
<Button variant="outline" size="sm" onClick={markAllAsRead}>
<CheckCheck className="mr-1 h-4 w-4" />
</Button>
@@ -73,8 +77,8 @@ export default function NotificationsPage() {
</TabsList>
<TabsContent value="all" className="space-y-2 mt-4">
{mockNotifications.map((n) => (
<NotificationItem key={n.id} notification={n} />
{notifications.map((notification) => (
<NotificationItem key={notification.id} notification={notification} />
))}
</TabsContent>
+23 -4
View File
@@ -1,3 +1,5 @@
"use client"
import { ArrowDownLeft, ArrowUpRight, CreditCard, DollarSign } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
@@ -9,10 +11,18 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table"
import { mockOrders, mockTransactions } from "@/lib/mock"
import { mockTransactions } from "@/lib/mock"
import { useAuthStore } from "@/store/auth"
import { useOrderStore } from "@/store/orders"
import { useShopStore } from "@/store/shops"
export default function ShopIncomePage() {
const completedOrders = mockOrders.filter((o) => o.status === "completed")
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 shopOrders = orders.filter((order) => order.shopId === shop?.id)
const completedOrders = shopOrders.filter((o) => o.status === "completed")
const totalIncome = completedOrders.reduce((acc, order) => acc + order.totalPrice, 0)
const currentMonth = new Date().getMonth()
@@ -20,10 +30,19 @@ export default function ShopIncomePage() {
.filter((o) => new Date(o.completedAt || "").getMonth() === currentMonth)
.reduce((acc, order) => acc + order.totalPrice, 0)
const pendingSettlement = mockOrders
const pendingSettlement = shopOrders
.filter((o) => ["in_progress", "pending_close", "pending_review"].includes(o.status))
.reduce((acc, order) => acc + order.totalPrice, 0)
const shopOrderIds = new Set(shopOrders.map((order) => order.id))
const relatedTransactions = mockTransactions.filter((transaction) => {
if (transaction.type === "withdrawal") return true
if (transaction.type !== "income") return false
const match = transaction.description.match(/ord\d+/)
if (!match) return false
return shopOrderIds.has(match[0])
})
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold"></h1>
@@ -73,7 +92,7 @@ export default function ShopIncomePage() {
</TableRow>
</TableHeader>
<TableBody>
{mockTransactions.map((transaction) => (
{relatedTransactions.map((transaction) => (
<TableRow key={transaction.id}>
<TableCell>
<div className="flex items-center gap-2">
+16 -6
View File
@@ -1,3 +1,5 @@
"use client"
import { AlertCircle, CheckCircle, Clock, ListOrdered } from "lucide-react"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
@@ -12,11 +14,19 @@ import {
TableRow,
} from "@/components/ui/table"
import { statusLabels } from "@/lib/constants"
import { mockOrders } from "@/lib/mock"
import { useAuthStore } from "@/store/auth"
import { useOrderStore } from "@/store/orders"
import { useShopStore } from "@/store/shops"
export default function ShopOrdersPage() {
const totalOrders = mockOrders.length
const activeOrders = mockOrders.filter((o) =>
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 shopOrders = orders.filter((order) => order.shopId === shop?.id)
const totalOrders = shopOrders.length
const activeOrders = shopOrders.filter((o) =>
[
"pending_payment",
"pending_accept",
@@ -25,8 +35,8 @@ export default function ShopOrdersPage() {
"pending_review",
].includes(o.status),
).length
const completedOrders = mockOrders.filter((o) => o.status === "completed").length
const disputedOrders = mockOrders.filter((o) => o.status === "disputed").length
const completedOrders = shopOrders.filter((o) => o.status === "completed").length
const disputedOrders = shopOrders.filter((o) => o.status === "disputed").length
return (
<div className="space-y-6">
@@ -91,7 +101,7 @@ export default function ShopOrdersPage() {
</TableRow>
</TableHeader>
<TableBody>
{mockOrders.map((order) => (
{shopOrders.map((order) => (
<TableRow key={order.id}>
<TableCell className="font-medium">{order.service.title}</TableCell>
<TableCell>{order.consumerName}</TableCell>
+31 -5
View File
@@ -8,7 +8,7 @@ import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
import { roleLabels } from "@/lib/constants"
import { mockGames, mockPosts } from "@/lib/mock"
import { mockGames, mockOrders, mockPlayers, mockPosts } from "@/lib/mock"
export default function CommunityPage() {
const [sortMode, setSortMode] = useState<"latest" | "hot">("latest")
@@ -74,7 +74,16 @@ export default function CommunityPage() {
</div>
<div className="space-y-4">
{filteredPosts.map((post) => (
{filteredPosts.map((post) =>
(() => {
const linkedOrder = post.linkedOrderId
? mockOrders.find((order) => order.id === post.linkedOrderId)
: null
const linkedPlayer = linkedOrder
? mockPlayers.find((player) => player.id === linkedOrder.playerId)
: null
return (
<Link key={post.id} href={`/post/${post.id}`}>
<Card className="hover:shadow-md transition-shadow">
<CardHeader className="pb-3">
@@ -110,15 +119,30 @@ export default function CommunityPage() {
</div>
)}
{post.linkedOrderId && (
<div className="mt-2 rounded border bg-muted/30 px-3 py-2 text-xs text-muted-foreground flex items-center gap-1.5">
<div className="mt-2 rounded border bg-muted/30 px-3 py-2 text-xs text-muted-foreground space-y-1.5">
<div className="flex items-center gap-1.5">
<ClipboardList className="h-3.5 w-3.5" />
</div>
{linkedOrder && (
<div className="pl-5">
<p>
{linkedOrder.service.gameName} · {linkedOrder.service.title}
</p>
<p>
{linkedOrder.playerName}
{linkedPlayer ? ` · ${linkedPlayer.rating}` : ""}
</p>
</div>
)}
</div>
)}
</CardContent>
<CardFooter className="pt-0 text-sm text-muted-foreground gap-4">
<span className="flex items-center gap-1">
<Heart className={`h-4 w-4 ${post.liked ? "fill-red-500 text-red-500" : ""}`} />
<Heart
className={`h-4 w-4 ${post.liked ? "fill-red-500 text-red-500" : ""}`}
/>
{post.likeCount}
</span>
<span className="flex items-center gap-1">
@@ -128,7 +152,9 @@ export default function CommunityPage() {
</CardFooter>
</Card>
</Link>
))}
)
})(),
)}
</div>
</div>
)
+5 -2
View File
@@ -29,10 +29,11 @@ import {
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
import { currentUser, mockNotifications, mockShops } from "@/lib/mock"
import { currentUser, mockShops } from "@/lib/mock"
import type { UserRole } from "@/lib/types"
import { cn } from "@/lib/utils"
import { useAuthStore } from "@/store/auth"
import { useNotificationStore } from "@/store/notifications"
const roleLabels: Record<UserRole, string> = {
consumer: "消费者",
@@ -72,7 +73,9 @@ export function Header() {
setMobileOpen(false)
}
const unreadCount = mockNotifications.filter((n) => !n.read).length
const unreadCount = useNotificationStore(
(state) => state.notifications.filter((notification) => !notification.read).length,
)
const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
+16
View File
@@ -0,0 +1,16 @@
import { create } from "zustand"
import { mockNotifications } from "@/lib/mock"
import type { Notification } from "@/lib/types"
interface NotificationState {
notifications: Notification[]
markAllAsRead: () => void
}
export const useNotificationStore = create<NotificationState>((set) => ({
notifications: mockNotifications,
markAllAsRead: () =>
set((state) => ({
notifications: state.notifications.map((notification) => ({ ...notification, read: true })),
})),
}))