feat(ui) : add site colors, dark mode toggle and card styling improvements
- Site color field with color picker in create/edit modals - Dark mode theme (mytheme-dark) with toggle in navbar - Stronger site color visibility on cards (gradient, top border, badges) - Bigger action buttons (btn-sm) on machine cards - White card backgrounds with proper dark mode support Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
221
app/components/machine/MachineCustomFieldsCard.vue
Normal file
221
app/components/machine/MachineCustomFieldsCard.vue
Normal file
@@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-lg">
|
||||
<div class="card-body space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="card-title">Champs personnalisés</h2>
|
||||
<p class="text-xs text-gray-500">
|
||||
Champs personnalisés propres à cette machine.
|
||||
</p>
|
||||
</div>
|
||||
<span v-if="visibleCustomFields.length" class="badge badge-outline">
|
||||
{{ visibleCustomFields.length }} champ{{ visibleCustomFields.length > 1 ? 's' : '' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- View mode: display values -->
|
||||
<template v-if="!isEditMode">
|
||||
<div v-if="visibleCustomFields.length" class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div
|
||||
v-for="field in visibleCustomFields"
|
||||
:key="field.customFieldValueId || field.id || field.name"
|
||||
class="form-control"
|
||||
>
|
||||
<label class="label">
|
||||
<span class="label-text text-sm">{{ field.name }}</span>
|
||||
</label>
|
||||
<div class="input input-bordered input-sm bg-base-200">
|
||||
{{ formatCustomFieldValue(field) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="text-xs text-gray-500">
|
||||
Aucun champ personnalisé défini pour cette machine.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<!-- Edit mode: definition management + value editing -->
|
||||
<template v-else>
|
||||
<p v-if="!customFields.length" class="text-xs text-gray-500">
|
||||
Aucun champ personnalisé défini.
|
||||
</p>
|
||||
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-for="(field, index) in customFields"
|
||||
:key="field.id || field.name || index"
|
||||
class="border border-base-200 rounded-md p-3 space-y-2"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="flex-1 space-y-2">
|
||||
<!-- Definition fields -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-2">
|
||||
<input
|
||||
:value="field.name"
|
||||
type="text"
|
||||
class="input input-bordered input-sm"
|
||||
placeholder="Nom du champ"
|
||||
@blur="handleDefinitionUpdate(field, 'name', ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<select
|
||||
:value="field.type || 'text'"
|
||||
class="select select-bordered select-sm"
|
||||
@change="handleDefinitionUpdate(field, 'type', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value="text">Texte</option>
|
||||
<option value="number">Nombre</option>
|
||||
<option value="select">Liste</option>
|
||||
<option value="boolean">Oui/Non</option>
|
||||
<option value="date">Date</option>
|
||||
</select>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm"
|
||||
:checked="!!field.required"
|
||||
@change="handleDefinitionUpdate(field, 'required', ($event.target as HTMLInputElement).checked)"
|
||||
/>
|
||||
Obligatoire
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options for select type -->
|
||||
<textarea
|
||||
v-if="(field.type || 'text') === 'select'"
|
||||
:value="field.optionsText || (Array.isArray(field.options) ? field.options.join('\n') : '')"
|
||||
class="textarea textarea-bordered textarea-sm h-20 w-full"
|
||||
placeholder="Option 1 Option 2"
|
||||
@blur="handleOptionsUpdate(field, ($event.target as HTMLTextAreaElement).value)"
|
||||
></textarea>
|
||||
|
||||
<!-- Value editing -->
|
||||
<div class="pt-1 border-t border-base-200">
|
||||
<label class="label py-0">
|
||||
<span class="label-text text-xs text-base-content/60">Valeur</span>
|
||||
</label>
|
||||
<input
|
||||
v-if="!field.type || field.type === 'text'"
|
||||
:value="field.value ?? ''"
|
||||
type="text"
|
||||
class="input input-bordered input-sm w-full"
|
||||
placeholder="Valeur..."
|
||||
@input="$emit('set-custom-field-value', field, ($event.target as HTMLInputElement).value)"
|
||||
@blur="$emit('update-custom-field', field)"
|
||||
/>
|
||||
<input
|
||||
v-else-if="field.type === 'number'"
|
||||
:value="field.value ?? ''"
|
||||
type="number"
|
||||
class="input input-bordered input-sm w-full"
|
||||
placeholder="Valeur..."
|
||||
@input="$emit('set-custom-field-value', field, ($event.target as HTMLInputElement).value)"
|
||||
@blur="$emit('update-custom-field', field)"
|
||||
/>
|
||||
<select
|
||||
v-else-if="field.type === 'select'"
|
||||
:value="field.value ?? ''"
|
||||
class="select select-bordered select-sm w-full"
|
||||
@change="onSelectChange(field, ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value="">Sélectionner...</option>
|
||||
<option
|
||||
v-for="option in field.options"
|
||||
:key="option"
|
||||
:value="option"
|
||||
>
|
||||
{{ option }}
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
v-else-if="field.type === 'boolean'"
|
||||
class="flex items-center gap-3 cursor-pointer py-1"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary toggle-sm"
|
||||
:checked="String(field.value).toLowerCase() === 'true'"
|
||||
@change="onBooleanChange(field, ($event.target as HTMLInputElement).checked)"
|
||||
>
|
||||
<span
|
||||
class="text-sm"
|
||||
:class="String(field.value).toLowerCase() === 'true' ? 'text-success font-medium' : 'text-base-content/60'"
|
||||
>
|
||||
{{ String(field.value).toLowerCase() === 'true' ? 'Oui' : 'Non' }}
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
v-else-if="field.type === 'date'"
|
||||
:value="field.value ?? ''"
|
||||
type="date"
|
||||
class="input input-bordered input-sm w-full"
|
||||
@input="$emit('set-custom-field-value', field, ($event.target as HTMLInputElement).value)"
|
||||
@blur="$emit('update-custom-field', field)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm btn-square flex-shrink-0 text-error"
|
||||
title="Supprimer ce champ"
|
||||
@click="$emit('delete-field', field.id || field.customFieldId)"
|
||||
>
|
||||
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm md:btn-md btn-primary"
|
||||
@click="$emit('add-field')"
|
||||
>
|
||||
Ajouter un champ personnalisé
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconLucideTrash from '~icons/lucide/trash'
|
||||
import { formatCustomFieldValue } from '~/shared/utils/customFieldUtils'
|
||||
|
||||
defineProps<{
|
||||
customFields: any[]
|
||||
visibleCustomFields: any[]
|
||||
isEditMode: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'set-custom-field-value': [field: any, value: unknown]
|
||||
'update-custom-field': [field: any]
|
||||
'add-field': []
|
||||
'delete-field': [fieldId: string]
|
||||
'update-field-definition': [fieldId: string, data: Record<string, unknown>]
|
||||
}>()
|
||||
|
||||
const handleDefinitionUpdate = (field: any, key: string, value: unknown) => {
|
||||
const fieldId = field.id || field.customFieldId
|
||||
if (!fieldId) return
|
||||
emit('update-field-definition', fieldId, { ...field, [key]: value })
|
||||
}
|
||||
|
||||
const handleOptionsUpdate = (field: any, raw: string) => {
|
||||
const fieldId = field.id || field.customFieldId
|
||||
if (!fieldId) return
|
||||
const options = raw.split('\n').map((o: string) => o.trim()).filter((o: string) => o.length > 0)
|
||||
emit('update-field-definition', fieldId, { ...field, options })
|
||||
}
|
||||
|
||||
const onSelectChange = (field: any, value: string) => {
|
||||
emit('set-custom-field-value', field, value)
|
||||
emit('update-custom-field', field)
|
||||
}
|
||||
|
||||
const onBooleanChange = (field: any, checked: boolean) => {
|
||||
emit('set-custom-field-value', field, checked ? 'true' : 'false')
|
||||
emit('update-custom-field', field)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user