fix: sync notification and shop dashboard state
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
import { Bell, CheckCheck, MessageSquare, ShoppingBag } from "lucide-react"
|
import { Bell, CheckCheck, MessageSquare, ShoppingBag } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
import { mockNotifications } from "@/lib/mock"
|
|
||||||
import type { Notification } from "@/lib/types"
|
import type { Notification } from "@/lib/types"
|
||||||
|
import { useNotificationStore } from "@/store/notifications"
|
||||||
|
|
||||||
const typeIcons: Record<Notification["type"], typeof Bell> = {
|
const typeIcons: Record<Notification["type"], typeof Bell> = {
|
||||||
order: ShoppingBag,
|
order: ShoppingBag,
|
||||||
@@ -46,10 +48,12 @@ function NotificationItem({ notification }: { notification: Notification }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function NotificationsPage() {
|
export default function NotificationsPage() {
|
||||||
const unreadCount = mockNotifications.filter((n) => !n.read).length
|
const notifications = useNotificationStore((state) => state.notifications)
|
||||||
const orderNotifs = mockNotifications.filter((n) => n.type === "order")
|
const markAllAsRead = useNotificationStore((state) => state.markAllAsRead)
|
||||||
const communityNotifs = mockNotifications.filter((n) => n.type === "community")
|
const unreadCount = notifications.filter((notification) => !notification.read).length
|
||||||
const systemNotifs = mockNotifications.filter((n) => n.type === "system")
|
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 (
|
return (
|
||||||
<div className="max-w-2xl space-y-6">
|
<div className="max-w-2xl space-y-6">
|
||||||
@@ -58,7 +62,7 @@ export default function NotificationsPage() {
|
|||||||
<h1 className="text-2xl font-bold">通知中心</h1>
|
<h1 className="text-2xl font-bold">通知中心</h1>
|
||||||
{unreadCount > 0 && <Badge>{unreadCount} 条未读</Badge>}
|
{unreadCount > 0 && <Badge>{unreadCount} 条未读</Badge>}
|
||||||
</div>
|
</div>
|
||||||
<Button variant="outline" size="sm">
|
<Button variant="outline" size="sm" onClick={markAllAsRead}>
|
||||||
<CheckCheck className="mr-1 h-4 w-4" />
|
<CheckCheck className="mr-1 h-4 w-4" />
|
||||||
全部已读
|
全部已读
|
||||||
</Button>
|
</Button>
|
||||||
@@ -73,8 +77,8 @@ export default function NotificationsPage() {
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="all" className="space-y-2 mt-4">
|
<TabsContent value="all" className="space-y-2 mt-4">
|
||||||
{mockNotifications.map((n) => (
|
{notifications.map((notification) => (
|
||||||
<NotificationItem key={n.id} notification={n} />
|
<NotificationItem key={notification.id} notification={notification} />
|
||||||
))}
|
))}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
import { ArrowDownLeft, ArrowUpRight, CreditCard, DollarSign } from "lucide-react"
|
import { ArrowDownLeft, ArrowUpRight, CreditCard, DollarSign } from "lucide-react"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
@@ -9,10 +11,18 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} 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() {
|
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 totalIncome = completedOrders.reduce((acc, order) => acc + order.totalPrice, 0)
|
||||||
|
|
||||||
const currentMonth = new Date().getMonth()
|
const currentMonth = new Date().getMonth()
|
||||||
@@ -20,10 +30,19 @@ export default function ShopIncomePage() {
|
|||||||
.filter((o) => new Date(o.completedAt || "").getMonth() === currentMonth)
|
.filter((o) => new Date(o.completedAt || "").getMonth() === currentMonth)
|
||||||
.reduce((acc, order) => acc + order.totalPrice, 0)
|
.reduce((acc, order) => acc + order.totalPrice, 0)
|
||||||
|
|
||||||
const pendingSettlement = mockOrders
|
const pendingSettlement = shopOrders
|
||||||
.filter((o) => ["in_progress", "pending_close", "pending_review"].includes(o.status))
|
.filter((o) => ["in_progress", "pending_close", "pending_review"].includes(o.status))
|
||||||
.reduce((acc, order) => acc + order.totalPrice, 0)
|
.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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h1 className="text-2xl font-bold">收入统计</h1>
|
<h1 className="text-2xl font-bold">收入统计</h1>
|
||||||
@@ -73,7 +92,7 @@ export default function ShopIncomePage() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{mockTransactions.map((transaction) => (
|
{relatedTransactions.map((transaction) => (
|
||||||
<TableRow key={transaction.id}>
|
<TableRow key={transaction.id}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
import { AlertCircle, CheckCircle, Clock, ListOrdered } from "lucide-react"
|
import { AlertCircle, CheckCircle, Clock, ListOrdered } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
@@ -12,11 +14,19 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
import { statusLabels } from "@/lib/constants"
|
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() {
|
export default function ShopOrdersPage() {
|
||||||
const totalOrders = mockOrders.length
|
const userId = useAuthStore((state) => state.user?.id)
|
||||||
const activeOrders = mockOrders.filter((o) =>
|
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_payment",
|
||||||
"pending_accept",
|
"pending_accept",
|
||||||
@@ -25,8 +35,8 @@ export default function ShopOrdersPage() {
|
|||||||
"pending_review",
|
"pending_review",
|
||||||
].includes(o.status),
|
].includes(o.status),
|
||||||
).length
|
).length
|
||||||
const completedOrders = mockOrders.filter((o) => o.status === "completed").length
|
const completedOrders = shopOrders.filter((o) => o.status === "completed").length
|
||||||
const disputedOrders = mockOrders.filter((o) => o.status === "disputed").length
|
const disputedOrders = shopOrders.filter((o) => o.status === "disputed").length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -91,7 +101,7 @@ export default function ShopOrdersPage() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{mockOrders.map((order) => (
|
{shopOrders.map((order) => (
|
||||||
<TableRow key={order.id}>
|
<TableRow key={order.id}>
|
||||||
<TableCell className="font-medium">{order.service.title}</TableCell>
|
<TableCell className="font-medium">{order.service.title}</TableCell>
|
||||||
<TableCell>{order.consumerName}</TableCell>
|
<TableCell>{order.consumerName}</TableCell>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Badge } from "@/components/ui/badge"
|
|||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
|
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
|
||||||
import { roleLabels } from "@/lib/constants"
|
import { roleLabels } from "@/lib/constants"
|
||||||
import { mockGames, mockPosts } from "@/lib/mock"
|
import { mockGames, mockOrders, mockPlayers, mockPosts } from "@/lib/mock"
|
||||||
|
|
||||||
export default function CommunityPage() {
|
export default function CommunityPage() {
|
||||||
const [sortMode, setSortMode] = useState<"latest" | "hot">("latest")
|
const [sortMode, setSortMode] = useState<"latest" | "hot">("latest")
|
||||||
@@ -74,7 +74,16 @@ export default function CommunityPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<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}`}>
|
<Link key={post.id} href={`/post/${post.id}`}>
|
||||||
<Card className="hover:shadow-md transition-shadow">
|
<Card className="hover:shadow-md transition-shadow">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
@@ -110,15 +119,30 @@ export default function CommunityPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{post.linkedOrderId && (
|
{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" />
|
<ClipboardList className="h-3.5 w-3.5" />
|
||||||
关联订单秀单
|
关联订单秀单
|
||||||
</div>
|
</div>
|
||||||
|
{linkedOrder && (
|
||||||
|
<div className="pl-5">
|
||||||
|
<p>
|
||||||
|
{linkedOrder.service.gameName} · {linkedOrder.service.title}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{linkedOrder.playerName}
|
||||||
|
{linkedPlayer ? ` · ${linkedPlayer.rating}` : ""}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="pt-0 text-sm text-muted-foreground gap-4">
|
<CardFooter className="pt-0 text-sm text-muted-foreground gap-4">
|
||||||
<span className="flex items-center gap-1">
|
<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}
|
{post.likeCount}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
@@ -128,7 +152,9 @@ export default function CommunityPage() {
|
|||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
)
|
||||||
|
})(),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,10 +29,11 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
|
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 type { UserRole } from "@/lib/types"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { useAuthStore } from "@/store/auth"
|
import { useAuthStore } from "@/store/auth"
|
||||||
|
import { useNotificationStore } from "@/store/notifications"
|
||||||
|
|
||||||
const roleLabels: Record<UserRole, string> = {
|
const roleLabels: Record<UserRole, string> = {
|
||||||
consumer: "消费者",
|
consumer: "消费者",
|
||||||
@@ -72,7 +73,9 @@ export function Header() {
|
|||||||
setMobileOpen(false)
|
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>) => {
|
const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|||||||
@@ -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 })),
|
||||||
|
})),
|
||||||
|
}))
|
||||||
Reference in New Issue
Block a user