feat: add calendar scheduling feature for agent teams

- Implemented FindCalendarSchedules method in agentTeamScheduleService to retrieve schedules within a specified time range.
- Added new API endpoint fetchAgentTeamScheduleCalendar to fetch schedules for the calendar view.
- Created ScheduleCalendar component to display agent team schedules in a calendar format with drag-and-drop functionality for moving and resizing schedules.
- Updated EditDialog component to support deleting schedules and handling default values for new entries.
- Enhanced DashboardAgentTeamSchedulesPage to toggle between calendar and list views, and to load calendar data based on selected week.
- Added tests for FindCalendarSchedules to ensure correct schedule retrieval and validation of time ranges.
This commit is contained in:
mlogclub
2026-04-29 09:23:54 +08:00
parent ce8a8d5bcc
commit 8646b002c1
10 changed files with 895 additions and 139 deletions
@@ -35,8 +35,10 @@ type ScheduleEditDialogProps = {
open: boolean
saving: boolean
itemId: number | null
defaultValues?: Partial<CreateAdminAgentTeamSchedulePayload> | null
onOpenChange: (open: boolean) => void
onSubmit: (payload: CreateAdminAgentTeamSchedulePayload) => Promise<void>
onDelete?: (id: number) => Promise<void>
}
const sourceTypeOptions = [
@@ -75,9 +77,15 @@ function toDateTimeLocal(value?: string) {
return value.replace(" ", "T").slice(0, 16)
}
function buildForm(item: AdminAgentTeamSchedule | null): EditForm {
function buildForm(item: AdminAgentTeamSchedule | null, defaultValues?: Partial<CreateAdminAgentTeamSchedulePayload> | null): EditForm {
if (!item) {
return emptyForm
return {
teamId: defaultValues?.teamId ? String(defaultValues.teamId) : emptyForm.teamId,
startAt: toDateTimeLocal(defaultValues?.startAt),
endAt: toDateTimeLocal(defaultValues?.endAt),
sourceType: (defaultValues?.sourceType as EditForm["sourceType"] | undefined) ?? emptyForm.sourceType,
remark: defaultValues?.remark ?? emptyForm.remark,
}
}
return {
teamId: String(item.teamId),
@@ -102,8 +110,10 @@ export function EditDialog({
open,
saving,
itemId,
defaultValues,
onOpenChange,
onSubmit,
onDelete,
}: ScheduleEditDialogProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@@ -111,9 +121,11 @@ export function EditDialog({
<ScheduleEditDialogBody
key={itemId ? `edit-${itemId}` : "create"}
itemId={itemId}
defaultValues={defaultValues}
saving={saving}
onOpenChange={onOpenChange}
onSubmit={onSubmit}
onDelete={onDelete}
/>
) : null}
</Dialog>
@@ -125,8 +137,10 @@ type ScheduleEditDialogBodyProps = Omit<ScheduleEditDialogProps, "open">
function ScheduleEditDialogBody({
saving,
itemId,
defaultValues,
onOpenChange,
onSubmit,
onDelete,
}: ScheduleEditDialogBodyProps) {
const [teams, setTeams] = useState<AdminAgentTeam[]>([])
const [loading, setLoading] = useState(false)
@@ -157,7 +171,7 @@ function ScheduleEditDialogBody({
useEffect(() => {
async function loadDetail() {
if (!itemId) {
reset(emptyForm)
reset(buildForm(null, defaultValues))
return
}
setLoading(true)
@@ -171,7 +185,7 @@ function ScheduleEditDialogBody({
}
}
void loadDetail()
}, [itemId, reset])
}, [defaultValues, itemId, reset])
useEffect(() => {
void loadOptions()
@@ -269,6 +283,11 @@ function ScheduleEditDialogBody({
</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}>
</Button>
) : null}
<Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={saving}>
</Button>