Files
clienthub/src/components/admin/tabs/PhasesTab.tsx
T
Simone Cavalli 59a46d37fa feat(02-03): build /admin/clients/[id] workspace with tabbed layout and all tab components
- Create /admin/clients/[id]/page.tsx — Server Component using Radix Tabs (Fasi & Task, Pagamenti, Documenti, Commenti)
- Create PhasesTab: phases list with add-phase form, task lists with add-task form, status selects for phases and tasks
- Create PaymentsTab: accepted_total editor (splits to 50% on each payment), payment status selects with paid_at on saldato
- Create DocumentsTab: add document (label + URL) form, document list with delete action
- Create CommentsTab: chronological comment display (admin vs cliente style), admin reply form with entity selector
- All mutations via inline Server Action closures bound to action= props; revalidatePath ensures fresh data
2026-05-15 21:16:10 +02:00

154 lines
4.5 KiB
TypeScript

import {
addPhase,
addTask,
updateTaskStatus,
updatePhaseStatus,
} from "@/app/admin/clients/[id]/actions";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import type { ClientFullDetail } from "@/lib/admin-queries";
type Props = {
phases: ClientFullDetail["phases"];
clientId: string;
};
const taskStatusOptions = [
{ value: "todo", label: "Da fare" },
{ value: "in_progress", label: "In corso" },
{ value: "done", label: "Fatto" },
];
const phaseStatusOptions = [
{ value: "upcoming", label: "In arrivo" },
{ value: "active", label: "Attiva" },
{ value: "done", label: "Completata" },
];
export async function PhasesTab({ phases, clientId }: Props) {
return (
<div className="space-y-6">
{/* Add phase form */}
<form
action={async (fd: FormData) => {
"use server";
await addPhase(clientId, fd);
}}
className="flex gap-2"
>
<Input
name="title"
placeholder="Nome nuova fase..."
className="max-w-xs"
required
/>
<Button type="submit" variant="outline" size="sm">
+ Fase
</Button>
</form>
{/* Phases list */}
{phases.length === 0 && (
<p className="text-sm text-gray-400">Nessuna fase ancora.</p>
)}
{phases.map((phase) => (
<div
key={phase.id}
className="border border-gray-200 rounded-lg p-4 bg-white"
>
<div className="flex items-center justify-between mb-3">
<h3 className="font-semibold text-gray-900">{phase.title}</h3>
<form
action={async (fd: FormData) => {
"use server";
await updatePhaseStatus(
phase.id,
clientId,
fd.get("status") as string
);
}}
className="flex items-center gap-2"
>
<select
name="status"
defaultValue={phase.status}
className="text-xs border border-gray-200 rounded px-2 py-1 bg-white"
>
{phaseStatusOptions.map((o) => (
<option key={o.value} value={o.value}>
{o.label}
</option>
))}
</select>
<Button type="submit" variant="ghost" size="sm" className="text-xs">
Salva
</Button>
</form>
</div>
{/* Tasks */}
<div className="space-y-2 mb-3">
{phase.tasks.map((task) => (
<div
key={task.id}
className="flex items-center justify-between pl-3 border-l-2 border-gray-100"
>
<span className="text-sm text-gray-800">{task.title}</span>
<form
action={async (fd: FormData) => {
"use server";
await updateTaskStatus(
task.id,
clientId,
fd.get("status") as string
);
}}
className="flex items-center gap-1"
>
<select
name="status"
defaultValue={task.status}
className="text-xs border border-gray-200 rounded px-2 py-1 bg-white"
>
{taskStatusOptions.map((o) => (
<option key={o.value} value={o.value}>
{o.label}
</option>
))}
</select>
<Button
type="submit"
variant="ghost"
size="sm"
className="text-xs px-1"
>
</Button>
</form>
</div>
))}
</div>
{/* Add task form */}
<form
action={async (fd: FormData) => {
"use server";
await addTask(phase.id, clientId, fd);
}}
className="flex gap-2 mt-2"
>
<Input
name="title"
placeholder="Nuovo task..."
className="text-sm max-w-xs"
required
/>
<Button type="submit" variant="ghost" size="sm" className="text-xs">
+ Task
</Button>
</form>
</div>
))}
</div>
);
}