first commit
Some checks failed
Build and Deploy / build (push) Failing after 1m34s

This commit is contained in:
2025-11-23 03:24:27 +01:00
commit cc5b8b32bb
6193 changed files with 832271 additions and 0 deletions

563
app/wizard.jsx Normal file
View File

@@ -0,0 +1,563 @@
import React, { useState } from 'react';
import {
User,
Server,
Bot,
ShieldCheck,
Wrench,
FileText,
ArrowRight,
ArrowLeft,
Send,
CheckCircle,
AlertCircle,
Copy
} from 'lucide-react';
// --- CONFIGURAZIONE API ---
const API_ENDPOINT = "https://api.example.com/v1/requirements";
const API_KEY_HEADER = "X-Api-Key"; // Se necessario
const API_KEY_VALUE = ""; // Inserire chiave se necessaria
export default function App() {
const [step, setStep] = useState(0);
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitStatus, setSubmitStatus] = useState(null); // 'success', 'error'
const [payloadPreview, setPayloadPreview] = useState("");
// Stato unico per tutti i dati del form
const [formData, setFormData] = useState({
// Metadata
commerciale: '',
cliente: '',
data: new Date().toISOString().split('T')[0],
// Step 1: Gestionale
gestionale_obiettivo: '',
gestionale_utenti_interni: '',
gestionale_utenti_esterni: false,
gestionale_device: 'desktop', // desktop, web, app
gestionale_entita: '',
gestionale_migrazione: 'nessuna', // nessuna, excel, legacy
// Step 2: Automazioni
automazione_trigger: '',
automazione_azione: '',
automazione_software_a: '',
automazione_software_b: '',
automazione_frequenza: 'bassa',
// Step 3: Compliance
compliance_dati: [], // array di stringhe
compliance_retention: '',
compliance_hosting: 'cloud',
// Step 4: Manutenzione
manutenzione_tipo: 'standard',
manutenzione_budget: '',
manutenzione_deadline: '',
// Step 5: Extra
note_finali: ''
});
// Gestione input generica
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
if (type === 'checkbox' && name === 'compliance_dati') {
// Gestione array per checkbox multiple
let updatedList = [...formData.compliance_dati];
if (checked) {
updatedList.push(value);
} else {
updatedList = updatedList.filter((item) => item !== value);
}
setFormData({ ...formData, compliance_dati: updatedList });
} else if (type === 'checkbox') {
setFormData({ ...formData, [name]: checked });
} else {
setFormData({ ...formData, [name]: value });
}
};
// Navigazione
const nextStep = () => setStep((prev) => Math.min(prev + 1, steps.length - 1));
const prevStep = () => setStep((prev) => Math.max(prev - 1, 0));
// Invio Dati
const handleSubmit = async () => {
setIsSubmitting(true);
const payload = JSON.stringify(formData, null, 2);
setPayloadPreview(payload);
try {
// Simulazione o Invio Reale
const response = await fetch(API_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
[API_KEY_HEADER]: API_KEY_VALUE
},
body: payload
});
if (response.ok) {
setSubmitStatus('success');
} else {
// Fallback per demo (visto che example.com darà 404/405)
console.warn("API Error (Expected in demo):", response.status);
setSubmitStatus('error');
}
} catch (error) {
console.error("Network Error:", error);
setSubmitStatus('error');
} finally {
setIsSubmitting(false);
nextStep(); // Vai alla schermata finale
}
};
const copyToClipboard = () => {
navigator.clipboard.writeText(payloadPreview);
alert("Dati copiati negli appunti!");
};
// Definizione degli Step
const steps = [
{ id: 0, title: "Start", icon: User, color: "bg-blue-500" },
{ id: 1, title: "Gestionale", icon: Server, color: "bg-indigo-500" },
{ id: 2, title: "Automazione", icon: Bot, color: "bg-purple-500" },
{ id: 3, title: "GDPR", icon: ShieldCheck, color: "bg-red-500" },
{ id: 4, title: "Service", icon: Wrench, color: "bg-orange-500" },
{ id: 5, title: "Note", icon: FileText, color: "bg-gray-500" },
{ id: 6, title: "Fine", icon: Send, color: "bg-green-500" },
];
const currentStepData = steps[step];
return (
<div className="min-h-screen bg-gray-50 font-sans text-slate-800 flex flex-col">
{/* HEADER / PROGRESS BAR */}
<div className="bg-white shadow-sm sticky top-0 z-10">
<div className="max-w-lg mx-auto px-4 py-3">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-bold text-gray-400 uppercase tracking-wider">
Step {step + 1} di {steps.length}
</span>
<span className="text-xs font-semibold text-blue-600">
{currentStepData.title}
</span>
</div>
<div className="h-2 w-full bg-gray-200 rounded-full overflow-hidden">
<div
className={`h-full transition-all duration-300 ease-out ${currentStepData.color}`}
style={{ width: `${((step + 1) / steps.length) * 100}%` }}
></div>
</div>
</div>
</div>
{/* CONTENT AREA */}
<div className="flex-1 max-w-lg mx-auto w-full p-4 pb-24 overflow-y-auto">
{/* Titolo Dinamico */}
<div className="mb-6 flex items-center gap-3">
<div className={`p-3 rounded-xl text-white shadow-lg ${currentStepData.color}`}>
<currentStepData.icon size={24} />
</div>
<h1 className="text-2xl font-bold text-gray-900">
{currentStepData.title === "Start" && "Anagrafica"}
{currentStepData.title === "Gestionale" && "Sviluppo App"}
{currentStepData.title === "Automazione" && "Bot & Integrazioni"}
{currentStepData.title === "GDPR" && "Privacy & Dati"}
{currentStepData.title === "Service" && "Manutenzione"}
{currentStepData.title === "Note" && "Info Extra"}
{currentStepData.title === "Fine" && "Riepilogo"}
</h1>
</div>
{/* STEP 0: ANAGRAFICA */}
{step === 0 && (
<div className="space-y-4 animate-fadeIn">
<InputGroup label="Riferimento Commerciale" icon={User}>
<input
type="text"
name="commerciale"
value={formData.commerciale}
onChange={handleChange}
placeholder="Il tuo nome"
className="w-full p-3 bg-white border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
/>
</InputGroup>
<InputGroup label="Nome Cliente / Azienda" icon={User}>
<input
type="text"
name="cliente"
value={formData.cliente}
onChange={handleChange}
placeholder="Es. Rossi Srl"
className="w-full p-3 bg-white border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
/>
</InputGroup>
<InputGroup label="Data Incontro">
<input
type="date"
name="data"
value={formData.data}
onChange={handleChange}
className="w-full p-3 bg-white border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
/>
</InputGroup>
<div className="bg-blue-50 p-4 rounded-lg text-sm text-blue-800 border border-blue-100 mt-4">
💡 <strong>Info:</strong> Tutti i campi successivi sono facoltativi. Compila solo le sezioni pertinenti al progetto.
</div>
</div>
)}
{/* STEP 1: GESTIONALE */}
{step === 1 && (
<div className="space-y-5 animate-fadeIn">
<Label text="Obiettivo principale del software?" />
<textarea
name="gestionale_obiettivo"
value={formData.gestionale_obiettivo}
onChange={handleChange}
placeholder="Es. Gestire il magazzino e le spedizioni..."
className="w-full p-3 h-24 bg-white border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none resize-none"
></textarea>
<div className="grid grid-cols-2 gap-3">
<div>
<Label text="Utenti Interni" />
<input
type="number"
name="gestionale_utenti_interni"
value={formData.gestionale_utenti_interni}
onChange={handleChange}
placeholder="N."
className="w-full p-3 border rounded-lg"
/>
</div>
<div>
<Label text="Utenti Esterni?" />
<div className="flex items-center h-full">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
name="gestionale_utenti_esterni"
checked={formData.gestionale_utenti_esterni}
onChange={handleChange}
className="w-5 h-5 accent-indigo-600"
/>
<span className="text-gray-700">, accedono</span>
</label>
</div>
</div>
</div>
<Label text="Tipo di Dispositivo richiesto" />
<div className="grid grid-cols-3 gap-2">
<SelectCard
selected={formData.gestionale_device === 'desktop'}
onClick={() => setFormData({...formData, gestionale_device: 'desktop'})}
label="Solo PC"
/>
<SelectCard
selected={formData.gestionale_device === 'web'}
onClick={() => setFormData({...formData, gestionale_device: 'web'})}
label="Web Resp."
/>
<SelectCard
selected={formData.gestionale_device === 'app'}
onClick={() => setFormData({...formData, gestionale_device: 'app'})}
label="App Nativa"
/>
</div>
<Label text="Migrazione Dati Attuali" />
<select
name="gestionale_migrazione"
value={formData.gestionale_migrazione}
onChange={handleChange}
className="w-full p-3 bg-white border rounded-lg"
>
<option value="nessuna">Nessuna (Start from scratch)</option>
<option value="carta">Cartaceo / Mentale</option>
<option value="excel">Excel / Fogli Google</option>
<option value="legacy">Vecchio Gestionale (SQL/CSV)</option>
</select>
</div>
)}
{/* STEP 2: AUTOMAZIONE */}
{step === 2 && (
<div className="space-y-5 animate-fadeIn">
<div>
<Label text="Trigger (Quando parte?)" />
<input
type="text"
name="automazione_trigger"
value={formData.automazione_trigger}
onChange={handleChange}
placeholder="Es. Arrivo email, compilazione form..."
className="w-full p-3 border rounded-lg focus:ring-2 focus:ring-purple-500"
/>
</div>
<div>
<Label text="Azione (Cosa fa?)" />
<textarea
name="automazione_azione"
value={formData.automazione_azione}
onChange={handleChange}
placeholder="Es. Crea fattura e salvala in Drive..."
className="w-full p-3 h-20 border rounded-lg focus:ring-2 focus:ring-purple-500 resize-none"
/>
</div>
<div className="p-4 bg-purple-50 rounded-lg border border-purple-100">
<Label text="Software Coinvolti" />
<div className="space-y-3 mt-2">
<input
type="text"
name="automazione_software_a"
value={formData.automazione_software_a}
onChange={handleChange}
placeholder="Software A (es. Gmail)"
className="w-full p-2 border rounded bg-white"
/>
<input
type="text"
name="automazione_software_b"
value={formData.automazione_software_b}
onChange={handleChange}
placeholder="Software B (es. Zucchetti)"
className="w-full p-2 border rounded bg-white"
/>
</div>
<p className="text-xs text-purple-600 mt-2 italic">Chiedi sempre se hanno accesso API o admin.</p>
</div>
</div>
)}
{/* STEP 3: GDPR */}
{step === 3 && (
<div className="space-y-5 animate-fadeIn">
<Label text="Tipologia Dati Trattati (GDPR)" />
<div className="space-y-2">
{['Dati Comuni (Anagrafiche)', 'Dati Finanziari (IBAN/CC)', 'Dati Sensibili (Sanitari/Giudiziari)'].map((tipo) => (
<label key={tipo} className="flex items-center gap-3 p-3 border rounded-lg bg-white active:bg-gray-50">
<input
type="checkbox"
name="compliance_dati"
value={tipo}
checked={formData.compliance_dati.includes(tipo)}
onChange={handleChange}
className="w-5 h-5 accent-red-600"
/>
<span className="text-sm font-medium text-gray-700">{tipo}</span>
</label>
))}
</div>
<Label text="Hosting Preferito" />
<div className="grid grid-cols-2 gap-3">
<SelectCard
selected={formData.compliance_hosting === 'cloud'}
onClick={() => setFormData({...formData, compliance_hosting: 'cloud'})}
label="Cloud (SaaS)"
sub="Gestito da noi"
/>
<SelectCard
selected={formData.compliance_hosting === 'premise'}
onClick={() => setFormData({...formData, compliance_hosting: 'premise'})}
label="On-Premise"
sub="Server Cliente"
/>
</div>
</div>
)}
{/* STEP 4: MANUTENZIONE */}
{step === 4 && (
<div className="space-y-5 animate-fadeIn">
<Label text="Livello Service Level Agreement (SLA)" />
<select
name="manutenzione_tipo"
value={formData.manutenzione_tipo}
onChange={handleChange}
className="w-full p-3 bg-white border rounded-lg mb-4"
>
<option value="standard">Standard (Sicurezza + Backup)</option>
<option value="business">Business (Monitoraggio Automazioni)</option>
<option value="critical">Critical (H24 - Costo alto)</option>
</select>
<div className="grid grid-cols-2 gap-4">
<div>
<Label text="Budget Stimato" />
<input
type="text"
name="manutenzione_budget"
value={formData.manutenzione_budget}
onChange={handleChange}
placeholder="€ Range"
className="w-full p-3 border rounded-lg"
/>
</div>
<div>
<Label text="Deadline" />
<input
type="date"
name="manutenzione_deadline"
value={formData.manutenzione_deadline}
onChange={handleChange}
className="w-full p-3 border rounded-lg"
/>
</div>
</div>
</div>
)}
{/* STEP 5: NOTE FINALI */}
{step === 5 && (
<div className="space-y-4 animate-fadeIn">
<div className="bg-yellow-50 border border-yellow-200 p-4 rounded-lg flex gap-3 items-start">
<AlertCircle className="text-yellow-600 shrink-0" size={20} />
<p className="text-sm text-yellow-800">
Usa questo spazio per dettagli tecnici non coperti dal wizard, sensazioni sul cliente o vincoli particolari.
</p>
</div>
<textarea
name="note_finali"
value={formData.note_finali}
onChange={handleChange}
placeholder="Scrivi qui note libere..."
className="w-full p-4 h-64 bg-white border border-gray-300 rounded-lg focus:ring-2 focus:ring-gray-500 outline-none resize-none text-lg"
></textarea>
</div>
)}
{/* STEP 6: ESITO INVIO */}
{step === 6 && (
<div className="text-center space-y-6 animate-fadeIn pt-10">
{submitStatus === 'success' ? (
<div className="flex flex-col items-center gap-4">
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center">
<CheckCircle className="text-green-600" size={40} />
</div>
<h2 className="text-2xl font-bold text-green-700">Inviato con Successo!</h2>
<p className="text-gray-600">Il team tecnico ha ricevuto i requisiti.</p>
<button
onClick={() => window.location.reload()}
className="mt-8 text-blue-600 underline font-medium"
>
Nuova Raccolta
</button>
</div>
) : (
<div className="flex flex-col items-center gap-4">
<div className="w-20 h-20 bg-orange-100 rounded-full flex items-center justify-center">
<AlertCircle className="text-orange-600" size={40} />
</div>
<h2 className="text-xl font-bold text-gray-800">Invio API Fallito</h2>
<p className="text-sm text-gray-500 px-4">
L'API non è raggiungibile (normale in questa demo). <br/>
Copia il report qui sotto e invialo manualmente.
</p>
<div className="w-full bg-gray-800 rounded-lg p-4 text-left relative group">
<pre className="text-green-400 text-xs overflow-x-auto font-mono whitespace-pre-wrap break-all">
{payloadPreview}
</pre>
<button
onClick={copyToClipboard}
className="absolute top-2 right-2 bg-white/20 hover:bg-white/40 text-white p-2 rounded-md backdrop-blur-sm transition"
>
<Copy size={16} />
</button>
</div>
<button
onClick={copyToClipboard}
className="w-full bg-gray-900 text-white py-3 px-6 rounded-xl font-semibold flex items-center justify-center gap-2"
>
<Copy size={18} /> Copia JSON
</button>
</div>
)}
</div>
)}
</div>
{/* FOOTER NAVIGATION */}
{step < 6 && (
<div className="bg-white border-t border-gray-200 p-4 fixed bottom-0 w-full max-w-lg mx-auto left-0 right-0 z-20">
<div className="flex gap-3">
<button
onClick={prevStep}
disabled={step === 0}
className={`p-4 rounded-xl flex items-center justify-center transition-colors ${step === 0 ? 'text-gray-300 bg-gray-50' : 'text-gray-600 bg-gray-100 hover:bg-gray-200'}`}
>
<ArrowLeft size={24} />
</button>
{step === 5 ? (
<button
onClick={handleSubmit}
disabled={isSubmitting}
className="flex-1 bg-green-600 text-white font-bold text-lg rounded-xl shadow-lg shadow-green-200 hover:bg-green-700 active:scale-95 transition-all flex items-center justify-center gap-2"
>
{isSubmitting ? 'Invio...' : 'Invia al Tecnico'} <Send size={20} />
</button>
) : (
<button
onClick={nextStep}
className="flex-1 bg-blue-600 text-white font-bold text-lg rounded-xl shadow-lg shadow-blue-200 hover:bg-blue-700 active:scale-95 transition-all flex items-center justify-center gap-2"
>
Avanti <ArrowRight size={24} />
</button>
)}
</div>
</div>
)}
</div>
);
}
// --- UI COMPONENTS ---
function InputGroup({ label, icon: Icon, children }) {
return (
<div className="space-y-1">
<div className="flex items-center gap-2 text-gray-700 font-semibold text-sm mb-1">
{Icon && <Icon size={16} className="text-gray-400" />}
{label}
</div>
{children}
</div>
);
}
function Label({ text }) {
return <h3 className="text-gray-800 font-semibold text-sm mb-2">{text}</h3>;
}
function SelectCard({ selected, onClick, label, sub }) {
return (
<div
onClick={onClick}
className={`p-3 rounded-lg border-2 cursor-pointer transition-all text-center ${
selected
? 'border-blue-500 bg-blue-50 text-blue-700'
: 'border-gray-200 bg-white text-gray-600 hover:bg-gray-50'
}`}
>
<div className="font-bold text-sm">{label}</div>
{sub && <div className="text-xs opacity-75">{sub}</div>}
</div>
);
}