Files
openclaw-backups/nextstep-features/treatment-milestones/components/TreatmentPlanForm.tsx

229 lines
7.2 KiB
TypeScript

'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { Calendar, Clock, Target } from 'lucide-react'
import { Card, Button, showToast } from '@/components/ui'
interface TreatmentPlanFormProps {
workspaceId: string
initialData?: {
title: string
totalCycles: number
startDate: string
cycleType: string
cycleDays: number
}
}
const CYCLE_TYPES = [
{ value: 'WEEKLY', label: 'Weekly', days: 7 },
{ value: 'BIWEEKLY', label: 'Every 2 weeks', days: 14 },
{ value: 'MONTHLY', label: 'Monthly', days: 30 },
{ value: 'CUSTOM', label: 'Custom', days: 0 },
]
export function TreatmentPlanForm({ workspaceId, initialData }: TreatmentPlanFormProps) {
const router = useRouter()
const [title, setTitle] = useState(initialData?.title || '')
const [totalCycles, setTotalCycles] = useState(initialData?.totalCycles || 12)
const [startDate, setStartDate] = useState(initialData?.startDate || '')
const [cycleType, setCycleType] = useState(initialData?.cycleType || 'WEEKLY')
const [customDays, setCustomDays] = useState(initialData?.cycleDays || 14)
const [saving, setSaving] = useState(false)
const selectedCycleType = CYCLE_TYPES.find(t => t.value === cycleType)
const cycleDays = cycleType === 'CUSTOM' ? customDays : (selectedCycleType?.days || 7)
// Calculate estimated end date
const estimatedEndDate = startDate
? new Date(new Date(startDate).getTime() + (cycleDays * totalCycles * 24 * 60 * 60 * 1000))
: null
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!title.trim()) {
showToast('Please enter a treatment name', 'error')
return
}
if (!startDate) {
showToast('Please select a start date', 'error')
return
}
setSaving(true)
try {
const response = await fetch(`/api/workspaces/${workspaceId}/treatment-plan`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: title.trim(),
totalCycles,
startDate,
cycleType,
cycleDays,
}),
})
if (!response.ok) throw new Error('Failed to create plan')
showToast('Treatment plan created!', 'success')
router.push('/treatment')
} catch {
showToast('Failed to create plan', 'error')
} finally {
setSaving(false)
}
}
return (
<Card>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Treatment Name */}
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
Treatment Name
</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="e.g., Grace's Chemotherapy Plan"
className="w-full px-3 py-2 border border-border rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-primary-200 focus:border-primary-500"
required
/>
</div>
{/* Total Cycles */}
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
<Target className="w-4 h-4 inline mr-1" />
Total Cycles/Sessions
</label>
<div className="flex items-center gap-4">
<input
type="range"
min="1"
max="50"
value={totalCycles}
onChange={(e) => setTotalCycles(parseInt(e.target.value))}
className="flex-1"
/>
<input
type="number"
min="1"
max="50"
value={totalCycles}
onChange={(e) => setTotalCycles(parseInt(e.target.value) || 1)}
className="w-20 px-3 py-2 border border-border rounded-lg text-center"
/>
</div>
<p className="text-xs text-secondary-500 mt-1">
Number of chemo sessions or treatment cycles
</p>
</div>
{/* Start Date */}
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
<Calendar className="w-4 h-4 inline mr-1" />
Start Date
</label>
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="w-full px-3 py-2 border border-border rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-primary-200 focus:border-primary-500"
required
/>
</div>
{/* Cycle Frequency */}
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
<Clock className="w-4 h-4 inline mr-1" />
How Often?
</label>
<div className="grid grid-cols-2 gap-2">
{CYCLE_TYPES.map((type) => (
<button
key={type.value}
type="button"
onClick={() => setCycleType(type.value)}
className={`p-3 rounded-lg border-2 text-left transition-all ${
cycleType === type.value
? 'border-primary-500 bg-primary-50'
: 'border-border hover:border-secondary-300'
}`}
>
<span className="font-medium text-secondary-900">{type.label}</span>
{type.value !== 'CUSTOM' && (
<span className="text-xs text-secondary-500 block">
Every {type.days} days
</span>
)}
</button>
))}
</div>
</div>
{/* Custom Days Input */}
{cycleType === 'CUSTOM' && (
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
Days Between Cycles
</label>
<input
type="number"
min="1"
max="90"
value={customDays}
onChange={(e) => setCustomDays(parseInt(e.target.value) || 1)}
className="w-full px-3 py-2 border border-border rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-primary-200 focus:border-primary-500"
/>
</div>
)}
{/* Estimated End Date */}
{estimatedEndDate && (
<div className="p-4 bg-primary-50 rounded-lg">
<p className="text-sm text-secondary-600">
Estimated completion:
</p>
<p className="font-semibold text-primary-700">
{estimatedEndDate.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</p>
</div>
)}
{/* Submit */}
<div className="flex gap-3">
<Button
type="button"
variant="secondary"
href="/treatment"
fullWidth
>
Cancel
</Button>
<Button
type="submit"
loading={saving}
fullWidth
>
Create Plan
</Button>
</div>
</form>
</Card>
)
}