feat: sidebar CTA removal, delete phase/task, public dashboard UX, chat panel

1. AdminSidebar: removed yellow "Genera preventivo" CTA (duplicated Preventivi tab nav item)
2. Delete phase/task: new deletePhase/deleteTask server actions with FK cascade and phase-status recompute; DeletePhaseTaskButton client component with window.confirm; trash icons in PhasesTab per phase and per task
3. Public dashboard: extend activeOffers query with offer_macros join (offer_name, offer_type); reorder sidebar 1°Offerte 2°Pagamenti 3°Documenti; OffersSection shows macro public_name as heading (never tier letter/internal_name); PaymentStatus gains totalLabel/overrideAmount/hideRows props — retainer=monthly label + no Acconto/Saldo rows
4. Offer editor: Descrizione textarea (nota interna) and Modalità toggle moved directly under title; Tags section now contains only Categoria/Ticket/Tipo/Obiettivo
5. Basecamp chat: API route supports entity_type="phase" with authorization scoping; comments scope in client-view broadened to include phaseIds and client_id (general); CommentsTab updated with phase/general entities; ChatProvider React context; ChatPanel fixed slide-in panel with FAB (bottom-right) and composer tag selector (Generale + phases); PhaseCard gets chat-bubble icon pre-tagging that phase; inline ChatSection block removed from dashboard main column

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 14:11:55 +02:00
parent 4b28f254ba
commit 64030afef3
14 changed files with 733 additions and 260 deletions
+62 -55
View File
@@ -6,6 +6,7 @@ import {
} from "@/app/admin/clients/[id]/actions";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { DeletePhaseTaskButton } from "@/components/admin/DeletePhaseTaskButton";
import type { ClientFullDetail } from "@/lib/admin-queries";
type Props = {
@@ -58,32 +59,35 @@ export async function PhasesTab({ phases, clientId }: Props) {
>
<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"
<div className="flex items-center gap-2">
<form
action={async (fd: FormData) => {
"use server";
await updatePhaseStatus(
phase.id,
clientId,
fd.get("status") as string
);
}}
className="flex items-center gap-2"
>
{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>
<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>
<DeletePhaseTaskButton type="phase" phaseId={phase.id} clientId={clientId} />
</div>
</div>
{/* Tasks */}
@@ -94,37 +98,40 @@ export async function PhasesTab({ phases, clientId }: Props) {
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"
<div className="flex items-center gap-1">
<form
action={async (fd: FormData) => {
"use server";
await updateTaskStatus(
task.id,
clientId,
fd.get("status") as string
);
}}
className="flex items-center gap-1"
>
{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>
<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>
<DeletePhaseTaskButton type="task" taskId={task.id} clientId={clientId} />
</div>
</div>
))}
</div>