feat(02-04): add ApproveButton, CommentForm, CommentList; wire interactive elements into client dashboard
- ApproveButton: 'use client', POSTs to /api/client/approve with token + deliverableId, calls router.refresh(); shows immutable "Approvato il [date]" badge once approved_at is set - CommentForm: 'use client', POSTs to /api/client/comment, calls router.refresh() on success; clears textarea after submit - CommentList: presentational Server Component, labels client author as "Tu" and admin as "iamcavalli" - page.tsx: fetches all comments server-side (scoped to client's task/deliverable ids), passes token + comments to ClientDashboard; revalidate=0 ensures approvals and comments always fresh - client-dashboard.tsx: passes token + comments down to PhaseTimeline - phase-timeline.tsx: renders ApproveButton on each deliverable (pending/submitted/approved), CommentList + CommentForm below each deliverable and each task
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
import type { ClientView } from '@/lib/client-view';
|
||||
import type { Comment } from '@/db/schema';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ApproveButton } from './client/ApproveButton';
|
||||
import { CommentList } from './client/CommentList';
|
||||
import { CommentForm } from './client/CommentForm';
|
||||
|
||||
interface PhaseTimelineProps {
|
||||
phases: ClientView['phases'];
|
||||
token: string;
|
||||
comments: Comment[];
|
||||
}
|
||||
|
||||
function PhaseStatusIcon({ status }: { status: 'upcoming' | 'active' | 'done' }) {
|
||||
@@ -113,7 +119,7 @@ const phaseStatusStyle: Record<'upcoming' | 'active' | 'done', string> = {
|
||||
done: 'border-transparent bg-[#16a34a] text-white',
|
||||
};
|
||||
|
||||
export function PhaseTimeline({ phases }: PhaseTimelineProps) {
|
||||
export function PhaseTimeline({ phases, token, comments }: PhaseTimelineProps) {
|
||||
if (phases.length === 0) {
|
||||
return (
|
||||
<p className="text-sm text-[#999999] italic">
|
||||
@@ -122,6 +128,10 @@ export function PhaseTimeline({ phases }: PhaseTimelineProps) {
|
||||
);
|
||||
}
|
||||
|
||||
// Helper: filter pre-fetched comments by entity id
|
||||
const commentsFor = (entityId: string) =>
|
||||
comments.filter((c) => c.entity_id === entityId);
|
||||
|
||||
return (
|
||||
<div className="space-y-0">
|
||||
{phases.map((phase, index) => {
|
||||
@@ -174,7 +184,7 @@ export function PhaseTimeline({ phases }: PhaseTimelineProps) {
|
||||
Nessun task ancora configurato.
|
||||
</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
<ul className="space-y-4">
|
||||
{phase.tasks.map((task) => (
|
||||
<li key={task.id} className="flex items-start gap-2.5">
|
||||
<TaskStatusIcon status={task.status} />
|
||||
@@ -194,26 +204,46 @@ export function PhaseTimeline({ phases }: PhaseTimelineProps) {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Deliverable annidati */}
|
||||
{/* Deliverable annidati con ApproveButton + CommentiDeliverable */}
|
||||
{task.deliverables.length > 0 && (
|
||||
<ul className="mt-1.5 space-y-1">
|
||||
<ul className="mt-1.5 space-y-3">
|
||||
{task.deliverables.map((d) => (
|
||||
<li
|
||||
key={d.id}
|
||||
className="flex items-center justify-between gap-2 bg-[#f9f9f9] rounded px-2 py-1"
|
||||
className="bg-[#f9f9f9] rounded px-3 py-2"
|
||||
>
|
||||
<span className="text-xs text-[#666666] truncate">
|
||||
{d.title}
|
||||
</span>
|
||||
{d.status === 'approved' && (
|
||||
<Badge className="text-xs border-transparent bg-[#16a34a] text-white shrink-0">
|
||||
Approvato
|
||||
</Badge>
|
||||
)}
|
||||
<div className="flex items-center justify-between gap-2 mb-2">
|
||||
<span className="text-xs text-[#666666] truncate font-medium">
|
||||
{d.title}
|
||||
</span>
|
||||
{/* ApproveButton: shown for pending/submitted; shows date badge once approved */}
|
||||
{(d.status === 'pending' || d.status === 'submitted' || d.approved_at !== null) && (
|
||||
<ApproveButton
|
||||
deliverableId={d.id}
|
||||
token={token}
|
||||
approvedAt={d.approved_at}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* Comments on this deliverable */}
|
||||
<CommentList comments={commentsFor(d.id)} />
|
||||
<CommentForm
|
||||
token={token}
|
||||
entityType="deliverable"
|
||||
entityId={d.id}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{/* Comments on the task itself */}
|
||||
<CommentList comments={commentsFor(task.id)} />
|
||||
<CommentForm
|
||||
token={token}
|
||||
entityType="task"
|
||||
entityId={task.id}
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user