40227-vm/frontend/src/components/classroom-support/ClassroomSupportManagementPanel.tsx
2026-06-17 21:45:57 +02:00

224 lines
7.8 KiB
TypeScript

import { Plus, Save, X } from 'lucide-react';
import type {
ClassroomStrategyDraft,
ClassroomSupportPage,
} from '@/business/classroom-support/types';
import { Button } from '@/components/ui/button';
import { ImageUpload } from '@/components/common/ImageUpload';
import { Input } from '@/components/ui/input';
import { NativeSelect } from '@/components/ui/native-select';
import { Textarea } from '@/components/ui/textarea';
import {
CLASSROOM_SUPPORT_AGE_FILTERS,
CLASSROOM_SUPPORT_CATEGORY_FILTERS,
CLASSROOM_SUPPORT_ZONE_FILTERS,
} from '@/shared/constants/classroomSupport';
import type { Strategy } from '@/shared/types/app';
interface ClassroomSupportManagementPanelProps {
readonly page: ClassroomSupportPage;
}
type DraftField = keyof ClassroomStrategyDraft;
function toStrategyCategory(value: string): Strategy['category'] {
switch (value) {
case 'transition':
case 'sensory':
case 'communication':
case 'behavior':
case 'social':
return value;
case 'visual-support':
default:
return 'visual-support';
}
}
function toStrategyAgeGroup(value: string): Strategy['ageGroup'] {
switch (value) {
case 'K-2':
case '3-5':
case '6-8':
return value;
case 'All':
default:
return 'All';
}
}
function toStrategyZone(value: string): Strategy['zone'] {
switch (value) {
case 'blue':
case 'yellow':
case 'red':
return value;
case 'green':
default:
return 'green';
}
}
export function ClassroomSupportManagementPanel({ page }: ClassroomSupportManagementPanelProps) {
if (!page.canManageStrategies) {
return null;
}
const draft = page.strategyDraft;
function updateTextField(field: DraftField, value: string) {
page.updateStrategyDraft({ [field]: value });
}
return (
<section className="rounded-2xl border border-slate-700/50 bg-slate-900/45 overflow-hidden">
<div className="flex flex-col gap-3 border-b border-slate-700/50 px-5 py-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h3 className="text-lg font-semibold text-white">Manage Strategy Cards</h3>
<p className="text-sm text-slate-400">Organization-level classroom support content</p>
</div>
<Button
type="button"
onClick={page.startCreateStrategy}
disabled={page.isSavingStrategies}
leadingIcon={<Plus size={16} />}
className="bg-emerald-600 hover:bg-emerald-500 text-white"
>
Add Strategy
</Button>
</div>
<div className="space-y-5 p-5">
{draft && (
<div className="rounded-xl border border-slate-700/60 bg-slate-950/35 p-4">
<div className="mb-4 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<h4 className="text-base font-semibold text-white">
{page.strategyDraftMode === 'create' ? 'New Strategy' : 'Edit Strategy'}
</h4>
<Button
type="button"
variant="ghost"
size="sm"
onClick={page.cancelStrategyDraft}
leadingIcon={<X size={16} />}
>
Cancel
</Button>
</div>
<div className="grid gap-4 lg:grid-cols-2">
<label className="space-y-2 text-sm font-medium text-slate-300">
Title
<Input
value={draft.title}
onChange={(event) => updateTextField('title', event.target.value)}
placeholder="Strategy title"
/>
</label>
<ImageUpload
value={draft.image || null}
onChange={(privateUrl) => page.updateStrategyDraft({ image: privateUrl ?? '' })}
table="classroom-support"
field="strategy-images"
label="Strategy image"
/>
<label className="space-y-2 text-sm font-medium text-slate-300 lg:col-span-2">
Description
<Textarea
value={draft.description}
onChange={(event) => updateTextField('description', event.target.value)}
placeholder="Short card description"
/>
</label>
<label className="space-y-2 text-sm font-medium text-slate-300 lg:col-span-2">
Implementation Tip
<Textarea
value={draft.implementationTip}
onChange={(event) => updateTextField('implementationTip', event.target.value)}
placeholder="Practical implementation detail"
/>
</label>
<label className="space-y-2 text-sm font-medium text-slate-300">
Category
<NativeSelect
value={draft.category}
onChange={(event) => page.updateStrategyDraft({ category: toStrategyCategory(event.target.value) })}
>
{CLASSROOM_SUPPORT_CATEGORY_FILTERS
.filter((category) => category.value !== 'all')
.map((category) => (
<option key={category.value} value={category.value}>
{category.label}
</option>
))}
</NativeSelect>
</label>
<label className="space-y-2 text-sm font-medium text-slate-300">
Age Group
<NativeSelect
value={draft.ageGroup}
onChange={(event) => page.updateStrategyDraft({ ageGroup: toStrategyAgeGroup(event.target.value) })}
>
{CLASSROOM_SUPPORT_AGE_FILTERS
.filter((age) => age.value !== 'all')
.map((age) => (
<option key={age.value} value={age.value}>
{age.label}
</option>
))}
</NativeSelect>
</label>
<label className="space-y-2 text-sm font-medium text-slate-300">
Zone
<NativeSelect
value={draft.zone}
onChange={(event) => page.updateStrategyDraft({ zone: toStrategyZone(event.target.value) })}
>
{CLASSROOM_SUPPORT_ZONE_FILTERS
.filter((zone) => zone.value !== 'all')
.map((zone) => (
<option key={zone.value} value={zone.value}>
{zone.label}
</option>
))}
</NativeSelect>
</label>
</div>
{page.strategyDraftError && (
<p className="mt-4 rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm font-medium text-red-300">
{page.strategyDraftError}
</p>
)}
<div className="mt-5 flex justify-end">
<Button
type="button"
onClick={page.saveStrategyDraft}
loading={page.isSavingStrategies}
leadingIcon={<Save size={16} />}
className="bg-blue-600 hover:bg-blue-500 text-white"
>
Save Strategy
</Button>
</div>
</div>
)}
{Boolean(page.strategyManagementError) && (
<p className="rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm font-medium text-red-300">
Classroom support cards could not be saved.
</p>
)}
{page.strategySaveMessage && (
<p className="rounded-lg border border-emerald-500/30 bg-emerald-500/10 px-3 py-2 text-sm font-medium text-emerald-300">
{page.strategySaveMessage}
</p>
)}
</div>
</section>
);
}