Files
eveAI/eveai_chat_client/static/assets/vue-components/FormField.vue
2025-07-20 19:31:55 +02:00

417 lines
12 KiB
Vue

<template>
<div class="form-field" :class="{ 'radio-field': fieldType === 'radio' }" style="margin-bottom: 15px;">
<!-- Label voor alle velden behalve boolean/checkbox die een speciale behandeling krijgen -->
<label v-if="fieldType !== 'checkbox'" :for="fieldId" class="field-label" :class="{ 'tooltip-label': description }">
{{ field.name }}
<span v-if="field.required" class="required" style="color: #d93025; margin-left: 2px;">*</span>
<!-- Tooltip voor description -->
<span v-if="description" class="tooltip-content">{{ description }}</span>
</label>
<!-- Container voor input velden -->
<div style="width: 100%;">
<!-- Context informatie indien aanwezig -->
<div v-if="field.context" class="field-context" style="margin-bottom: 8px; color: #666; background-color: #f8f9fa; padding: 8px; border-radius: 4px;">
{{ field.context }}
</div>
<!-- Tekstinvoer (string/str) -->
<input
v-if="fieldType === 'text'"
:id="fieldId"
type="text"
v-model="value"
:required="field.required"
:placeholder="field.placeholder || ''"
:title="description"
style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ddd; box-sizing: border-box;"
>
<!-- Numerieke invoer (int/float) -->
<input
v-if="fieldType === 'number'"
:id="fieldId"
type="number"
v-model.number="value"
:required="field.required"
:step="stepValue"
:placeholder="field.placeholder || ''"
:title="description"
style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ddd; box-sizing: border-box;"
>
<!-- Tekstvlak (text) -->
<textarea
v-if="fieldType === 'textarea'"
:id="fieldId"
v-model="value"
:required="field.required"
:rows="field.rows || 3"
:placeholder="field.placeholder || ''"
:title="description"
style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ddd; box-sizing: border-box; resize: vertical;"
></textarea>
<!-- Selectielijst (enum) -->
<select
v-if="fieldType === 'select'"
:id="fieldId"
v-model="value"
:required="field.required"
:title="description"
style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ddd; box-sizing: border-box;"
>
<option value="">Selecteer een optie</option>
<option
v-for="option in (field.allowedValues || field.allowed_values || [])"
:key="option"
:value="option"
>
{{ option }}
</option>
</select>
<!-- Radio buttons (options) -->
<div v-if="fieldType === 'radio'" class="radio-group">
<label
v-for="option in (field.allowedValues || field.allowed_values || [])"
:key="option"
class="radio-label"
>
<input
type="radio"
:name="fieldId"
:value="option"
v-model="value"
:required="field.required"
>
<span>{{ option }}</span>
</label>
</div>
<!-- Checkbox (boolean) -->
<div v-if="fieldType === 'checkbox'" class="checkbox-container" style="display: flex; align-items: center;">
<label class="checkbox-label" style="display: flex; align-items: center; cursor: pointer;">
<input
type="checkbox"
:id="fieldId"
v-model="value"
:required="field.required"
style="margin-right: 8px;"
>
<span class="checkbox-text">{{ field.name }}</span>
<span v-if="field.required" class="required" style="color: #d93025; margin-left: 2px;">*</span>
</label>
</div>
<!-- Bestandsupload -->
<input
v-if="fieldType === 'file'"
:id="fieldId"
type="file"
@change="handleFileUpload"
:required="field.required"
:accept="field.accept || '*'"
:title="description"
style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ddd; box-sizing: border-box;"
>
<!-- Datum invoer -->
<input
v-if="fieldType === 'date'"
:id="fieldId"
type="date"
v-model="value"
:required="field.required"
:title="description"
style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ddd; box-sizing: border-box;"
>
<!-- Tijd invoer -->
<input
v-if="fieldType === 'time'"
:id="fieldId"
type="time"
v-model="value"
:required="field.required"
:title="description"
style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ddd; box-sizing: border-box;"
>
<!-- Email invoer -->
<input
v-if="fieldType === 'email'"
:id="fieldId"
type="email"
v-model="value"
:required="field.required"
:placeholder="field.placeholder || ''"
:title="description"
style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ddd; box-sizing: border-box;"
>
<!-- URL invoer -->
<input
v-if="fieldType === 'url'"
:id="fieldId"
type="url"
v-model="value"
:required="field.required"
:placeholder="field.placeholder || ''"
:title="description"
style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #ddd; box-sizing: border-box;"
>
</div>
</div>
</template>
<script>
export default {
name: 'FormField',
props: {
field: {
type: Object,
required: true,
validator: (field) => {
return field.name && field.type;
}
},
fieldId: {
type: String,
required: true
},
modelValue: {
default: null
}
},
emits: ['update:modelValue'],
computed: {
value: {
get() {
// Gebruik default waarde als modelValue undefined is
if (this.modelValue === undefined || this.modelValue === null) {
if (this.field.type === 'boolean') {
return this.field.default === true;
}
return this.field.default !== undefined ? this.field.default : '';
}
return this.modelValue;
},
set(value) {
// Voorkom emit als de waarde niet is veranderd
if (JSON.stringify(value) !== JSON.stringify(this.modelValue)) {
this.$emit('update:modelValue', value);
}
}
},
fieldType() {
// Map Python types naar HTML input types
const typeMap = {
'str': 'text',
'string': 'text',
'int': 'number',
'integer': 'number',
'float': 'number',
'text': 'textarea',
'enum': 'select',
'options': 'radio',
'boolean': 'checkbox',
'file': 'file',
'date': 'date',
'time': 'time',
'email': 'email',
'url': 'url'
};
return typeMap[this.field.type] || this.field.type;
},
stepValue() {
return this.field.type === 'float' ? 'any' : 1;
},
description() {
return this.field.description || '';
}
},
methods: {
handleFileUpload(event) {
const file = event.target.files[0];
if (file) {
this.value = file;
}
}
}
};
</script>
<style scoped>
/* Form field layout */
.form-field {
margin-bottom: 15px;
display: grid;
grid-template-columns: 35% 65%;
align-items: start;
gap: 10px;
}
/* Radio field special layout - full width for better alignment */
.form-field.radio-field {
display: block;
}
.form-field.radio-field .field-label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
.form-field.radio-field .field-context {
margin-left: 0;
margin-bottom: 12px;
}
/* Field label styling */
.field-label {
margin-right: 10px;
font-weight: 500;
position: relative;
}
/* Tooltip functionality */
.tooltip-label {
cursor: help;
}
.tooltip-content {
visibility: hidden;
opacity: 0;
position: absolute;
z-index: 1000;
bottom: 125%;
left: 0;
background-color: #333;
color: white;
text-align: left;
padding: 8px 12px;
border-radius: 6px;
font-size: 0.8rem;
font-weight: normal;
line-height: 1.4;
white-space: normal;
max-width: 300px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
transition: opacity 0.3s, visibility 0.3s;
}
.tooltip-content::after {
content: "";
position: absolute;
top: 100%;
left: 20px;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #333 transparent transparent transparent;
}
.tooltip-label:hover .tooltip-content {
visibility: visible;
opacity: 1;
}
/* Input field focus styling */
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
outline: none;
border-color: #4a90e2 !important;
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
}
/* Radio button styling */
.radio-group {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 4px;
}
.radio-label[data-v-8b0edd] {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 12px;
width: 100%;
}
.radio-label input[type="radio"] {
flex: 0 0 20px; /* fixed width of 20px, no growing/shrinking */
height: 13px;
width: 13px;
margin: 0;
vertical-align: middle;
}
.radio-label span {
flex: 1; /* take up all remaining space */
word-break: break-word; /* break long words if needed */
margin-left: 12px;
}
/* Checkbox styling */
.checkbox-container {
display: flex;
align-items: center;
justify-content: flex-start;
}
.checkbox-label {
display: flex;
align-items: center;
cursor: pointer;
text-align: left;
}
.checkbox-text {
font-size: 0.9rem;
color: #555;
}
/* Context field styling */
.field-context {
margin-bottom: 8px;
font-size: 0.9rem;
color: #666;
background-color: #f8f9fa;
padding: 8px;
border-radius: 4px;
text-align: left;
}
.required {
color: #d93025;
margin-left: 2px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.form-field {
display: block;
grid-template-columns: none;
}
.form-field .field-label {
margin-bottom: 8px;
display: block;
}
.tooltip-content {
position: fixed;
left: 10px;
right: 10px;
max-width: none;
bottom: auto;
top: 50%;
transform: translateY(-50%);
}
}
</style>