diff --git a/src/app/admin/quotes/new/actions.ts b/src/app/admin/quotes/new/actions.ts deleted file mode 100644 index 0530a9c..0000000 --- a/src/app/admin/quotes/new/actions.ts +++ /dev/null @@ -1,3 +0,0 @@ -"use server"; - -export { createQuote } from "@/lib/quote-actions"; diff --git a/src/app/admin/quotes/new/page.tsx b/src/app/admin/quotes/new/page.tsx deleted file mode 100644 index 3ec49cf..0000000 --- a/src/app/admin/quotes/new/page.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Card } from "@/components/ui/card"; -import { QuoteBuilderForm } from "@/components/admin/quotes/QuoteBuilderForm"; -import { getAllClientsWithPayments } from "@/lib/admin-queries"; -import { getAllOfferMacrosWithMicros } from "@/lib/admin-queries"; - -export const revalidate = 0; - -export default async function QuoteBuilderPage() { - const [clientsData, offersData] = await Promise.all([ - getAllClientsWithPayments(), - getAllOfferMacrosWithMicros(), - ]); - - return ( -
-
-

Crea Preventivo

-

- Componi un preventivo per il cliente selezionando l'offerta e il prezzo -

-
- - - - -
- ); -} diff --git a/src/components/admin/leads/SendQuoteModal.tsx b/src/components/admin/leads/SendQuoteModal.tsx index 03fde23..69abc66 100644 --- a/src/components/admin/leads/SendQuoteModal.tsx +++ b/src/components/admin/leads/SendQuoteModal.tsx @@ -22,7 +22,6 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Loader2 } from "lucide-react"; const assignQuoteSchema = z.object({ @@ -33,7 +32,6 @@ const assignQuoteSchema = z.object({ export function SendQuoteModal({ leadId }: { leadId: string }) { const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); - const [tab, setTab] = useState<"existing" | "new">("existing"); const form = useForm({ resolver: zodResolver(assignQuoteSchema), @@ -72,58 +70,36 @@ export function SendQuoteModal({ leadId }: { leadId: string }) { Invia Preventivo al Lead - setTab(v as "existing" | "new")}> - - Preventivo Esistente - Crea Nuovo - - -
- - ( - - Token Preventivo - - - - - - )} - /> -

- Copia il token dal preventivo e incollalo qui. Il lead sarà aggiornato a - "Proposal Sent". -

- - - -
- - -

- Crea un nuovo preventivo per questo lead. Ti reindirizzeremo al builder. +

+ + ( + + Token Preventivo + + + + + + )} + /> +

+ Copia il token dal preventivo e incollalo qui. Il lead sarà aggiornato a + "Proposal Sent".

- - - + + ); diff --git a/src/components/admin/quotes/OfferSelector.tsx b/src/components/admin/quotes/OfferSelector.tsx deleted file mode 100644 index 0893eab..0000000 --- a/src/components/admin/quotes/OfferSelector.tsx +++ /dev/null @@ -1,46 +0,0 @@ -"use client"; - -import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectGroup, - SelectLabel, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { OfferMacro, OfferMicro } from "@/db/schema"; - -interface OfferSelectorProps { - macros: (OfferMacro & { micros: OfferMicro[] })[]; - value: string; - onValueChange: (value: string) => void; -} - -export function OfferSelector({ macros, value, onValueChange }: OfferSelectorProps) { - return ( -
- - -
- ); -} diff --git a/src/components/admin/quotes/PriceOverrideInput.tsx b/src/components/admin/quotes/PriceOverrideInput.tsx deleted file mode 100644 index c0484ab..0000000 --- a/src/components/admin/quotes/PriceOverrideInput.tsx +++ /dev/null @@ -1,63 +0,0 @@ -"use client"; - -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Check, X } from "lucide-react"; -import { useState } from "react"; - -interface PriceOverrideInputProps { - value: string; - onChange: (value: string) => void; - calculatedTotal?: number; -} - -export function PriceOverrideInput({ value, onChange, calculatedTotal }: PriceOverrideInputProps) { - const [isTouched, setIsTouched] = useState(false); - - const numValue = parseFloat(value) || 0; - const matches = calculatedTotal !== undefined && Math.abs(numValue - calculatedTotal) < 0.01; - - return ( -
- -
- onChange(e.target.value)} - onBlur={() => setIsTouched(true)} - className="pr-10" - required - /> - {isTouched && value && ( -
- {matches ? ( - - ) : ( - - )} -
- )} -
- - {calculatedTotal !== undefined && ( -

- Prezzo calcolato: €{calculatedTotal.toLocaleString("it-IT", { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - })} -

- )} - - {isTouched && value && !matches && ( -

- Attenzione: il valore inserito non corrisponde al totale calcolato (server verificherà) -

- )} -
- ); -} diff --git a/src/components/admin/quotes/QuoteBuilderForm.tsx b/src/components/admin/quotes/QuoteBuilderForm.tsx deleted file mode 100644 index 69af168..0000000 --- a/src/components/admin/quotes/QuoteBuilderForm.tsx +++ /dev/null @@ -1,210 +0,0 @@ -"use client"; - -import { useState, useCallback, useTransition } from "react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Card } from "@/components/ui/card"; -import { QuotePreview } from "./QuotePreview"; -import { OfferSelector } from "./OfferSelector"; -import { PriceOverrideInput } from "./PriceOverrideInput"; -import { createQuote, getOfferWithPhases } from "@/lib/quote-actions"; -import { OfferMacro, OfferMicro } from "@/db/schema"; -import { Check, Copy } from "lucide-react"; - -interface ClientOption { - id: string; - name: string; -} - -interface QuoteBuilderFormProps { - clients: ClientOption[]; - offerMacros: (OfferMacro & { micros: OfferMicro[] })[]; -} - -export function QuoteBuilderForm({ clients, offerMacros }: QuoteBuilderFormProps) { - const [isPending, startTransition] = useTransition(); - const [selectedClient, setSelectedClient] = useState(""); - const [selectedOffer, setSelectedOffer] = useState(""); - const [selectedTotal, setSelectedTotal] = useState(""); - const [currentOffer, setCurrentOffer] = useState(null); - const [successLink, setSuccessLink] = useState(null); - const [error, setError] = useState(null); - const [copied, setCopied] = useState(false); - - // Fetch offer details when selection changes - const handleOfferChange = useCallback((offerId: string) => { - setSelectedOffer(offerId); - setError(null); - startTransition(async () => { - const offer = await getOfferWithPhases(offerId); - setCurrentOffer(offer); - }); - }, []); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - setError(null); - setSuccessLink(null); - - startTransition(async () => { - const result = await createQuote({ - client_id: selectedClient, - offer_micro_id: selectedOffer, - accepted_total: selectedTotal, - }); - - if (result.success) { - setSuccessLink(result.publicLink || ""); - // Reset form - setSelectedClient(""); - setSelectedOffer(""); - setSelectedTotal(""); - setCurrentOffer(null); - } else { - setError(result.error || "Errore sconosciuto"); - } - }); - }; - - const handleCopyLink = async () => { - if (successLink) { - const fullUrl = `${window.location.origin}${successLink}`; - await navigator.clipboard.writeText(fullUrl); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } - }; - - if (successLink) { - return ( - -
-
- -
-

Preventivo creato

-

- Il preventivo è stato salvato e il link è pronto per essere condiviso -

-
- -
-

Link pubblico

-
- - {window.location.origin} - {successLink} - - -
-
- - -
- ); - } - - return ( -
- {/* LEFT COLUMN: FORM */} -
-
-

Crea Preventivo

- - {/* Client Select */} -
- - -
- - {/* Offer Select */} - {selectedClient && ( - - )} - - {/* Price Input */} - {selectedOffer && ( - - )} - - {/* Error Message */} - {error && ( -
-

{error}

-
- )} - - {/* Submit Button */} - - -
- - {/* RIGHT COLUMN: PREVIEW */} -
- -
-
- ); -} diff --git a/src/components/admin/quotes/QuotePreview.tsx b/src/components/admin/quotes/QuotePreview.tsx deleted file mode 100644 index 0c8c76c..0000000 --- a/src/components/admin/quotes/QuotePreview.tsx +++ /dev/null @@ -1,61 +0,0 @@ -"use client"; - -import { Card } from "@/components/ui/card"; -import { OfferMicro, OfferPhase } from "@/db/schema"; - -interface QuotePreviewProps { - offer: (OfferMicro & { phases: OfferPhase[] }) | null; - selectedTotal: number; -} - -export function QuotePreview({ offer, selectedTotal }: QuotePreviewProps) { - if (!offer) { - return ( -
-

Seleziona un'offerta per visualizzare l'anteprima

-
- ); - } - - return ( - -
-

{offer.public_name}

- {offer.transformation_promise && ( -

{offer.transformation_promise}

- )} -

- Durata: {offer.duration_months} mese{offer.duration_months !== 1 ? "i" : ""} -

-
- -
-

Fasi incluse

- {offer.phases && offer.phases.length > 0 ? ( - offer.phases.map((phase) => ( -
-

{phase.title}

- {phase.description && ( -

{phase.description}

- )} -
- )) - ) : ( -

Nessuna fase configurata

- )} -
- -
-
- Totale preventivo - - €{selectedTotal.toLocaleString("it-IT", { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - -
-

- Questo preventivo sarà visibile al cliente tramite link riservato -

-
-
- ); -} diff --git a/src/lib/admin-queries.ts b/src/lib/admin-queries.ts index 7e86f3c..a98eb6c 100644 --- a/src/lib/admin-queries.ts +++ b/src/lib/admin-queries.ts @@ -859,7 +859,6 @@ export async function getQuotesByClient(clientId: string): Promise { /** * Get all offer macros with their micros (ordered by sort_order) - * Used by: /admin/quotes/new form (OfferSelector dropdown) */ export async function getAllOfferMacrosWithMicros(): Promise< (OfferMacro & { micros: OfferMicro[] })[]