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:
+18
-4
@@ -1,6 +1,6 @@
|
||||
import { eq, inArray, asc, desc, sql } from "drizzle-orm";
|
||||
import { db } from "@/db";
|
||||
import { clients, projects, phases, tasks, deliverables, payments, documents, notes, comments, project_offers, offer_micros, offer_micro_services, offer_services, clientTranscripts } from "@/db/schema";
|
||||
import { clients, projects, phases, tasks, deliverables, payments, documents, notes, comments, project_offers, offer_micros, offer_micro_services, offer_services, offer_macros, clientTranscripts } from "@/db/schema";
|
||||
|
||||
/**
|
||||
* ClientView: Legacy shape used by ClientDashboard component.
|
||||
@@ -55,6 +55,8 @@ export interface ClientView {
|
||||
activeOffers?: Array<{
|
||||
id: string;
|
||||
public_name: string;
|
||||
offer_name: string; // offer_macros.public_name — macro-level name shown to client
|
||||
offer_type: string; // una_tantum | retainer
|
||||
cumulative_price: string;
|
||||
accepted_total: string | null;
|
||||
}>;
|
||||
@@ -125,6 +127,8 @@ export interface ProjectView {
|
||||
activeOffers?: Array<{
|
||||
id: string;
|
||||
public_name: string; // offer_micros.public_name — NEVER internal_name
|
||||
offer_name: string; // offer_macros.public_name — macro-level name shown to client
|
||||
offer_type: string; // una_tantum | retainer
|
||||
cumulative_price: string; // sum of assigned offer_services.price
|
||||
accepted_total: string | null;
|
||||
}>;
|
||||
@@ -293,12 +297,15 @@ export async function getProjectView(projectId: string): Promise<ProjectView | n
|
||||
const projectOfferRows = await db
|
||||
.select({
|
||||
id: project_offers.id,
|
||||
public_name: offer_micros.public_name, // EXPLICITLY public_name, never internal_name
|
||||
public_name: offer_micros.public_name, // EXPLICITLY public_name, never internal_name
|
||||
offer_name: offer_macros.public_name, // macro-level public name — NEVER internal_name
|
||||
offer_type: offer_macros.offer_type, // una_tantum | retainer
|
||||
accepted_total: project_offers.accepted_total,
|
||||
micro_id: project_offers.micro_id,
|
||||
})
|
||||
.from(project_offers)
|
||||
.innerJoin(offer_micros, eq(project_offers.micro_id, offer_micros.id))
|
||||
.innerJoin(offer_macros, eq(offer_micros.macro_id, offer_macros.id))
|
||||
.where(eq(project_offers.project_id, projectId));
|
||||
|
||||
// Cumulative price per micro (sum of assigned services)
|
||||
@@ -318,6 +325,8 @@ export async function getProjectView(projectId: string): Promise<ProjectView | n
|
||||
const activeOffers = projectOfferRows.map((o) => ({
|
||||
id: o.id,
|
||||
public_name: o.public_name,
|
||||
offer_name: o.offer_name,
|
||||
offer_type: o.offer_type,
|
||||
cumulative_price: cumulMap.get(o.micro_id) ?? "0",
|
||||
accepted_total: o.accepted_total ? String(o.accepted_total) : null,
|
||||
}));
|
||||
@@ -335,8 +344,13 @@ export async function getProjectView(projectId: string): Promise<ProjectView | n
|
||||
.where(eq(clientTranscripts.client_id, project.client_id))
|
||||
.orderBy(desc(clientTranscripts.call_date));
|
||||
|
||||
// Comments scoped to tasks and deliverables of this project
|
||||
const allEntityIds = [...taskIds, ...deliverablesRows.map((d) => d.id)];
|
||||
// Comments scoped to: general (client_id), phases, tasks, and deliverables of this project
|
||||
const allEntityIds = [
|
||||
project.client_id, // general messages entity_id = client_id
|
||||
...phaseIds, // phase-tagged messages
|
||||
...taskIds,
|
||||
...deliverablesRows.map((d) => d.id),
|
||||
];
|
||||
const commentsRows =
|
||||
allEntityIds.length === 0
|
||||
? []
|
||||
|
||||
Reference in New Issue
Block a user