| |
|
| | import React, { useState } from 'react'; |
| | import { AppSettings } from '../types'; |
| | import { |
| | Save, |
| | Globe, |
| | User, |
| | Info, |
| | Database, |
| | FileSpreadsheet, |
| | Copy, |
| | CheckCircle2, |
| | Lock, |
| | ExternalLink, |
| | ShieldCheck, |
| | Hash, |
| | Download, |
| | |
| | AlertTriangle |
| | } from 'lucide-react'; |
| |
|
| | interface SettingsPanelProps { |
| | settings: AppSettings; |
| | onSave: (settings: AppSettings) => void; |
| | } |
| |
|
| | const SettingsPanel: React.FC<SettingsPanelProps> = ({ settings, onSave }) => { |
| | const [localSettings, setLocalSettings] = useState<AppSettings>(settings); |
| | const [copied, setCopied] = useState(false); |
| | const [activeTab, setActiveTab] = useState<'profile' | 'google' | 'info'>('profile'); |
| |
|
| | const googleScriptCode = ` |
| | /** |
| | * AUDITPRO UNIVERSAL SYNC SCRIPT |
| | * Copiez ce code dans Extensions > Apps Script de votre Google Sheet |
| | */ |
| | function doPost(e) { |
| | try { |
| | var payload = JSON.parse(e.postData.contents); |
| | var audit = payload.data; |
| | |
| | // On utilise l'ID envoyé par l'app pour cibler la bonne feuille |
| | var sheetId = audit.sheetId; |
| | var ss = sheetId ? SpreadsheetApp.openById(sheetId) : SpreadsheetApp.getActiveSpreadsheet(); |
| | var sheet = ss.getSheets()[0]; |
| | |
| | // En-têtes si feuille vide |
| | if (sheet.getLastRow() == 0) { |
| | sheet.appendRow(["DATE", "INSPECTEUR", "MODÈLE", "QUESTION", "RÉPONSE", "OBSERVATION", "PHOTOS (NB)"]); |
| | sheet.getRange(1, 1, 1, 7).setFontWeight("bold").setBackground("#4F46E5").setFontColor("white"); |
| | } |
| | |
| | audit.responses.forEach(function(resp) { |
| | sheet.appendRow([ |
| | new Date(audit.startedAt).toLocaleString(), |
| | audit.inspectorName, |
| | audit.checklistName, |
| | resp.itemLabel, |
| | resp.value, |
| | resp.comment || "", |
| | resp.photos ? resp.photos.length : 0 |
| | ]); |
| | }); |
| | |
| | return ContentService.createTextOutput("SUCCESS").setMimeType(ContentService.MimeType.TEXT); |
| | } catch(f) { |
| | return ContentService.createTextOutput("ERROR: " + f.message).setMimeType(ContentService.MimeType.TEXT); |
| | } |
| | } |
| | `.trim(); |
| |
|
| | const copyScript = () => { |
| | navigator.clipboard.writeText(googleScriptCode); |
| | setCopied(true); |
| | setTimeout(() => setCopied(false), 2000); |
| | }; |
| |
|
| | return ( |
| | <div className="space-y-6 pb-20 animate-in fade-in duration-500"> |
| | <div className="bg-white rounded-[3rem] shadow-2xl border border-slate-200 overflow-hidden"> |
| | {/* Navigation Tabs */} |
| | <div className="flex border-b border-slate-100 bg-slate-50/30 overflow-x-auto"> |
| | <button onClick={() => setActiveTab('profile')} className={`px-8 py-6 flex items-center gap-2 text-[11px] font-black uppercase tracking-widest transition-all border-b-2 ${activeTab === 'profile' ? 'border-indigo-600 text-indigo-600 bg-white' : 'border-transparent text-slate-400 hover:text-slate-600'}`}> |
| | <User size={16} /> Identité |
| | </button> |
| | <button onClick={() => setActiveTab('google')} className={`px-8 py-6 flex items-center gap-2 text-[11px] font-black uppercase tracking-widest transition-all border-b-2 ${activeTab === 'google' ? 'border-indigo-600 text-indigo-600 bg-white' : 'border-transparent text-slate-400 hover:text-slate-600'}`}> |
| | <Globe size={16} /> Synchro Google |
| | </button> |
| | <button onClick={() => setActiveTab('info')} className={`px-8 py-6 flex items-center gap-2 text-[11px] font-black uppercase tracking-widest transition-all border-b-2 ${activeTab === 'info' ? 'border-indigo-600 text-indigo-600 bg-white' : 'border-transparent text-slate-400 hover:text-slate-600'}`}> |
| | <Database size={16} /> Mon Espace |
| | </button> |
| | </div> |
| | |
| | <div className="p-8 sm:p-12"> |
| | {activeTab === 'profile' && ( |
| | <div className="space-y-8 animate-in slide-in-from-left-4 duration-300"> |
| | <div className="space-y-3"> |
| | <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest block">Signature Inspecteur</label> |
| | <input |
| | type="text" |
| | value={localSettings.inspectorName} |
| | onChange={(e) => setLocalSettings({...localSettings, inspectorName: e.target.value})} |
| | placeholder="Votre nom complet" |
| | className="w-full border-2 border-slate-100 rounded-2xl p-5 text-slate-800 font-bold focus:border-indigo-600 outline-none bg-slate-50/50 transition-all text-lg" |
| | /> |
| | <p className="text-[10px] text-slate-400 font-bold italic italic">Ce nom sera gravé dans chaque audit que vous réaliserez.</p> |
| | </div> |
| | </div> |
| | )} |
| | |
| | {activeTab === 'google' && ( |
| | <div className="space-y-10 animate-in slide-in-from-left-4 duration-300"> |
| | <div className="p-8 bg-indigo-600 rounded-[2rem] text-white shadow-xl shadow-indigo-100 flex items-start gap-6"> |
| | <div className="bg-white/20 p-4 rounded-2xl backdrop-blur-md"> |
| | <ShieldCheck size={32} /> |
| | </div> |
| | <div> |
| | <h4 className="text-xl font-black mb-2">Liaison de Données</h4> |
| | <p className="text-sm text-indigo-100/90 leading-relaxed font-medium"> |
| | Ces identifiants sont strictement personnels. Ils permettent à l'application de savoir exactement dans quelle Google Sheet envoyer vos rapports. |
| | </p> |
| | </div> |
| | </div> |
| | |
| | <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> |
| | <div className="space-y-3"> |
| | <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest flex items-center gap-2"> |
| | <Hash size={14} /> ID de la Google Sheet |
| | </label> |
| | <input |
| | type="text" |
| | value={localSettings.sheetId} |
| | onChange={(e) => setLocalSettings({...localSettings, sheetId: e.target.value})} |
| | placeholder="Ex: 1bc-DEFgH..." |
| | className="w-full border-2 border-slate-100 rounded-2xl p-5 text-slate-700 font-mono text-sm outline-none bg-slate-50 focus:border-indigo-600 transition-all" |
| | /> |
| | </div> |
| | <div className="space-y-3"> |
| | <label className="text-[10px] font-black text-slate-400 uppercase tracking-widest flex items-center gap-2"> |
| | <Globe size={14} /> URL du Script (Web App) |
| | </label> |
| | <input |
| | type="url" |
| | value={localSettings.webhookUrl} |
| | onChange={(e) => setLocalSettings({...localSettings, webhookUrl: e.target.value})} |
| | placeholder="https://script.google.com/macros/s/..." |
| | className="w-full border-2 border-slate-100 rounded-2xl p-5 text-slate-700 font-mono text-sm outline-none bg-slate-50 focus:border-indigo-600 transition-all" |
| | /> |
| | </div> |
| | </div> |
| | |
| | <div className="space-y-5 pt-4"> |
| | <div className="flex items-center justify-between"> |
| | <h4 className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Script Apps Script Requis</h4> |
| | <button onClick={copyScript} className={`flex items-center gap-2 px-5 py-2.5 rounded-xl text-xs font-black transition-all shadow-sm ${copied ? 'bg-emerald-500 text-white' : 'bg-slate-100 text-slate-600 hover:bg-slate-200'}`}> |
| | {copied ? <CheckCircle2 size={16} /> : <Copy size={16} />} {copied ? 'Copié !' : 'Copier le script'} |
| | </button> |
| | </div> |
| | <div className="bg-slate-900 rounded-[1.5rem] p-6 relative group"> |
| | <pre className="text-[10px] text-emerald-400 font-mono overflow-x-auto max-h-56 leading-relaxed whitespace-pre-wrap"> |
| | {googleScriptCode} |
| | </pre> |
| | </div> |
| | </div> |
| | </div> |
| | )} |
| | |
| | {activeTab === 'info' && ( |
| | <div className="space-y-8 animate-in slide-in-from-left-4 duration-300"> |
| | <div className="p-10 bg-slate-50 rounded-[2.5rem] border border-slate-100 space-y-6"> |
| | <div className="flex items-center gap-4 text-indigo-600"> |
| | <Database size={32} /> |
| | <h4 className="text-2xl font-black tracking-tight">Souveraineté Totale</h4> |
| | </div> |
| | <p className="text-sm text-slate-600 leading-relaxed font-bold"> |
| | Votre espace contient tout votre ADN professionnel : modèles, audits et réglages cloud. |
| | L'application ne stocke rien en ligne de son côté. |
| | </p> |
| | |
| | <div className="space-y-4 pt-4 border-t border-slate-200"> |
| | <div className="flex items-start gap-3"> |
| | <div className="bg-amber-100 text-amber-600 p-2 rounded-lg mt-1"><AlertTriangle size={16} /></div> |
| | <p className="text-xs text-slate-500 font-medium leading-relaxed"> |
| | Chaque fois que vous changez d'ordinateur ou que vous videz votre navigateur, vous devez importer votre fichier <code>.auditpro</code>. |
| | </p> |
| | </div> |
| | <div className="flex items-start gap-3"> |
| | <div className="bg-indigo-100 text-indigo-600 p-2 rounded-lg mt-1"><Download size={16} /></div> |
| | <p className="text-xs text-slate-500 font-medium leading-relaxed"> |
| | Pensez à sauvegarder votre "Clé" régulièrement pour ne perdre aucun audit récent. |
| | </p> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | )} |
| | |
| | <div className="mt-12 pt-10 border-t border-slate-100 flex justify-end"> |
| | <button |
| | onClick={() => onSave(localSettings)} |
| | className="bg-indigo-600 text-white px-12 py-5 rounded-2xl hover:bg-indigo-700 shadow-2xl shadow-indigo-100 font-black transition-all flex items-center gap-3 active:scale-95" |
| | > |
| | <Save size={20} /> ENREGISTRER L'ESPACE |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | ); |
| | }; |
| |
|
| | export default SettingsPanel; |
| |
|