feat(ui): refine dashboard configuration pages
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { EmptyState } from "@/components/ui/empty-state"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import {
|
import {
|
||||||
@@ -27,7 +28,7 @@ import { notifyInfo, notifySuccess } from "@/lib/toast"
|
|||||||
import type { Game, PlayerService } from "@/lib/types"
|
import type { Game, PlayerService } from "@/lib/types"
|
||||||
import { useAuthStore } from "@/store/auth"
|
import { useAuthStore } from "@/store/auth"
|
||||||
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"
|
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"
|
||||||
import { ArrowLeft } from "lucide-react"
|
import { AlertCircle, ArrowLeft } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useRouter, useSearchParams } from "next/navigation"
|
import { useRouter, useSearchParams } from "next/navigation"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
@@ -159,19 +160,35 @@ export default function NewServicePage() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (loadingService || (currentRole === "owner" && shopLoading)) {
|
if (loadingService || (currentRole === "owner" && shopLoading)) {
|
||||||
return <div className="text-sm text-muted-foreground">加载中...</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-2xl px-4 py-8">
|
||||||
|
<EmptyState title="服务信息加载中" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentRole === "owner" && shopError) {
|
if (currentRole === "owner" && shopError) {
|
||||||
return <div className="text-sm text-muted-foreground">{shopError}</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-2xl px-4 py-8">
|
||||||
|
<EmptyState title="店铺信息加载失败" description={shopError} icon={AlertCircle} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceId && (!editingService || !scopedPlayerIdSet.has(editingService.playerId))) {
|
if (serviceId && (!editingService || !scopedPlayerIdSet.has(editingService.playerId))) {
|
||||||
return <div className="text-sm text-muted-foreground">服务不存在或当前身份不可编辑</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-2xl px-4 py-8">
|
||||||
|
<EmptyState title="服务不可编辑" description="服务不存在或当前身份不可编辑。" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetPlayerId) {
|
if (!targetPlayerId) {
|
||||||
return <div className="text-sm text-muted-foreground">当前身份下没有可管理的服务范围</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-2xl px-4 py-8">
|
||||||
|
<EmptyState title="当前身份下没有可管理的服务范围" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = async (data: z.infer<typeof serviceSchema>) => {
|
const onSubmit = async (data: z.infer<typeof serviceSchema>) => {
|
||||||
@@ -214,8 +231,8 @@ export default function NewServicePage() {
|
|||||||
返回服务列表
|
返回服务列表
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Card className="hover:shadow-card-hover">
|
<Card className="border-border/80 shadow-sm">
|
||||||
<CardHeader>
|
<CardHeader className="border-b border-border/60">
|
||||||
<CardTitle>发布服务</CardTitle>
|
<CardTitle>发布服务</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@@ -303,7 +320,7 @@ export default function NewServicePage() {
|
|||||||
<Button type="submit" className="flex-1" disabled={isSubmitting}>
|
<Button type="submit" className="flex-1" disabled={isSubmitting}>
|
||||||
{isSubmitting ? "发布中..." : "发布服务"}
|
{isSubmitting ? "发布中..." : "发布服务"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="button" variant="outline" asChild>
|
<Button type="button" variant="outline" className="border-border/60" asChild>
|
||||||
<Link href="/dashboard/services">取消</Link>
|
<Link href="/dashboard/services">取消</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
import { EmptyState } from "@/components/ui/empty-state"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@@ -30,7 +31,7 @@ import { toApiError } from "@/lib/errors"
|
|||||||
import { useMyShop } from "@/lib/hooks/use-my-shop"
|
import { useMyShop } from "@/lib/hooks/use-my-shop"
|
||||||
import { notifyInfo, notifySuccess } from "@/lib/toast"
|
import { notifyInfo, notifySuccess } from "@/lib/toast"
|
||||||
import type { Player } from "@/lib/types"
|
import type { Player } from "@/lib/types"
|
||||||
import { MoreHorizontal, Star, UserPlus } from "lucide-react"
|
import { AlertCircle, MoreHorizontal, Star, UserPlus } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
|
|
||||||
@@ -131,15 +132,27 @@ export default function EmployeesPage() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div className="text-sm text-muted-foreground">加载中...</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-6xl px-4 py-8">
|
||||||
|
<EmptyState title="员工信息加载中" icon={UserPlus} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div className="text-sm text-muted-foreground">{error}</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-6xl px-4 py-8">
|
||||||
|
<EmptyState title="员工信息加载失败" description={error} icon={AlertCircle} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shop) {
|
if (!shop) {
|
||||||
return <div className="text-sm text-muted-foreground">当前账号没有可管理的店铺</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-6xl px-4 py-8">
|
||||||
|
<EmptyState title="当前账号没有可管理的店铺" icon={UserPlus} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleInvite = async () => {
|
const handleInvite = async () => {
|
||||||
@@ -185,7 +198,7 @@ export default function EmployeesPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="hover:shadow-card-hover">
|
<Card className="border-border/80 shadow-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-base">签约打手 ({filteredPlayers.length})</CardTitle>
|
<CardTitle className="text-base">签约打手 ({filteredPlayers.length})</CardTitle>
|
||||||
@@ -212,14 +225,19 @@ export default function EmployeesPage() {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{dataLoading ? (
|
{dataLoading ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={6} className="text-center text-sm text-muted-foreground">
|
<TableCell colSpan={6} className="py-6">
|
||||||
加载中...
|
<EmptyState title="员工加载中" icon={UserPlus} className="min-h-[180px]" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
) : filteredPlayers.length === 0 ? (
|
) : filteredPlayers.length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={6} className="text-center text-sm text-muted-foreground">
|
<TableCell colSpan={6} className="py-6">
|
||||||
暂无签约打手
|
<EmptyState
|
||||||
|
title="暂无签约打手"
|
||||||
|
description="邀请打手加入后会出现在这里。"
|
||||||
|
icon={UserPlus}
|
||||||
|
className="min-h-[180px] border-dashed"
|
||||||
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
) : (
|
) : (
|
||||||
@@ -248,7 +266,7 @@ export default function EmployeesPage() {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Star className="h-3.5 w-3.5 fill-yellow-400 text-yellow-400" />
|
<Star className="h-3.5 w-3.5 fill-warning text-warning" />
|
||||||
{player.rating}
|
{player.rating}
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { EmptyState } from "@/components/ui/empty-state"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import {
|
import {
|
||||||
@@ -17,22 +18,34 @@ import { toApiError } from "@/lib/errors"
|
|||||||
import { useMyShop } from "@/lib/hooks/use-my-shop"
|
import { useMyShop } from "@/lib/hooks/use-my-shop"
|
||||||
import { notifyInfo, notifySuccess } from "@/lib/toast"
|
import { notifyInfo, notifySuccess } from "@/lib/toast"
|
||||||
import type { Shop } from "@/lib/types"
|
import type { Shop } from "@/lib/types"
|
||||||
import { Save } from "lucide-react"
|
import { AlertCircle, Save } from "lucide-react"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
|
||||||
export default function ShopRulesPage() {
|
export default function ShopRulesPage() {
|
||||||
const { shop, setShop, loading, error } = useMyShop()
|
const { shop, setShop, loading, error } = useMyShop()
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div className="text-sm text-muted-foreground">加载中...</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-4xl px-4 py-8">
|
||||||
|
<EmptyState title="规则加载中" icon={Save} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div className="text-sm text-muted-foreground">{error}</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-4xl px-4 py-8">
|
||||||
|
<EmptyState title="规则加载失败" description={error} icon={AlertCircle} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shop) {
|
if (!shop) {
|
||||||
return <div className="text-sm text-muted-foreground">当前账号没有可管理的店铺</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-4xl px-4 py-8">
|
||||||
|
<EmptyState title="当前账号没有可管理的店铺" icon={Save} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ShopRulesForm key={shop.id} shop={shop} setShop={setShop} />
|
return <ShopRulesForm key={shop.id} shop={shop} setShop={setShop} />
|
||||||
@@ -83,7 +96,7 @@ function ShopRulesForm({ shop, setShop }: { shop: Shop; setShop: (shop: Shop | n
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-6">
|
<div className="grid gap-6">
|
||||||
<Card className="hover:shadow-card-hover">
|
<Card className="border-border/80 shadow-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">员工权限</CardTitle>
|
<CardTitle className="text-base">员工权限</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -117,7 +130,7 @@ function ShopRulesForm({ shop, setShop }: { shop: Shop; setShop: (shop: Shop | n
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="hover:shadow-card-hover">
|
<Card className="border-border/80 shadow-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">派单设置</CardTitle>
|
<CardTitle className="text-base">派单设置</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -143,7 +156,7 @@ function ShopRulesForm({ shop, setShop }: { shop: Shop; setShop: (shop: Shop | n
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="hover:shadow-card-hover">
|
<Card className="border-border/80 shadow-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">抽成设置</CardTitle>
|
<CardTitle className="text-base">抽成设置</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { EmptyState } from "@/components/ui/empty-state"
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
import { updateShopTemplate } from "@/lib/api"
|
import { updateShopTemplate } from "@/lib/api"
|
||||||
import { getShopSections } from "@/lib/domain/shop-template"
|
import { getShopSections } from "@/lib/domain/shop-template"
|
||||||
@@ -9,7 +10,7 @@ import { toApiError } from "@/lib/errors"
|
|||||||
import { useMyShop } from "@/lib/hooks/use-my-shop"
|
import { useMyShop } from "@/lib/hooks/use-my-shop"
|
||||||
import { notifyInfo } from "@/lib/toast"
|
import { notifyInfo } from "@/lib/toast"
|
||||||
import type { Shop, ShopSection } from "@/lib/types"
|
import type { Shop, ShopSection } from "@/lib/types"
|
||||||
import { Eye, EyeOff, GripVertical } from "lucide-react"
|
import { AlertCircle, Eye, EyeOff, GripVertical } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { type DragEvent, useEffect, useState } from "react"
|
import { type DragEvent, useEffect, useState } from "react"
|
||||||
|
|
||||||
@@ -35,15 +36,27 @@ export default function ShopTemplatesPage() {
|
|||||||
const { shop, setShop, loading, error } = useMyShop()
|
const { shop, setShop, loading, error } = useMyShop()
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div className="text-sm text-muted-foreground">加载中...</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-4xl px-4 py-8">
|
||||||
|
<EmptyState title="模板加载中" icon={GripVertical} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div className="text-sm text-muted-foreground">{error}</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-4xl px-4 py-8">
|
||||||
|
<EmptyState title="模板加载失败" description={error} icon={AlertCircle} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shop) {
|
if (!shop) {
|
||||||
return <div className="text-sm text-muted-foreground">当前账号没有可管理的店铺</div>
|
return (
|
||||||
|
<div className="container mx-auto max-w-4xl px-4 py-8">
|
||||||
|
<EmptyState title="当前账号没有可管理的店铺" icon={GripVertical} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ShopTemplatesEditor key={shop.id} shop={shop} setShop={setShop} />
|
return <ShopTemplatesEditor key={shop.id} shop={shop} setShop={setShop} />
|
||||||
@@ -136,7 +149,7 @@ function ShopTemplatesEditor({
|
|||||||
return (
|
return (
|
||||||
<div className="container mx-auto max-w-4xl px-4 py-8 space-y-8">
|
<div className="container mx-auto max-w-4xl px-4 py-8 space-y-8">
|
||||||
{showSavedToast ? (
|
{showSavedToast ? (
|
||||||
<div className="fixed right-6 top-6 z-50 rounded-md border bg-background px-4 py-2 text-sm shadow-md">
|
<div className="fixed right-6 top-6 z-50 rounded-md border border-border/80 bg-background px-4 py-2 text-sm shadow-sm">
|
||||||
模板已保存
|
模板已保存
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -146,7 +159,7 @@ function ShopTemplatesEditor({
|
|||||||
<p className="text-sm text-muted-foreground mt-1">自定义店铺主页的展示内容和顺序</p>
|
<p className="text-sm text-muted-foreground mt-1">自定义店铺主页的展示内容和顺序</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="outline" asChild>
|
<Button variant="outline" className="border-border/60" asChild>
|
||||||
<Link href={`/shop/${shop.id}`}>预览</Link>
|
<Link href={`/shop/${shop.id}`}>预览</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -160,7 +173,7 @@ function ShopTemplatesEditor({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="hover:shadow-card-hover">
|
<Card className="border-border/80 shadow-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">页面组件</CardTitle>
|
<CardTitle className="text-base">页面组件</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -168,7 +181,7 @@ function ShopTemplatesEditor({
|
|||||||
{sections.map((section, index) => (
|
{sections.map((section, index) => (
|
||||||
<div
|
<div
|
||||||
key={section.type}
|
key={section.type}
|
||||||
className={`flex items-center gap-3 rounded-lg border p-3 transition-colors ${dragIndex === index ? "opacity-50" : "opacity-100"} ${dropIndex === index ? "border-t-2 border-b-2 border-t-primary border-b-primary" : ""}`}
|
className={`flex items-center gap-3 rounded-lg border border-border/60 p-3 transition-colors ${dragIndex === index ? "opacity-50" : "opacity-100"} ${dropIndex === index ? "border-b-2 border-t-2 border-b-primary border-t-primary" : ""}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -203,7 +216,7 @@ function ShopTemplatesEditor({
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="hover:shadow-card-hover">
|
<Card className="border-border/80 shadow-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">提示</CardTitle>
|
<CardTitle className="text-base">提示</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
Reference in New Issue
Block a user