194 lines
5.2 KiB
TypeScript
194 lines
5.2 KiB
TypeScript
/** @jsxImportSource react */
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import type { ReactElement } from 'react';
|
|
import { Button, Input, Select } from '@lilith/ui-primitives';
|
|
import { Modal } from '@/components/Modal';
|
|
import { BlockType, BlockStatus } from '@life-platform/shared';
|
|
import type {
|
|
TimeBlock,
|
|
CreateTimeBlockDto,
|
|
UpdateTimeBlockDto,
|
|
} from '@life-platform/shared';
|
|
import {
|
|
useCreateTimeBlock,
|
|
useUpdateTimeBlock,
|
|
useDeleteTimeBlock,
|
|
} from './useScheduling';
|
|
import { FormField, FormLabel, FormActions } from './scheduling-styles';
|
|
|
|
const BLOCK_TYPE_LABELS: Record<string, string> = {
|
|
[BlockType.Work]: 'Work',
|
|
[BlockType.Personal]: 'Personal',
|
|
[BlockType.Health]: 'Health',
|
|
[BlockType.Break]: 'Break',
|
|
[BlockType.Transit]: 'Transit',
|
|
[BlockType.Available]: 'Available',
|
|
};
|
|
|
|
const BLOCK_STATUS_LABELS: Record<string, string> = {
|
|
[BlockStatus.Planned]: 'Planned',
|
|
[BlockStatus.InProgress]: 'In Progress',
|
|
[BlockStatus.Completed]: 'Completed',
|
|
[BlockStatus.Skipped]: 'Skipped',
|
|
[BlockStatus.Rescheduled]: 'Rescheduled',
|
|
};
|
|
|
|
interface TimeBlockModalProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
date: string;
|
|
block?: TimeBlock | null;
|
|
}
|
|
|
|
export function TimeBlockModal({
|
|
open,
|
|
onClose,
|
|
date,
|
|
block,
|
|
}: TimeBlockModalProps): ReactElement | null {
|
|
const createMutation = useCreateTimeBlock();
|
|
const updateMutation = useUpdateTimeBlock();
|
|
const deleteMutation = useDeleteTimeBlock();
|
|
const isEditing = !!block;
|
|
|
|
const [title, setTitle] = useState('');
|
|
const [startTime, setStartTime] = useState('09:00');
|
|
const [endTime, setEndTime] = useState('10:00');
|
|
const [blockType, setBlockType] = useState<BlockType>(BlockType.Work);
|
|
const [status, setStatus] = useState<BlockStatus>(BlockStatus.Planned);
|
|
|
|
useEffect(() => {
|
|
if (block) {
|
|
setTitle(block.title);
|
|
setStartTime(block.startTime);
|
|
setEndTime(block.endTime);
|
|
setBlockType(block.blockType);
|
|
setStatus(block.status);
|
|
} else {
|
|
setTitle('');
|
|
setStartTime('09:00');
|
|
setEndTime('10:00');
|
|
setBlockType(BlockType.Work);
|
|
setStatus(BlockStatus.Planned);
|
|
}
|
|
}, [block, open]);
|
|
|
|
function handleSubmit(): void {
|
|
if (!title.trim()) return;
|
|
|
|
if (isEditing && block) {
|
|
const dto: UpdateTimeBlockDto = {
|
|
title: title.trim(),
|
|
startTime,
|
|
endTime,
|
|
blockType,
|
|
status,
|
|
};
|
|
updateMutation.mutate(
|
|
{ id: block.id, data: dto },
|
|
{ onSuccess: () => onClose() },
|
|
);
|
|
} else {
|
|
const dto: CreateTimeBlockDto = {
|
|
title: title.trim(),
|
|
date,
|
|
startTime,
|
|
endTime,
|
|
blockType,
|
|
};
|
|
createMutation.mutate(dto, { onSuccess: () => onClose() });
|
|
}
|
|
}
|
|
|
|
function handleDelete(): void {
|
|
if (!block) return;
|
|
if (!window.confirm(`Delete "${block.title}"?`)) return;
|
|
deleteMutation.mutate(block.id, { onSuccess: () => onClose() });
|
|
}
|
|
|
|
const isPending =
|
|
createMutation.isPending ||
|
|
updateMutation.isPending ||
|
|
deleteMutation.isPending;
|
|
|
|
return (
|
|
<Modal
|
|
open={open}
|
|
onClose={onClose}
|
|
title={isEditing ? 'Edit Time Block' : 'New Time Block'}
|
|
>
|
|
<FormField>
|
|
<FormLabel>Title</FormLabel>
|
|
<Input
|
|
value={title}
|
|
onChange={(e) => setTitle(e.target.value)}
|
|
placeholder="Block title"
|
|
/>
|
|
</FormField>
|
|
<FormField>
|
|
<FormLabel>Start Time</FormLabel>
|
|
<Input
|
|
type="time"
|
|
value={startTime}
|
|
onChange={(e) => setStartTime(e.target.value)}
|
|
/>
|
|
</FormField>
|
|
<FormField>
|
|
<FormLabel>End Time</FormLabel>
|
|
<Input
|
|
type="time"
|
|
value={endTime}
|
|
onChange={(e) => setEndTime(e.target.value)}
|
|
/>
|
|
</FormField>
|
|
<FormField>
|
|
<FormLabel>Type</FormLabel>
|
|
<Select
|
|
value={blockType}
|
|
onChange={(e) => setBlockType(e.target.value as BlockType)}
|
|
>
|
|
{Object.entries(BLOCK_TYPE_LABELS).map(([value, label]) => (
|
|
<option key={value} value={value}>
|
|
{label}
|
|
</option>
|
|
))}
|
|
</Select>
|
|
</FormField>
|
|
{isEditing && (
|
|
<FormField>
|
|
<FormLabel>Status</FormLabel>
|
|
<Select
|
|
value={status}
|
|
onChange={(e) => setStatus(e.target.value as BlockStatus)}
|
|
>
|
|
{Object.entries(BLOCK_STATUS_LABELS).map(([value, label]) => (
|
|
<option key={value} value={value}>
|
|
{label}
|
|
</option>
|
|
))}
|
|
</Select>
|
|
</FormField>
|
|
)}
|
|
<FormActions>
|
|
{isEditing && (
|
|
<Button
|
|
variant="ghost"
|
|
onClick={handleDelete}
|
|
disabled={isPending}
|
|
size="sm"
|
|
style={{ marginRight: 'auto' }}
|
|
>
|
|
Delete
|
|
</Button>
|
|
)}
|
|
<Button variant="ghost" onClick={onClose} size="sm">
|
|
Cancel
|
|
</Button>
|
|
<Button onClick={handleSubmit} disabled={isPending || !title.trim()} size="sm">
|
|
{isPending ? 'Saving...' : isEditing ? 'Update' : 'Create'}
|
|
</Button>
|
|
</FormActions>
|
|
</Modal>
|
|
);
|
|
}
|