```
✨ feat(Product.php): Ajoute relation DevisLine et méthodes associées en français. ✨ feat(DevisLine.php): Ajoute propriétés et relations pour ligne de devis en français. ✨ feat(DevisController.php): Intègre génération PDF et ajout de lignes de devis en français. 🎨 style: Améliore la mise en page et l'esthétique de l'interface admin en français. ✨ feat: Initialise TomSelect et gère les adresses client dans DevisManager en français. 🐛 fix: Corrige l'initialisation de TomSelect et la gestion des lignes répétées en français. ✅ test: Ajoute génération du bon pour accord et signature en français. ```
This commit is contained in:
@@ -38,99 +38,116 @@
|
||||
</div>
|
||||
|
||||
<div class="relative space-y-10">
|
||||
{# GRILLE À 3 COLONNES #}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{# SECTION ENTÊTE #}
|
||||
{% set input_class = "w-full bg-slate-900/60 border border-white/10 rounded-2xl px-5 py-4 text-sm text-white outline-none focus:border-blue-500/50 focus:bg-slate-900/90 transition-all duration-300" %}
|
||||
{% set label_class = "block text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] mb-3 ml-2" %}
|
||||
|
||||
{# COLONNE 1 : NUMÉRO #}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 items-start">
|
||||
<div class="space-y-3">
|
||||
{{ form_label(form.num) }}
|
||||
<div class="relative">
|
||||
{{ form_widget(form.num) }}
|
||||
</div>
|
||||
{{ form_label(form.num, null, {'label_attr': {'class': label_class}}) }}
|
||||
{{ form_widget(form.num, {'attr': {'class': input_class}}) }}
|
||||
</div>
|
||||
|
||||
{# COLONNE 2 : DATE #}
|
||||
<div class="space-y-3">
|
||||
{{ form_label(form.createA) }}
|
||||
<div class="relative">
|
||||
{{ form_widget(form.createA) }}
|
||||
</div>
|
||||
{{ form_label(form.createA, null, {'label_attr': {'class': label_class}}) }}
|
||||
{{ form_widget(form.createA, {'attr': {'class': input_class}}) }}
|
||||
</div>
|
||||
|
||||
{# COLONNE 3 : CLIENT #}
|
||||
<div class="space-y-3" is="devis-manager">
|
||||
{{ form_label(form.customer) }}
|
||||
{{ form_widget(form.customer) }}
|
||||
{{ form_label(form.customer, null, {'label_attr': {'class': label_class}}) }}
|
||||
{{ form_widget(form.customer, {'attr': {'class': input_class}}) }}
|
||||
</div>
|
||||
<div>
|
||||
<label for="billAddress">Adresse de facturation</label>
|
||||
<select id="billAddress" name="devis[ship_address]">
|
||||
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="shipAddress">Adresse de livraison</label>
|
||||
<select id="shipAddress" name="devis[ship_address]">
|
||||
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{# SECTION ADRESSES #}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<div class="space-y-3">
|
||||
<label for="billAddress" class="{{ label_class }}">Adresse de facturation</label>
|
||||
<select id="billAddress" name="devis[bill_address]" class="{{ input_class }}"></select>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<label for="shipAddress" class="{{ label_class }}">Adresse de livraison</label>
|
||||
<select id="shipAddress" name="devis[ship_address]" class="{{ input_class }}"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-white/5">
|
||||
|
||||
{# SECTION REPEATER #}
|
||||
<div class="form-repeater" data-component="repeater" is="repeat-line">
|
||||
<ol class="form-repeater__rows" data-ref="rows" tabindex="0">
|
||||
<li class="form-repeater__row" style="border-bottom: 1px solid white">
|
||||
<fieldset class="form-group form-group--horizontal">
|
||||
<div class="flex space-x-4">
|
||||
<div class="flex-1">
|
||||
<div class="form-field">
|
||||
<div class="mb-1">
|
||||
<label for="product" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Produit</label>
|
||||
<select data-load="product" type="text" name="lines[0][title]" id="lines[0][title]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="product" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Nombre de jour</label>
|
||||
<input type="number" step="1" name="lines[0][price]" id="lines[0][price]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="product" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Prix Ht</label>
|
||||
<input type="number" step="0.1" name="lines[0][price]" id="lines[0][price]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="product" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date de début</label>
|
||||
<input type="date" name="lines[0][price]" id="lines[0][price]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="product" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date de fin</label>
|
||||
<input type="date" name="lines[0][price]" id="lines[0][price]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<button
|
||||
class="w-full form-repeater__remove-button bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded"
|
||||
data-ref="removeButton"
|
||||
type="button"
|
||||
>
|
||||
Supprimer la ligne
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mb-6 px-4">
|
||||
<h4 class="text-sm font-black text-white uppercase tracking-widest">Détail des prestations</h4>
|
||||
</div>
|
||||
|
||||
<ol class="form-repeater__rows space-y-4" data-ref="rows">
|
||||
<li class="form-repeater__row group animate-in slide-in-from-right-5 duration-300">
|
||||
<fieldset class="backdrop-blur-md bg-white/5 border border-white/10 rounded-[2.5rem] p-8 hover:border-blue-500/30 transition-all shadow-xl">
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-5 items-end">
|
||||
|
||||
{# 1. PRODUIT #}
|
||||
<div class="lg:col-span-3">
|
||||
<label class="{{ label_class }}">Produit / Prestation</label>
|
||||
<select data-load="product" name="lines[0][product_id]" class="{{ input_class }}" required>
|
||||
<option value="">Sélectionner...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{# 2. DURÉE #}
|
||||
<div class="lg:col-span-1">
|
||||
<label class="{{ label_class }}">Jours</label>
|
||||
<input type="number" name="lines[0][days]" min="1" placeholder="1" class="{{ input_class }} [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" required />
|
||||
</div>
|
||||
|
||||
{# 3. PRIX HT J1 #}
|
||||
<div class="lg:col-span-2">
|
||||
<label class="{{ label_class }}">HT J1 (€)</label>
|
||||
<input type="number" step="0.01" name="lines[0][price_ht]" placeholder="0.00" class="{{ input_class }} text-right font-mono" required />
|
||||
</div>
|
||||
|
||||
{# 4. PRIX HT SUP #}
|
||||
<div class="lg:col-span-1">
|
||||
<label class="{{ label_class }}">HT Sup</label>
|
||||
<input type="number" step="0.01" name="lines[0][price_sup_ht]" placeholder="0.00" class="{{ input_class }} text-right font-mono" required />
|
||||
</div>
|
||||
|
||||
{# 5. DATES #}
|
||||
<div class="lg:col-span-2">
|
||||
<label class="{{ label_class }}">Date Début</label>
|
||||
<input type="date" name="lines[0][date_start]" class="{{ input_class }} [color-scheme:dark]" required />
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="{{ label_class }}">Date Fin</label>
|
||||
<input type="date" name="lines[0][date_end]" class="{{ input_class }} [color-scheme:dark]" required />
|
||||
</div>
|
||||
|
||||
{# 6. SUPPRIMER #}
|
||||
<div class="lg:col-span-1 flex justify-center pb-1">
|
||||
<button type="button" data-ref="removeButton" class="p-4 bg-red-500/10 hover:bg-red-500 text-red-500 hover:text-white rounded-2xl transition-all duration-300 shadow-lg hover:shadow-red-500/20">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</li>
|
||||
</ol>
|
||||
<button
|
||||
class="mt-2 form-repeater__add-button w-full bg-purple-600 hover:bg-purple-700 text-white px-3 py-1 rounded"
|
||||
data-ref="addButton"
|
||||
type="button"
|
||||
>
|
||||
+ Ajouter une ligne
|
||||
</button>
|
||||
|
||||
<div class="mt-6 px-4">
|
||||
<button type="button" data-ref="addButton" class="w-full py-4 border-2 border-dashed border-white/10 hover:border-purple-500/50 bg-white/5 hover:bg-purple-500/10 rounded-3xl text-purple-400 text-[10px] font-black uppercase tracking-[0.4em] transition-all flex items-center justify-center space-x-3 group">
|
||||
<span class="text-xl group-hover:rotate-90 transition-transform duration-300">+</span>
|
||||
<span>Ajouter une prestation</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{# BOUTON LARGEUR TOTALE #}
|
||||
<div class="pt-4">
|
||||
<button type="submit" class="w-full py-6 bg-blue-600 hover:bg-blue-500 text-white text-[11px] font-black uppercase tracking-[0.5em] rounded-2xl shadow-2xl shadow-blue-600/40 transition-all hover:scale-[1.005] active:scale-95 flex items-center justify-center group">
|
||||
<span>Valider et créer le devis</span>
|
||||
<svg class="w-5 h-5 ml-4 transform group-hover:translate-x-2 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
{# VALIDATION #}
|
||||
<div class="pt-8 px-4">
|
||||
<button type="submit" class="w-full py-6 bg-blue-600 hover:bg-blue-500 text-white text-[11px] font-black uppercase tracking-[0.5em] rounded-[2rem] shadow-2xl shadow-blue-600/30 transition-all hover:scale-[1.01] active:scale-95 flex items-center justify-center group">
|
||||
<span>Valider et enregistrer le devis</span>
|
||||
<svg class="w-5 h-5 ml-4 transform group-hover:translate-x-2 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7m0 0l-7 7m7-7H3"/>
|
||||
</svg>
|
||||
</button>
|
||||
@@ -139,28 +156,5 @@
|
||||
</div>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
label {
|
||||
@apply block text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] mb-3 ml-2 !important;
|
||||
}
|
||||
|
||||
input, select {
|
||||
@apply w-full bg-slate-900/60 border border-white/5 rounded-2xl px-6 py-5 text-sm text-white outline-none focus:border-blue-500/50 focus:bg-slate-900/80 transition-all duration-500 !important;
|
||||
}
|
||||
|
||||
input[readonly] {
|
||||
@apply border-white/5 bg-white/5 cursor-not-allowed text-slate-500 !important;
|
||||
}
|
||||
|
||||
select {
|
||||
@apply appearance-none cursor-pointer;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%233b82f6'%3E%3Cpath stroke-linecap='round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"%3E%3C/path%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 1.5rem center;
|
||||
background-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user