refactor: support i18n
This commit is contained in:
@@ -34,6 +34,7 @@ import {
|
||||
type AdminAgentTeamScheduleBatchPreview,
|
||||
type BatchAdminAgentTeamSchedulePayload,
|
||||
} from "@/lib/api/admin"
|
||||
import { useI18n } from "@/i18n/provider"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
type BatchScheduleDialogProps = {
|
||||
@@ -42,15 +43,24 @@ type BatchScheduleDialogProps = {
|
||||
onSuccess: (created: number) => void | Promise<void>
|
||||
}
|
||||
|
||||
const weekdayOptions = [
|
||||
{ value: 1, label: "周一" },
|
||||
{ value: 2, label: "周二" },
|
||||
{ value: 3, label: "周三" },
|
||||
{ value: 4, label: "周四" },
|
||||
{ value: 5, label: "周五" },
|
||||
{ value: 6, label: "周六" },
|
||||
{ value: 7, label: "周日" },
|
||||
]
|
||||
type TFunction = (key: string, values?: Record<string, string | number>) => string
|
||||
|
||||
const weekdayKeys = [
|
||||
"weekdayMon",
|
||||
"weekdayTue",
|
||||
"weekdayWed",
|
||||
"weekdayThu",
|
||||
"weekdayFri",
|
||||
"weekdaySat",
|
||||
"weekdaySun",
|
||||
] as const
|
||||
|
||||
function getWeekdayOptions(t: TFunction) {
|
||||
return weekdayKeys.map((key, index) => ({
|
||||
value: index + 1,
|
||||
label: t(`agentTeamSchedule.${key}`),
|
||||
}))
|
||||
}
|
||||
|
||||
function todayDateValue() {
|
||||
const today = new Date()
|
||||
@@ -88,32 +98,33 @@ function buildPayload(form: BatchFormState): BatchAdminAgentTeamSchedulePayload
|
||||
}
|
||||
}
|
||||
|
||||
function getWeekdayLabel(value: number) {
|
||||
return weekdayOptions.find((option) => option.value === value)?.label ?? `周${value}`
|
||||
function getWeekdayLabel(value: number, t: TFunction) {
|
||||
const weekdayOptions = getWeekdayOptions(t)
|
||||
return weekdayOptions.find((option) => option.value === value)?.label ?? String(value)
|
||||
}
|
||||
|
||||
function validateForm(form: BatchFormState) {
|
||||
function validateForm(form: BatchFormState, t: TFunction) {
|
||||
const today = todayDateValue()
|
||||
if (form.selectedTeamIds.length === 0) {
|
||||
return "请选择至少一个客服组"
|
||||
return t("agentTeamSchedule.selectAtLeastOneTeam")
|
||||
}
|
||||
if (!form.startDate || !form.endDate) {
|
||||
return "请选择日期范围"
|
||||
return t("agentTeamSchedule.selectDateRange")
|
||||
}
|
||||
if (form.startDate < today) {
|
||||
return "开始日期不能早于今天"
|
||||
return t("agentTeamSchedule.startBeforeToday")
|
||||
}
|
||||
if (form.endDate < form.startDate) {
|
||||
return "结束日期不能早于开始日期"
|
||||
return t("agentTeamSchedule.endBeforeStart")
|
||||
}
|
||||
if (form.weekdays.length === 0) {
|
||||
return "请选择至少一个星期"
|
||||
return t("agentTeamSchedule.selectAtLeastOneWeekday")
|
||||
}
|
||||
if (!form.startTime || !form.endTime) {
|
||||
return "请选择开始和结束时间"
|
||||
return t("agentTeamSchedule.selectStartEndTime")
|
||||
}
|
||||
if (form.endTime <= form.startTime) {
|
||||
return "结束时间必须晚于开始时间"
|
||||
return t("agentTeamSchedule.endAfterStart")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -123,6 +134,7 @@ export function BatchScheduleDialog({
|
||||
onOpenChange,
|
||||
onSuccess,
|
||||
}: BatchScheduleDialogProps) {
|
||||
const t = useI18n()
|
||||
const [teams, setTeams] = useState<AdminAgentTeam[]>([])
|
||||
const [form, setForm] = useState(defaultFormState)
|
||||
const [step, setStep] = useState<DialogStep>("form")
|
||||
@@ -134,6 +146,7 @@ export function BatchScheduleDialog({
|
||||
const openRef = useRef(open)
|
||||
const previewRequestIdRef = useRef(0)
|
||||
const busy = loadingTeams || previewing || submitting
|
||||
const weekdayOptions = useMemo(() => getWeekdayOptions(t), [t])
|
||||
|
||||
const teamOptions = useMemo(
|
||||
() =>
|
||||
@@ -181,7 +194,7 @@ export function BatchScheduleDialog({
|
||||
}
|
||||
} catch (error) {
|
||||
if (!ignore) {
|
||||
toast.error(error instanceof Error ? error.message : "加载客服组选项失败")
|
||||
toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.loadTeamsFailed"))
|
||||
}
|
||||
} finally {
|
||||
if (!ignore) {
|
||||
@@ -194,7 +207,7 @@ export function BatchScheduleDialog({
|
||||
return () => {
|
||||
ignore = true
|
||||
}
|
||||
}, [open])
|
||||
}, [open, t])
|
||||
|
||||
function updateForm(values: Partial<BatchFormState>) {
|
||||
previewRequestIdRef.current += 1
|
||||
@@ -234,7 +247,7 @@ export function BatchScheduleDialog({
|
||||
}
|
||||
|
||||
async function handlePreview() {
|
||||
const validationMessage = validateForm(form)
|
||||
const validationMessage = validateForm(form, t)
|
||||
if (validationMessage) {
|
||||
toast.error(validationMessage)
|
||||
return
|
||||
@@ -254,7 +267,7 @@ export function BatchScheduleDialog({
|
||||
setStep("preview")
|
||||
} catch (error) {
|
||||
if (openRef.current && previewRequestIdRef.current === requestId) {
|
||||
toast.error(error instanceof Error ? error.message : "预览批量排班失败")
|
||||
toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.previewFailed"))
|
||||
}
|
||||
} finally {
|
||||
if (openRef.current && previewRequestIdRef.current === requestId) {
|
||||
@@ -265,11 +278,11 @@ export function BatchScheduleDialog({
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!preview || !previewPayload) {
|
||||
toast.error("请先预览批量排班")
|
||||
toast.error(t("agentTeamSchedule.previewFirst"))
|
||||
return
|
||||
}
|
||||
if (preview.conflict) {
|
||||
toast.error("存在冲突排班,不能提交")
|
||||
toast.error(t("agentTeamSchedule.conflictCannotSubmit"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -277,17 +290,17 @@ export function BatchScheduleDialog({
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const data = await generateAgentTeamScheduleBatch(payload)
|
||||
toast.success(`已创建 ${data.created} 条客服组排班`)
|
||||
toast.success(t("agentTeamSchedule.batchCreated", { count: data.created }))
|
||||
setPreview(null)
|
||||
setPreviewPayload(null)
|
||||
onOpenChange(false)
|
||||
try {
|
||||
await onSuccess(data.created)
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? `排班已生成,但刷新列表失败:${error.message}` : "排班已生成,但刷新列表失败")
|
||||
toast.error(error instanceof Error ? t("agentTeamSchedule.batchRefreshFailed", { message: error.message }) : t("agentTeamSchedule.batchRefreshFailedFallback"))
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "生成批量排班失败")
|
||||
toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.generateFailed"))
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
@@ -297,9 +310,9 @@ export function BatchScheduleDialog({
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="flex max-h-[calc(100vh-2rem)] flex-col gap-0 overflow-hidden p-0 sm:max-w-4xl">
|
||||
<DialogHeader className="shrink-0 px-6 pt-6">
|
||||
<DialogTitle>批量排班</DialogTitle>
|
||||
<DialogTitle>{t("agentTeamSchedule.batch")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
选择客服组、日期范围和工作时间,先预览后生成排班。
|
||||
{t("agentTeamSchedule.batchDescription")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -307,15 +320,15 @@ export function BatchScheduleDialog({
|
||||
{step === "form" ? (
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Label>客服组</Label>
|
||||
<Label>{t("agentTeamSchedule.team")}</Label>
|
||||
<div className="flex gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<OptionCombobox
|
||||
value=""
|
||||
options={teamOptions}
|
||||
placeholder={loadingTeams ? "加载客服组中..." : "添加客服组"}
|
||||
searchPlaceholder="搜索客服组"
|
||||
emptyText={teams.length === 0 ? "暂无客服组" : "已选择全部客服组"}
|
||||
placeholder={loadingTeams ? t("agentTeamSchedule.loadingTeams") : t("agentTeamSchedule.addTeam")}
|
||||
searchPlaceholder={t("agentTeamSchedule.searchTeam")}
|
||||
emptyText={teams.length === 0 ? t("agentTeamSchedule.noTeams") : t("agentTeamSchedule.allTeamsSelected")}
|
||||
disabled={loadingTeams}
|
||||
onChange={handleTeamSelect}
|
||||
/>
|
||||
@@ -332,7 +345,7 @@ export function BatchScheduleDialog({
|
||||
size="icon-sm"
|
||||
className="size-5 rounded-sm"
|
||||
onClick={() => removeTeam(team.id)}
|
||||
aria-label={`移除${team.name}`}
|
||||
aria-label={t("agentTeamSchedule.removeTeam", { name: team.name })}
|
||||
>
|
||||
<XIcon className="size-3" />
|
||||
</Button>
|
||||
@@ -340,13 +353,13 @@ export function BatchScheduleDialog({
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-muted-foreground">尚未选择客服组</div>
|
||||
<div className="text-sm text-muted-foreground">{t("agentTeamSchedule.noSelectedTeams")}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="batch-schedule-start-date">开始日期</Label>
|
||||
<Label htmlFor="batch-schedule-start-date">{t("agentTeamSchedule.startDate")}</Label>
|
||||
<Input
|
||||
id="batch-schedule-start-date"
|
||||
type="date"
|
||||
@@ -356,7 +369,7 @@ export function BatchScheduleDialog({
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="batch-schedule-end-date">结束日期</Label>
|
||||
<Label htmlFor="batch-schedule-end-date">{t("agentTeamSchedule.endDate")}</Label>
|
||||
<Input
|
||||
id="batch-schedule-end-date"
|
||||
type="date"
|
||||
@@ -368,7 +381,7 @@ export function BatchScheduleDialog({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>星期</Label>
|
||||
<Label>{t("agentTeamSchedule.weekday")}</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{weekdayOptions.map((option) => {
|
||||
const selected = selectedWeekdays.has(option.value)
|
||||
@@ -391,7 +404,7 @@ export function BatchScheduleDialog({
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="batch-schedule-start-time">开始时间</Label>
|
||||
<Label htmlFor="batch-schedule-start-time">{t("agentTeamSchedule.startTime")}</Label>
|
||||
<Input
|
||||
id="batch-schedule-start-time"
|
||||
type="time"
|
||||
@@ -400,7 +413,7 @@ export function BatchScheduleDialog({
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="batch-schedule-end-time">结束时间</Label>
|
||||
<Label htmlFor="batch-schedule-end-time">{t("agentTeamSchedule.endTime")}</Label>
|
||||
<Input
|
||||
id="batch-schedule-end-time"
|
||||
type="time"
|
||||
@@ -411,11 +424,11 @@ export function BatchScheduleDialog({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="batch-schedule-remark">备注</Label>
|
||||
<Label htmlFor="batch-schedule-remark">{t("agentTeamSchedule.remark")}</Label>
|
||||
<Textarea
|
||||
id="batch-schedule-remark"
|
||||
rows={4}
|
||||
placeholder="请输入备注"
|
||||
placeholder={t("agentTeamSchedule.remarkPlaceholder")}
|
||||
value={form.remark}
|
||||
onChange={(event) => updateForm({ remark: event.target.value })}
|
||||
/>
|
||||
@@ -425,13 +438,14 @@ export function BatchScheduleDialog({
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
共 {preview?.total ?? 0} 条排班
|
||||
{hasConflict ? ",存在冲突,请返回调整" : ",确认无冲突后可生成"}
|
||||
{hasConflict
|
||||
? t("agentTeamSchedule.previewSummaryConflict", { total: preview?.total ?? 0 })
|
||||
: t("agentTeamSchedule.previewSummaryReady", { total: preview?.total ?? 0 })}
|
||||
</div>
|
||||
{hasConflict ? (
|
||||
<Badge variant="destructive">存在冲突</Badge>
|
||||
<Badge variant="destructive">{t("agentTeamSchedule.hasConflict")}</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary">无冲突</Badge>
|
||||
<Badge variant="secondary">{t("agentTeamSchedule.noConflict")}</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
@@ -439,12 +453,12 @@ export function BatchScheduleDialog({
|
||||
<Table>
|
||||
<TableHeader className="bg-muted/40">
|
||||
<TableRow>
|
||||
<TableHead>客服组</TableHead>
|
||||
<TableHead>日期</TableHead>
|
||||
<TableHead>星期</TableHead>
|
||||
<TableHead>时间</TableHead>
|
||||
<TableHead>备注</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>{t("agentTeamSchedule.team")}</TableHead>
|
||||
<TableHead>{t("agentTeamSchedule.date")}</TableHead>
|
||||
<TableHead>{t("agentTeamSchedule.weekday")}</TableHead>
|
||||
<TableHead>{t("agentTeamSchedule.time")}</TableHead>
|
||||
<TableHead>{t("agentTeamSchedule.remark")}</TableHead>
|
||||
<TableHead>{t("agentTeamSchedule.status")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -456,11 +470,11 @@ export function BatchScheduleDialog({
|
||||
)}
|
||||
>
|
||||
<TableCell>
|
||||
<div className="font-medium">{item.teamName || `客服组#${item.teamId}`}</div>
|
||||
<div className="text-xs text-muted-foreground">组ID:{item.teamId}</div>
|
||||
<div className="font-medium">{item.teamName || t("agentTeamSchedule.teamFallback", { id: item.teamId })}</div>
|
||||
<div className="text-xs text-muted-foreground">{t("agentTeamSchedule.teamId", { id: item.teamId })}</div>
|
||||
</TableCell>
|
||||
<TableCell>{item.date}</TableCell>
|
||||
<TableCell>{getWeekdayLabel(item.weekday)}</TableCell>
|
||||
<TableCell>{getWeekdayLabel(item.weekday, t)}</TableCell>
|
||||
<TableCell>
|
||||
{item.startAt.slice(11, 16)} - {item.endAt.slice(11, 16)}
|
||||
</TableCell>
|
||||
@@ -468,10 +482,10 @@ export function BatchScheduleDialog({
|
||||
<TableCell>
|
||||
{item.conflict ? (
|
||||
<span className="font-medium">
|
||||
{item.conflictReason || "排班冲突"}
|
||||
{item.conflictReason || t("agentTeamSchedule.conflictFallback")}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">可生成</span>
|
||||
<span className="text-muted-foreground">{t("agentTeamSchedule.canGenerate")}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -479,7 +493,7 @@ export function BatchScheduleDialog({
|
||||
{preview && preview.items.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="py-10 text-center text-muted-foreground">
|
||||
没有可生成的排班
|
||||
{t("agentTeamSchedule.emptyPreview")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
@@ -500,7 +514,7 @@ export function BatchScheduleDialog({
|
||||
disabled={submitting}
|
||||
>
|
||||
<ArrowLeftIcon />
|
||||
返回修改
|
||||
{t("agentTeamSchedule.backToEdit")}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
@@ -509,12 +523,12 @@ export function BatchScheduleDialog({
|
||||
onClick={() => handleOpenChange(false)}
|
||||
disabled={busy}
|
||||
>
|
||||
取消
|
||||
{t("agentTeamSchedule.cancel")}
|
||||
</Button>
|
||||
{step === "form" ? (
|
||||
<Button type="button" onClick={() => void handlePreview()} disabled={busy}>
|
||||
{previewing ? <Loader2Icon className="animate-spin" /> : <CheckIcon />}
|
||||
预览
|
||||
{t("agentTeamSchedule.preview")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -523,7 +537,7 @@ export function BatchScheduleDialog({
|
||||
disabled={submitting || hasConflict || !preview || !previewPayload || preview.items.length === 0}
|
||||
>
|
||||
{submitting ? <Loader2Icon className="animate-spin" /> : <CheckIcon />}
|
||||
生成排班
|
||||
{t("agentTeamSchedule.generate")}
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
|
||||
@@ -58,7 +58,7 @@ export function formatDateTimeValue(date: Date) {
|
||||
}
|
||||
|
||||
export function formatMonthTitle(monthStart: Date) {
|
||||
return `${monthStart.getFullYear()}年${String(monthStart.getMonth() + 1).padStart(2, "0")}月`
|
||||
return `${monthStart.getFullYear()}-${String(monthStart.getMonth() + 1).padStart(2, "0")}`
|
||||
}
|
||||
|
||||
function formatDate(date: Date) {
|
||||
|
||||
@@ -13,7 +13,17 @@ import { cn, formatDateTime } from "@/lib/utils"
|
||||
import { isSameLocalDay } from "./calendar-date-range"
|
||||
import { buildDayTimeLayout } from "./calendar-time-layout"
|
||||
|
||||
const weekDayNames = ["一", "二", "三", "四", "五", "六", "日"]
|
||||
type TFunction = (key: string, values?: Record<string, string | number>) => string
|
||||
|
||||
const weekDayKeys = [
|
||||
"weekdayShortMon",
|
||||
"weekdayShortTue",
|
||||
"weekdayShortWed",
|
||||
"weekdayShortThu",
|
||||
"weekdayShortFri",
|
||||
"weekdayShortSat",
|
||||
"weekdayShortSun",
|
||||
] as const
|
||||
const dayMs = 24 * 60 * 60 * 1000
|
||||
const minuteMs = 60 * 1000
|
||||
const minDurationMs = 15 * minuteMs
|
||||
@@ -31,6 +41,7 @@ type ScheduleCalendarProps = {
|
||||
onEdit: (item: AdminAgentTeamSchedule) => void
|
||||
onMove: (payload: UpdateAdminAgentTeamSchedulePayload) => Promise<void>
|
||||
onResize: (payload: UpdateAdminAgentTeamSchedulePayload) => Promise<void>
|
||||
t: TFunction
|
||||
}
|
||||
|
||||
type DragState =
|
||||
@@ -226,6 +237,7 @@ export function ScheduleCalendar({
|
||||
onEdit,
|
||||
onMove,
|
||||
onResize,
|
||||
t,
|
||||
}: ScheduleCalendarProps) {
|
||||
const days = buildCalendarDays(calendarStart, calendarEnd)
|
||||
const defaultTeamID = teams[0]?.id ?? 0
|
||||
@@ -250,7 +262,7 @@ export function ScheduleCalendar({
|
||||
return {
|
||||
itemId: state.item.id,
|
||||
date: null,
|
||||
label: "拖到日历日期格内",
|
||||
label: t("agentTeamSchedule.dropInsideCalendar"),
|
||||
invalid: true,
|
||||
x: pointerEvent.clientX,
|
||||
y: pointerEvent.clientY,
|
||||
@@ -265,7 +277,7 @@ export function ScheduleCalendar({
|
||||
return {
|
||||
itemId: state.item.id,
|
||||
date,
|
||||
label: "不能修改历史日期",
|
||||
label: t("agentTeamSchedule.historyReadonly"),
|
||||
invalid: true,
|
||||
x: pointerEvent.clientX,
|
||||
y: pointerEvent.clientY,
|
||||
@@ -274,11 +286,11 @@ export function ScheduleCalendar({
|
||||
|
||||
const point = { x: pointerEvent.clientX, y: pointerEvent.clientY }
|
||||
if (state.type === "move") {
|
||||
return buildPreviewFromPayload(state.item.id, date, buildMovePayload(state.item, date), point, "无法移动到这里")
|
||||
return buildPreviewFromPayload(state.item.id, date, buildMovePayload(state.item, date), point, t("agentTeamSchedule.cannotMoveHere"))
|
||||
}
|
||||
|
||||
const payload = buildResizePayload(state.item, state.edge, getPointerDateInCell(pointerEvent, cell))
|
||||
return buildPreviewFromPayload(state.item.id, date, payload, point, "不能跨天或少于 15 分钟")
|
||||
return buildPreviewFromPayload(state.item.id, date, payload, point, t("agentTeamSchedule.resizeInvalid"))
|
||||
}
|
||||
|
||||
function cleanupPointerInteraction(
|
||||
@@ -365,7 +377,7 @@ export function ScheduleCalendar({
|
||||
if (teams.length === 0 && !loading) {
|
||||
return (
|
||||
<div className="flex min-h-64 items-center justify-center rounded-lg border bg-background text-sm text-muted-foreground">
|
||||
暂无客服组,无法展示排班日历
|
||||
{t("agentTeamSchedule.noTeamsCalendar")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -426,7 +438,7 @@ export function ScheduleCalendar({
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
{today ? (
|
||||
<span className="rounded-sm bg-primary px-1.5 py-0.5 text-[10px] font-medium leading-none text-primary-foreground">
|
||||
今天
|
||||
{t("agentTeamSchedule.today")}
|
||||
</span>
|
||||
) : null}
|
||||
{historical ? null : <CalendarPlusIcon className="size-3.5 text-muted-foreground" />}
|
||||
@@ -434,7 +446,7 @@ export function ScheduleCalendar({
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{daySchedules.slice(0, 5).map((item) => {
|
||||
const teamName = item.teamName || teams.find((team) => team.id === item.teamId)?.name || `客服组#${item.teamId}`
|
||||
const teamName = item.teamName || teams.find((team) => team.id === item.teamId)?.name || t("agentTeamSchedule.teamFallback", { id: item.teamId })
|
||||
const busy = savingId === item.id
|
||||
const active = interactionPreview?.itemId === item.id
|
||||
const timeLayout = dayTimeLayout.items.get(item.id)
|
||||
@@ -512,7 +524,7 @@ export function ScheduleCalendar({
|
||||
)
|
||||
})}
|
||||
{daySchedules.length > 5 ? (
|
||||
<div className="text-xs text-muted-foreground">还有 {daySchedules.length - 5} 条</div>
|
||||
<div className="text-xs text-muted-foreground">{t("agentTeamSchedule.moreItems", { count: daySchedules.length - 5 })}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
@@ -528,7 +540,7 @@ export function ScheduleCalendar({
|
||||
return (
|
||||
<div key={date} className="grid grid-cols-[112px_minmax(0,1fr)]">
|
||||
<div className="border-r bg-muted/40 px-3 py-3 text-sm font-medium">
|
||||
<div>周{weekDayNames[dayIndex] ?? ""}</div>
|
||||
<div>{t(`agentTeamSchedule.${weekDayKeys[dayIndex] ?? "weekdayShortMon"}`)}</div>
|
||||
<div className="mt-1 text-xs font-normal text-muted-foreground">{date.slice(5)}</div>
|
||||
</div>
|
||||
{renderDayCell(day, dayIndex, {
|
||||
@@ -562,9 +574,9 @@ export function ScheduleCalendar({
|
||||
return (
|
||||
<div className="min-w-[960px] overflow-hidden rounded-lg border bg-background">
|
||||
<div className="grid grid-cols-7 border-b bg-muted/40">
|
||||
{weekDayNames.map((name) => (
|
||||
<div key={name} className="flex h-10 items-center justify-center border-l first:border-l-0 text-sm font-medium">
|
||||
周{name}
|
||||
{weekDayKeys.map((key) => (
|
||||
<div key={key} className="flex h-10 items-center justify-center border-l first:border-l-0 text-sm font-medium">
|
||||
{t(`agentTeamSchedule.${key}`)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { Controller, Resolver, useForm } from "react-hook-form"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { Controller, type Resolver, useForm } from "react-hook-form"
|
||||
import { toast } from "sonner"
|
||||
import { z } from "zod/v4"
|
||||
|
||||
@@ -27,8 +27,11 @@ import {
|
||||
type AdminAgentTeamSchedule,
|
||||
type CreateAdminAgentTeamSchedulePayload,
|
||||
fetchAgentTeamSchedule,
|
||||
fetchAgentTeamsAll
|
||||
fetchAgentTeamsAll,
|
||||
} from "@/lib/api/admin"
|
||||
import { useI18n } from "@/i18n/provider"
|
||||
|
||||
type TFunction = (key: string, values?: Record<string, string | number>) => string
|
||||
|
||||
type ScheduleEditDialogProps = {
|
||||
open: boolean
|
||||
@@ -47,10 +50,18 @@ const emptyForm: EditForm = {
|
||||
remark: "",
|
||||
}
|
||||
|
||||
const editFormSchema = z.object({
|
||||
teamId: z.string().trim().regex(/^\d+$/, "请选择客服组"),
|
||||
startAt: z.string().trim().min(1, "开始时间不能为空"),
|
||||
endAt: z.string().trim().min(1, "结束时间不能为空"),
|
||||
type EditForm = {
|
||||
teamId: string
|
||||
startAt: string
|
||||
endAt: string
|
||||
remark: string
|
||||
}
|
||||
|
||||
function createEditFormSchema(t: TFunction) {
|
||||
return z.object({
|
||||
teamId: z.string().trim().regex(/^\d+$/, t("agentTeamSchedule.teamRequired")),
|
||||
startAt: z.string().trim().min(1, t("agentTeamSchedule.startRequired")),
|
||||
endAt: z.string().trim().min(1, t("agentTeamSchedule.endRequired")),
|
||||
remark: z.string().trim(),
|
||||
}).superRefine((value, ctx) => {
|
||||
const startAt = parseDateTimeLocal(value.startAt)
|
||||
@@ -62,7 +73,7 @@ const editFormSchema = z.object({
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["endAt"],
|
||||
message: "结束时间必须晚于开始时间",
|
||||
message: t("agentTeamSchedule.endAfterStart"),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -70,24 +81,18 @@ const editFormSchema = z.object({
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["endAt"],
|
||||
message: "单条排班记录不能跨天",
|
||||
message: t("agentTeamSchedule.singleDayOnly"),
|
||||
})
|
||||
}
|
||||
if (startAt < startOfLocalDay(new Date())) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["startAt"],
|
||||
message: "不能添加或修改历史日期的排班",
|
||||
message: t("agentTeamSchedule.historyReadonly"),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
type EditForm = z.infer<typeof editFormSchema>
|
||||
const editFormResolver = zodResolver(editFormSchema as never) as Resolver<
|
||||
z.input<typeof editFormSchema>,
|
||||
undefined,
|
||||
z.output<typeof editFormSchema>
|
||||
>
|
||||
}
|
||||
|
||||
function toDateTimeLocal(value?: string) {
|
||||
if (!value) {
|
||||
@@ -180,6 +185,7 @@ function ScheduleEditDialogBody({
|
||||
onSubmit,
|
||||
onDelete,
|
||||
}: ScheduleEditDialogBodyProps) {
|
||||
const t = useI18n()
|
||||
const [teams, setTeams] = useState<AdminAgentTeam[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const loadOptions = useCallback(async () => {
|
||||
@@ -187,14 +193,15 @@ function ScheduleEditDialogBody({
|
||||
const teamsData = await fetchAgentTeamsAll()
|
||||
setTeams(teamsData)
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "加载选项失败")
|
||||
toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.loadOptionsFailed"))
|
||||
}
|
||||
}, [])
|
||||
const form = useForm<
|
||||
z.input<typeof editFormSchema>,
|
||||
undefined,
|
||||
z.output<typeof editFormSchema>
|
||||
>({
|
||||
}, [t])
|
||||
const editFormSchema = useMemo(() => createEditFormSchema(t), [t])
|
||||
const editFormResolver = useMemo(
|
||||
() => zodResolver(editFormSchema) as Resolver<EditForm>,
|
||||
[editFormSchema],
|
||||
)
|
||||
const form = useForm<EditForm>({
|
||||
resolver: editFormResolver,
|
||||
defaultValues: emptyForm,
|
||||
})
|
||||
@@ -218,13 +225,13 @@ function ScheduleEditDialogBody({
|
||||
const data = await fetchAgentTeamSchedule(itemId)
|
||||
reset(buildForm(data))
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "加载客服组排班详情失败")
|
||||
toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.loadDetailFailed"))
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
void loadDetail()
|
||||
}, [defaultValues, itemId, reset])
|
||||
}, [defaultValues, itemId, reset, t])
|
||||
|
||||
useEffect(() => {
|
||||
void loadOptions()
|
||||
@@ -237,18 +244,18 @@ function ScheduleEditDialogBody({
|
||||
return (
|
||||
<DialogContent className="max-w-xl gap-0 p-0 sm:max-w-xl">
|
||||
<DialogHeader className="px-6 pt-6">
|
||||
<DialogTitle>{itemId ? "编辑客服组排班" : "新建客服组排班"}</DialogTitle>
|
||||
<DialogTitle>{itemId ? t("agentTeamSchedule.editTitle") : t("agentTeamSchedule.createTitle")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="text-muted-foreground">加载中...</div>
|
||||
<div className="text-muted-foreground">{t("agentTeamSchedule.loading")}</div>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<div className="space-y-4 p-6">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<Field data-invalid={!!errors.teamId}>
|
||||
<FieldLabel>客服组</FieldLabel>
|
||||
<FieldLabel>{t("agentTeamSchedule.team")}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -260,8 +267,9 @@ function ScheduleEditDialogBody({
|
||||
value: String(team.id),
|
||||
label: team.name,
|
||||
}))}
|
||||
placeholder="请选择客服组"
|
||||
searchPlaceholder="搜索客服组"
|
||||
placeholder={t("agentTeamSchedule.teamRequired")}
|
||||
searchPlaceholder={t("agentTeamSchedule.searchTeam")}
|
||||
emptyText={t("agentTeamSchedule.emptyTeam")}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
@@ -272,14 +280,14 @@ function ScheduleEditDialogBody({
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<Field data-invalid={!!errors.startAt}>
|
||||
<FieldLabel htmlFor="agent-team-schedule-start-at">开始时间</FieldLabel>
|
||||
<FieldLabel htmlFor="agent-team-schedule-start-at">{t("agentTeamSchedule.startTime")}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Input id="agent-team-schedule-start-at" type="datetime-local" min={minDateTime} {...register("startAt")} />
|
||||
<FieldError errors={[errors.startAt]} />
|
||||
</FieldContent>
|
||||
</Field>
|
||||
<Field data-invalid={!!errors.endAt}>
|
||||
<FieldLabel htmlFor="agent-team-schedule-end-at">结束时间</FieldLabel>
|
||||
<FieldLabel htmlFor="agent-team-schedule-end-at">{t("agentTeamSchedule.endTime")}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Input id="agent-team-schedule-end-at" type="datetime-local" min={minDateTime} {...register("endAt")} />
|
||||
<FieldError errors={[errors.endAt]} />
|
||||
@@ -287,23 +295,23 @@ function ScheduleEditDialogBody({
|
||||
</Field>
|
||||
</div>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="agent-team-schedule-remark">备注</FieldLabel>
|
||||
<FieldLabel htmlFor="agent-team-schedule-remark">{t("agentTeamSchedule.remark")}</FieldLabel>
|
||||
<FieldContent>
|
||||
<Textarea id="agent-team-schedule-remark" rows={4} placeholder="请输入备注" {...register("remark")} />
|
||||
<Textarea id="agent-team-schedule-remark" rows={4} placeholder={t("agentTeamSchedule.remarkPlaceholder")} {...register("remark")} />
|
||||
</FieldContent>
|
||||
</Field>
|
||||
</div>
|
||||
<DialogFooter className="mx-0 mb-0 px-6 py-4">
|
||||
{itemId && onDelete ? (
|
||||
<Button type="button" variant="destructive" onClick={() => void onDelete(itemId)} disabled={saving}>
|
||||
删除
|
||||
{t("agentTeamSchedule.delete")}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={saving}>
|
||||
取消
|
||||
{t("agentTeamSchedule.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" disabled={saving || loading}>
|
||||
{saving ? "保存中..." : "保存"}
|
||||
{saving ? t("agentTeamSchedule.saving") : t("agentTeamSchedule.save")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
type PageResult,
|
||||
type UpdateAdminAgentTeamSchedulePayload,
|
||||
} from "@/lib/api/admin"
|
||||
import { useI18n } from "@/i18n/provider"
|
||||
import { formatDateTime } from "@/lib/utils"
|
||||
import { BatchScheduleDialog } from "./_components/batch-schedule-dialog"
|
||||
import { ScheduleCalendar } from "./_components/calendar"
|
||||
@@ -56,7 +57,6 @@ import {
|
||||
addDays,
|
||||
addMonths,
|
||||
formatDateTimeValue,
|
||||
formatMonthTitle,
|
||||
formatWeekTitle,
|
||||
startOfDay,
|
||||
startOfMonth,
|
||||
@@ -79,6 +79,7 @@ function isHistoricalSchedule(item: AdminAgentTeamSchedule) {
|
||||
}
|
||||
|
||||
export default function DashboardAgentTeamSchedulesPage() {
|
||||
const t = useI18n()
|
||||
const [viewMode, setViewMode] = useState<ViewMode>("month")
|
||||
const [teamFilterInput, setTeamFilterInput] = useState("all")
|
||||
const [teamFilter, setTeamFilter] = useState("all")
|
||||
@@ -120,11 +121,11 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
})
|
||||
setResult(data)
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "加载客服组排班失败")
|
||||
toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.loadFailed"))
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [limit, page, teamFilter])
|
||||
}, [limit, page, t, teamFilter])
|
||||
|
||||
const loadCalendarData = useCallback(async () => {
|
||||
setCalendarLoading(true)
|
||||
@@ -138,20 +139,20 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
})
|
||||
setCalendarItems(data)
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "加载客服组排班日历失败")
|
||||
toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.loadCalendarFailed"))
|
||||
} finally {
|
||||
setCalendarLoading(false)
|
||||
}
|
||||
}, [monthStart, teamFilter, viewMode, weekStart])
|
||||
}, [monthStart, t, teamFilter, viewMode, weekStart])
|
||||
|
||||
const loadTeams = useCallback(async () => {
|
||||
try {
|
||||
const data = await fetchAgentTeamsAll()
|
||||
setTeams(data)
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "加载客服组选项失败")
|
||||
toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.loadTeamsFailed"))
|
||||
}
|
||||
}, [])
|
||||
}, [t])
|
||||
|
||||
const refreshActiveView = useCallback(async () => {
|
||||
await Promise.all([
|
||||
@@ -194,7 +195,7 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
|
||||
function openEditDialog(item: AdminAgentTeamSchedule) {
|
||||
if (isHistoricalSchedule(item)) {
|
||||
toast.error("不能修改历史日期的排班")
|
||||
toast.error(t("agentTeamSchedule.historyReadonly"))
|
||||
return
|
||||
}
|
||||
setDialogDefaults(null)
|
||||
@@ -221,17 +222,17 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
try {
|
||||
if (editingItem) {
|
||||
await updateAgentTeamSchedule({ id: editingItem.id, ...payload })
|
||||
toast.success("已更新客服组排班")
|
||||
toast.success(t("agentTeamSchedule.updated"))
|
||||
} else {
|
||||
await createAgentTeamSchedule(payload)
|
||||
toast.success("已创建客服组排班")
|
||||
toast.success(t("agentTeamSchedule.created"))
|
||||
}
|
||||
setDialogOpen(false)
|
||||
setEditingItem(null)
|
||||
setDialogDefaults(null)
|
||||
await refreshActiveView()
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "保存客服组排班失败")
|
||||
toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.saveFailed"))
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
@@ -245,13 +246,13 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
setActionLoadingId(id)
|
||||
try {
|
||||
await deleteAgentTeamSchedule(id)
|
||||
toast.success("已删除客服组排班")
|
||||
toast.success(t("agentTeamSchedule.deleted"))
|
||||
setDialogOpen(false)
|
||||
setEditingItem(null)
|
||||
setDialogDefaults(null)
|
||||
await refreshActiveView()
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "删除客服组排班失败")
|
||||
toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.deleteFailed"))
|
||||
} finally {
|
||||
setActionLoadingId(null)
|
||||
}
|
||||
@@ -264,16 +265,16 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
async function handleCalendarUpdate(payload: UpdateAdminAgentTeamSchedulePayload) {
|
||||
const startAt = parseLocalDateTime(payload.startAt)
|
||||
if (startAt && startAt < startOfDay(new Date())) {
|
||||
toast.error("不能修改历史日期的排班")
|
||||
toast.error(t("agentTeamSchedule.historyReadonly"))
|
||||
return
|
||||
}
|
||||
setActionLoadingId(payload.id)
|
||||
try {
|
||||
await updateAgentTeamSchedule(payload)
|
||||
toast.success("已更新客服组排班")
|
||||
toast.success(t("agentTeamSchedule.updated"))
|
||||
await loadCalendarData()
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "更新客服组排班失败")
|
||||
toast.error(error instanceof Error ? error.message : t("agentTeamSchedule.updateFailed"))
|
||||
await loadCalendarData()
|
||||
} finally {
|
||||
setActionLoadingId(null)
|
||||
@@ -298,7 +299,7 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
onClick={() => setViewMode("month")}
|
||||
>
|
||||
<CalendarDaysIcon />
|
||||
月
|
||||
{t("agentTeamSchedule.month")}
|
||||
</Button>
|
||||
<Button
|
||||
variant={viewMode === "week" ? "default" : "outline"}
|
||||
@@ -306,7 +307,7 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
onClick={() => setViewMode("week")}
|
||||
>
|
||||
<CalendarRangeIcon />
|
||||
周
|
||||
{t("agentTeamSchedule.week")}
|
||||
</Button>
|
||||
<Button
|
||||
variant={viewMode === "list" ? "default" : "outline"}
|
||||
@@ -314,37 +315,42 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
onClick={() => setViewMode("list")}
|
||||
>
|
||||
<ListIcon />
|
||||
列表
|
||||
{t("agentTeamSchedule.list")}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
{viewMode === "month" ? (
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="icon-sm" onClick={() => setMonthStart(addMonths(monthStart, -1))} aria-label="上一月">
|
||||
<Button variant="outline" size="icon-sm" onClick={() => setMonthStart(addMonths(monthStart, -1))} aria-label={t("agentTeamSchedule.prevMonth")}>
|
||||
<ChevronLeftIcon />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setMonthStart(startOfMonth(new Date()))}>
|
||||
本月
|
||||
{t("agentTeamSchedule.thisMonth")}
|
||||
</Button>
|
||||
<Button variant="outline" size="icon-sm" onClick={() => setMonthStart(addMonths(monthStart, 1))} aria-label="下一月">
|
||||
<Button variant="outline" size="icon-sm" onClick={() => setMonthStart(addMonths(monthStart, 1))} aria-label={t("agentTeamSchedule.nextMonth")}>
|
||||
<ChevronRightIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
) : null}
|
||||
{viewMode === "week" ? (
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="icon-sm" onClick={() => setWeekStart(addDays(weekStart, -7))} aria-label="上一周">
|
||||
<Button variant="outline" size="icon-sm" onClick={() => setWeekStart(addDays(weekStart, -7))} aria-label={t("agentTeamSchedule.prevWeek")}>
|
||||
<ChevronLeftIcon />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setWeekStart(startOfWeek(new Date()))}>
|
||||
本周
|
||||
{t("agentTeamSchedule.thisWeek")}
|
||||
</Button>
|
||||
<Button variant="outline" size="icon-sm" onClick={() => setWeekStart(addDays(weekStart, 7))} aria-label="下一周">
|
||||
<Button variant="outline" size="icon-sm" onClick={() => setWeekStart(addDays(weekStart, 7))} aria-label={t("agentTeamSchedule.nextWeek")}>
|
||||
<ChevronRightIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
) : null}
|
||||
{viewMode === "month" ? (
|
||||
<div className="text-sm text-muted-foreground">{formatMonthTitle(monthStart)}</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t("agentTeamSchedule.monthTitle", {
|
||||
year: monthStart.getFullYear(),
|
||||
month: String(monthStart.getMonth() + 1).padStart(2, "0"),
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
{viewMode === "week" ? (
|
||||
<div className="text-sm text-muted-foreground">{formatWeekTitle(weekStart)}</div>
|
||||
@@ -352,7 +358,7 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
{viewMode !== "list" ? (
|
||||
<Button variant="outline" size="sm" onClick={goToToday}>
|
||||
<CalendarSearchIcon />
|
||||
今天
|
||||
{t("agentTeamSchedule.today")}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -362,18 +368,18 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
<OptionCombobox
|
||||
value={teamFilterInput}
|
||||
options={[
|
||||
{ value: "all", label: "全部客服组" },
|
||||
{ value: "all", label: t("agentTeamSchedule.allTeams") },
|
||||
...teams.map((team) => ({ value: String(team.id), label: team.name })),
|
||||
]}
|
||||
placeholder="筛选客服组"
|
||||
searchPlaceholder="搜索客服组"
|
||||
emptyText="未找到客服组"
|
||||
placeholder={t("agentTeamSchedule.filterTeam")}
|
||||
searchPlaceholder={t("agentTeamSchedule.searchTeam")}
|
||||
emptyText={t("agentTeamSchedule.emptyTeam")}
|
||||
onChange={(value) => setTeamFilterInput(value)}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" onClick={applyFilters} disabled={refreshing}>
|
||||
<SearchIcon />
|
||||
查询
|
||||
{t("agentTeamSchedule.query")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -381,15 +387,15 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
disabled={refreshing}
|
||||
>
|
||||
<RefreshCwIcon className={refreshing ? "animate-spin" : ""} />
|
||||
刷新
|
||||
{t("agentTeamSchedule.refresh")}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => setBatchDialogOpen(true)}>
|
||||
<LayersIcon />
|
||||
批量排班
|
||||
{t("agentTeamSchedule.batch")}
|
||||
</Button>
|
||||
<Button onClick={() => openCreateDialog()}>
|
||||
<PlusIcon />
|
||||
新建
|
||||
{t("agentTeamSchedule.new")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -409,6 +415,7 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
onEdit={openEditDialog}
|
||||
onMove={handleCalendarUpdate}
|
||||
onResize={handleCalendarUpdate}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -417,9 +424,9 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
<Table>
|
||||
<TableHeader className="bg-muted/40">
|
||||
<TableRow>
|
||||
<TableHead>客服组</TableHead>
|
||||
<TableHead>时间范围</TableHead>
|
||||
<TableHead className="w-[92px] text-right">操作</TableHead>
|
||||
<TableHead>{t("agentTeamSchedule.team")}</TableHead>
|
||||
<TableHead>{t("agentTeamSchedule.timeRange")}</TableHead>
|
||||
<TableHead className="w-[92px] text-right">{t("agentTeamSchedule.actions")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -431,8 +438,8 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
<CalendarClockIcon className="size-4" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="font-medium">{item.teamName || `客服组#${item.teamId}`}</div>
|
||||
<div className="text-xs text-muted-foreground">组ID:{item.teamId}</div>
|
||||
<div className="font-medium">{item.teamName || t("agentTeamSchedule.teamFallback", { id: item.teamId })}</div>
|
||||
<div className="text-xs text-muted-foreground">{t("agentTeamSchedule.teamId", { id: item.teamId })}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -443,12 +450,12 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
<TableCell className="text-right">
|
||||
<ButtonGroup className="ml-auto">
|
||||
<Button variant="outline" size="sm" onClick={() => openEditDialog(item)} disabled={isHistoricalSchedule(item)}>
|
||||
编辑
|
||||
{t("agentTeamSchedule.edit")}
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={<Button variant="outline" size="icon-sm" />}
|
||||
aria-label={`更多操作 ${item.startAt}`}
|
||||
aria-label={t("agentTeamSchedule.moreActions", { name: item.startAt })}
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
</DropdownMenuTrigger>
|
||||
@@ -458,7 +465,7 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
className="text-destructive focus:text-destructive"
|
||||
>
|
||||
<Trash2Icon />
|
||||
{actionLoadingId === item.id ? "删除中..." : "删除"}
|
||||
{actionLoadingId === item.id ? t("agentTeamSchedule.deleting") : t("agentTeamSchedule.delete")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -469,7 +476,7 @@ export default function DashboardAgentTeamSchedulesPage() {
|
||||
{!loading && result.results.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3} className="py-12 text-center text-muted-foreground">
|
||||
没有匹配的客服组排班
|
||||
{t("agentTeamSchedule.emptyRows")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user