216 lines
9.8 KiB
TypeScript
216 lines
9.8 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { pb } from '@services/pocketbase';
|
|
|
|
interface NewTicketProps {
|
|
onCancel: () => void;
|
|
onTicketCreated: () => void; // Rappel pour recharger la liste et revenir en arrière
|
|
}
|
|
|
|
export default function NewTicket({ onCancel, onTicketCreated }: NewTicketProps) {
|
|
const [category, setCategory] = useState<'Incident Critique' | 'Évolution' | 'Administratif'>('Incident Critique');
|
|
const [ci, Ci] = useState('general');
|
|
const [subject, setSubject] = useState('');
|
|
const [affectedAsset, setAffectedAsset] = useState('');
|
|
const [incidentTime, setIncidentTime] = useState('');
|
|
const [description, setDescription] = useState('');
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const [error, setError] = useState('');
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setError('');
|
|
setIsSubmitting(true);
|
|
|
|
try {
|
|
const currentUser = pb.authStore.record;
|
|
if (!currentUser) throw new Error("Utilisateur non authentifié");
|
|
|
|
// Construction de la description enrichie avec les métadonnées techniques
|
|
let fullDescription = description;
|
|
if (affectedAsset.trim()) {
|
|
fullDescription = `[Équipement concerné: ${affectedAsset}]\n` + fullDescription;
|
|
}
|
|
if (category === 'Incident Critique' && incidentTime) {
|
|
fullDescription = `[Heure de l'incident: ${new Date(incidentTime).toLocaleString('fr-FR')}]\n` + fullDescription;
|
|
}
|
|
|
|
// Enregistrement dans PocketBase
|
|
await pb.collection('aegis_tickets').create({
|
|
author: currentUser.id,
|
|
company: currentUser.company,
|
|
category,
|
|
subject,
|
|
description: fullDescription,
|
|
status: 'Ouvert',
|
|
});
|
|
|
|
onTicketCreated(); // Retourne à la liste et rafraîchit
|
|
} catch (err) {
|
|
console.error("Erreur lors de la création du ticket :", err);
|
|
setError("Impossible d'enregistrer le ticket sur le serveur sécurisé.");
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-50 font-sans text-slate-900 pb-12">
|
|
|
|
{/* Header */}
|
|
<header className="bg-white border-b border-slate-200 px-8 py-4 sticky top-0 z-10 shadow-sm">
|
|
<div className="max-w-7xl mx-auto flex items-center">
|
|
<button
|
|
type="button"
|
|
onClick={onCancel}
|
|
className="mr-4 p-2 text-slate-400 hover:text-slate-900 hover:bg-slate-50 rounded-full transition-colors"
|
|
>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
|
</svg>
|
|
</button>
|
|
<div>
|
|
<h1 className="text-xl font-bold text-slate-900 tracking-tight">Ouvrir un nouveau ticket</h1>
|
|
<p className="text-sm text-slate-500 mt-0.5">Qualification de votre requête ITSM</p>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Formulaire Principal */}
|
|
<main className="max-w-4xl mx-auto px-8 pt-8">
|
|
<form onSubmit={handleSubmit} className="space-y-8 bg-white rounded-xl shadow-sm border border-slate-200 p-8">
|
|
|
|
{error && (
|
|
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md text-sm font-medium">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{/* Section 1 : Qualification */}
|
|
<div>
|
|
<h2 className="text-base font-semibold text-slate-900 mb-4 border-b border-slate-100 pb-2">1. Qualification de la demande</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
<div>
|
|
<label htmlFor="category" className="block text-sm font-medium text-slate-700 mb-1">Type de demande</label>
|
|
<select
|
|
id="category"
|
|
value={category}
|
|
onChange={(e) => setCategory(e.target.value as any)}
|
|
className="block w-full rounded-md border border-slate-300 py-2.5 px-3 text-sm focus:border-blue-900 focus:outline-none focus:ring-1 focus:ring-blue-900 bg-white"
|
|
>
|
|
<option value="Incident Critique">Incident Critique (Panne, Dégradation)</option>
|
|
<option value="Évolution">Évolution (Ajout de ressources, Modification)</option>
|
|
<option value="Administratif">Administratif (Facturation, Contrat)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="ci" className="block text-sm font-medium text-slate-700 mb-1">Infrastructure concernée (CI)</label>
|
|
<select
|
|
id="ci"
|
|
value={ci}
|
|
onChange={(e) => Ci(e.target.value)}
|
|
className="block w-full rounded-md border border-slate-300 py-2.5 px-3 text-sm focus:border-blue-900 focus:outline-none focus:ring-1 focus:ring-blue-900 bg-white"
|
|
>
|
|
<option value="general">Général / Non spécifique</option>
|
|
<option value="px1">Serveur Proxmox Principal (Hyperviseur)</option>
|
|
<option value="fw1">Pare-feu Périphérique (WAN)</option>
|
|
<option value="db1">Base de données MariaDB</option>
|
|
</select>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
{/* Section 2 : Détails techniques & Actifs */}
|
|
<div>
|
|
<h2 className="text-base font-semibold text-slate-900 mb-4 border-b border-slate-100 pb-2">2. Détails techniques</h2>
|
|
<div className="space-y-6">
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label htmlFor="subject" className="block text-sm font-medium text-slate-700 mb-1">Sujet de l'intervention <span className="text-blue-900 font-bold">*</span></label>
|
|
<input
|
|
type="text"
|
|
id="subject"
|
|
required
|
|
value={subject}
|
|
onChange={(e) => setSubject(e.target.value)}
|
|
placeholder="Ex: Perte de paquets sur le lien WAN principal"
|
|
className="block w-full rounded-md border border-slate-300 py-2.5 px-3 text-sm focus:border-blue-900 focus:outline-none focus:ring-1 focus:ring-blue-900"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="affectedAsset" className="block text-sm font-medium text-slate-700 mb-1">
|
|
Équipement ou Produit concerné <span className="text-slate-400 font-normal">(Optionnel)</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="affectedAsset"
|
|
value={affectedAsset}
|
|
onChange={(e) => setAffectedAsset(e.target.value)}
|
|
placeholder="Ex: Ordinateur Direction, Imprimante X, etc."
|
|
className="block w-full rounded-md border border-slate-300 py-2.5 px-3 text-sm focus:border-blue-900 focus:outline-none focus:ring-1 focus:ring-blue-900"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{category === 'Incident Critique' && (
|
|
<div>
|
|
<label htmlFor="incidentTime" className="block text-sm font-medium text-slate-700 mb-1">
|
|
Heure exacte du début de l'incident <span className="text-red-500 font-bold">*</span>
|
|
</label>
|
|
<input
|
|
type="datetime-local"
|
|
id="incidentTime"
|
|
required
|
|
value={incidentTime}
|
|
onChange={(e) => setIncidentTime(e.target.value)}
|
|
className="block w-full max-w-md rounded-md border border-slate-300 py-2.5 px-3 text-sm focus:border-blue-900 focus:outline-none focus:ring-1 focus:ring-blue-900 bg-white"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<label htmlFor="description" className="block text-sm font-medium text-slate-700 mb-1">
|
|
Description détaillée <span className="text-blue-900 font-bold">*</span>
|
|
</label>
|
|
<textarea
|
|
id="description"
|
|
required
|
|
rows={6}
|
|
value={description}
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
placeholder="Décrivez précisément votre besoin ou les symptômes observés..."
|
|
className="block w-full rounded-md border border-slate-300 py-2.5 px-3 text-sm focus:border-blue-900 focus:outline-none focus:ring-1 focus:ring-blue-900 resize-y"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="pt-4 flex justify-end space-x-3 border-t border-slate-100">
|
|
<button
|
|
type="button"
|
|
onClick={onCancel}
|
|
className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
|
|
>
|
|
Annuler
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={isSubmitting}
|
|
className={`px-6 py-2 text-sm font-medium text-white rounded-lg transition-colors shadow-sm ${
|
|
category === 'Incident Critique' ? 'bg-red-600 hover:bg-red-700 focus:ring-red-600' : 'bg-blue-900 hover:bg-blue-800 focus:ring-blue-900'
|
|
} focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50`}
|
|
>
|
|
{isSubmitting ? 'Transmission en cours...' : 'Soumettre le ticket'}
|
|
</button>
|
|
</div>
|
|
|
|
</form>
|
|
</main>
|
|
</div>
|
|
);
|
|
} |