staging #31

Merged
esteban merged 11 commits from staging into create_form 2026-07-24 18:28:28 +00:00
4 changed files with 296 additions and 9 deletions
Showing only changes of commit aeb32e4561 - Show all commits

View File

@ -31,7 +31,7 @@
{ "key": "correo", "label": "form.correo", "type": "email", "required": true, "colspan": 2 }, { "key": "correo", "label": "form.correo", "type": "email", "required": true, "colspan": 2 },
{ "key": "reglamento_firma", "label": "form.reglamento_firma", "type": "text", "required": true, "colspan": 2, "placeholder": "form.reglamento_firma_placeholder" }, { "key": "reglamento_firma", "label": "form.reglamento_firma", "type": "text", "required": true, "colspan": 2, "placeholder": "form.reglamento_firma_placeholder" },
{ "key": "fecha_nacimiento", "label": "form.fecha_nacimiento", "type": "date", "required": true }, { "key": "fecha_nacimiento", "label": "form.fecha_nacimiento", "type": "date", "required": true },
{ "key": "nacionalidad", "label": "form.nacionalidad", "type": "text", "required": true }, { "key": "nacionalidad", "label": "form.nacionalidad", "type": "autocomplete", "required": true, "source": "/forms/paises.json" },
{ {
"key": "sexo", "key": "sexo",
"label": "form.sexo", "label": "form.sexo",
@ -46,11 +46,11 @@
}, },
{ "key": "direccion_completa", "label": "form.direccion", "type": "text", "required": true, "colspan": 2 }, { "key": "direccion_completa", "label": "form.direccion", "type": "text", "required": true, "colspan": 2 },
{ "key": "ciudad", "label": "form.ciudad", "type": "text", "required": true }, { "key": "ciudad", "label": "form.ciudad", "type": "text", "required": true },
{ "key": "estado", "label": "form.estado", "type": "text", "required": true }, { "key": "estado", "label": "form.estado", "type": "text" },
{ "key": "pais", "label": "form.pais", "type": "text", "required": true }, { "key": "pais", "label": "form.pais", "type": "select", "required": true, "source": "/forms/paises.json" },
{ "key": "postal", "label": "form.codigo_postal", "type": "text", "required": true }, { "key": "postal", "label": "form.codigo_postal", "type": "text", "required": true },
{ "key": "telefono", "label": "form.mobile", "type": "text", "required": true }, { "key": "telefono", "label": "form.mobile", "type": "text", "required": true },
{ "key": "whatsapp", "label": "form.whatsapp", "type": "text", "required": true } { "key": "whatsapp", "label": "form.whatsapp", "type": "text" }
] ]
}, },
{ {

195
public/forms/paises.json Normal file
View File

@ -0,0 +1,195 @@
[
{ "label": "Afganistán", "value": "Afganistán" },
{ "label": "Albania", "value": "Albania" },
{ "label": "Alemania", "value": "Alemania" },
{ "label": "Andorra", "value": "Andorra" },
{ "label": "Angola", "value": "Angola" },
{ "label": "Antigua y Barbuda", "value": "Antigua y Barbuda" },
{ "label": "Arabia Saudita", "value": "Arabia Saudita" },
{ "label": "Argelia", "value": "Argelia" },
{ "label": "Argentina", "value": "Argentina" },
{ "label": "Armenia", "value": "Armenia" },
{ "label": "Australia", "value": "Australia" },
{ "label": "Austria", "value": "Austria" },
{ "label": "Azerbaiyán", "value": "Azerbaiyán" },
{ "label": "Bahamas", "value": "Bahamas" },
{ "label": "Bangladés", "value": "Bangladés" },
{ "label": "Barbados", "value": "Barbados" },
{ "label": "Baréin", "value": "Baréin" },
{ "label": "Bélgica", "value": "Bélgica" },
{ "label": "Belice", "value": "Belice" },
{ "label": "Benín", "value": "Benín" },
{ "label": "Bielorrusia", "value": "Bielorrusia" },
{ "label": "Birmania", "value": "Birmania" },
{ "label": "Bolivia", "value": "Bolivia" },
{ "label": "Bosnia y Herzegovina", "value": "Bosnia y Herzegovina" },
{ "label": "Botsuana", "value": "Botsuana" },
{ "label": "Brasil", "value": "Brasil" },
{ "label": "Brunéi", "value": "Brunéi" },
{ "label": "Bulgaria", "value": "Bulgaria" },
{ "label": "Burkina Faso", "value": "Burkina Faso" },
{ "label": "Burundi", "value": "Burundi" },
{ "label": "Cabo Verde", "value": "Cabo Verde" },
{ "label": "Camboya", "value": "Camboya" },
{ "label": "Camerún", "value": "Camerún" },
{ "label": "Canadá", "value": "Canadá" },
{ "label": "Catar", "value": "Catar" },
{ "label": "Chad", "value": "Chad" },
{ "label": "Chile", "value": "Chile" },
{ "label": "China", "value": "China" },
{ "label": "Chipre", "value": "Chipre" },
{ "label": "Colombia", "value": "Colombia" },
{ "label": "Comoras", "value": "Comoras" },
{ "label": "Congo", "value": "Congo" },
{ "label": "Corea del Norte", "value": "Corea del Norte" },
{ "label": "Corea del Sur", "value": "Corea del Sur" },
{ "label": "Costa de Marfil", "value": "Costa de Marfil" },
{ "label": "Costa Rica", "value": "Costa Rica" },
{ "label": "Croacia", "value": "Croacia" },
{ "label": "Cuba", "value": "Cuba" },
{ "label": "Dinamarca", "value": "Dinamarca" },
{ "label": "Dominica", "value": "Dominica" },
{ "label": "Ecuador", "value": "Ecuador" },
{ "label": "Egipto", "value": "Egipto" },
{ "label": "El Salvador", "value": "El Salvador" },
{ "label": "Emiratos Árabes Unidos", "value": "Emiratos Árabes Unidos" },
{ "label": "Eritrea", "value": "Eritrea" },
{ "label": "Eslovaquia", "value": "Eslovaquia" },
{ "label": "Eslovenia", "value": "Eslovenia" },
{ "label": "España", "value": "España" },
{ "label": "Estados Unidos", "value": "Estados Unidos" },
{ "label": "Estonia", "value": "Estonia" },
{ "label": "Etiopía", "value": "Etiopía" },
{ "label": "Filipinas", "value": "Filipinas" },
{ "label": "Finlandia", "value": "Finlandia" },
{ "label": "Fiyi", "value": "Fiyi" },
{ "label": "Francia", "value": "Francia" },
{ "label": "Gabón", "value": "Gabón" },
{ "label": "Gambia", "value": "Gambia" },
{ "label": "Georgia", "value": "Georgia" },
{ "label": "Ghana", "value": "Ghana" },
{ "label": "Granada", "value": "Granada" },
{ "label": "Grecia", "value": "Grecia" },
{ "label": "Guatemala", "value": "Guatemala" },
{ "label": "Guinea", "value": "Guinea" },
{ "label": "Guinea Ecuatorial", "value": "Guinea Ecuatorial" },
{ "label": "Guinea-Bisáu", "value": "Guinea-Bisáu" },
{ "label": "Guyana", "value": "Guyana" },
{ "label": "Haití", "value": "Haití" },
{ "label": "Honduras", "value": "Honduras" },
{ "label": "Hungría", "value": "Hungría" },
{ "label": "India", "value": "India" },
{ "label": "Indonesia", "value": "Indonesia" },
{ "label": "Irak", "value": "Irak" },
{ "label": "Irán", "value": "Irán" },
{ "label": "Irlanda", "value": "Irlanda" },
{ "label": "Islandia", "value": "Islandia" },
{ "label": "Islas Marshall", "value": "Islas Marshall" },
{ "label": "Islas Salomón", "value": "Islas Salomón" },
{ "label": "Israel", "value": "Israel" },
{ "label": "Italia", "value": "Italia" },
{ "label": "Jamaica", "value": "Jamaica" },
{ "label": "Japón", "value": "Japón" },
{ "label": "Jordania", "value": "Jordania" },
{ "label": "Kazajistán", "value": "Kazajistán" },
{ "label": "Kenia", "value": "Kenia" },
{ "label": "Kirguistán", "value": "Kirguistán" },
{ "label": "Kiribati", "value": "Kiribati" },
{ "label": "Kuwait", "value": "Kuwait" },
{ "label": "Laos", "value": "Laos" },
{ "label": "Lesoto", "value": "Lesoto" },
{ "label": "Letonia", "value": "Letonia" },
{ "label": "Líbano", "value": "Líbano" },
{ "label": "Liberia", "value": "Liberia" },
{ "label": "Libia", "value": "Libia" },
{ "label": "Liechtenstein", "value": "Liechtenstein" },
{ "label": "Lituania", "value": "Lituania" },
{ "label": "Luxemburgo", "value": "Luxemburgo" },
{ "label": "Madagascar", "value": "Madagascar" },
{ "label": "Malasia", "value": "Malasia" },
{ "label": "Malaui", "value": "Malaui" },
{ "label": "Maldivas", "value": "Maldivas" },
{ "label": "Mali", "value": "Mali" },
{ "label": "Malta", "value": "Malta" },
{ "label": "Marruecos", "value": "Marruecos" },
{ "label": "Mauricio", "value": "Mauricio" },
{ "label": "Mauritania", "value": "Mauritania" },
{ "label": "México", "value": "México" },
{ "label": "Micronesia", "value": "Micronesia" },
{ "label": "Moldavia", "value": "Moldavia" },
{ "label": "Mónaco", "value": "Mónaco" },
{ "label": "Mongolia", "value": "Mongolia" },
{ "label": "Montenegro", "value": "Montenegro" },
{ "label": "Mozambique", "value": "Mozambique" },
{ "label": "Namibia", "value": "Namibia" },
{ "label": "Nauru", "value": "Nauru" },
{ "label": "Nepal", "value": "Nepal" },
{ "label": "Nicaragua", "value": "Nicaragua" },
{ "label": "Níger", "value": "Níger" },
{ "label": "Nigeria", "value": "Nigeria" },
{ "label": "Noruega", "value": "Noruega" },
{ "label": "Nueva Zelanda", "value": "Nueva Zelanda" },
{ "label": "Omán", "value": "Omán" },
{ "label": "Países Bajos", "value": "Países Bajos" },
{ "label": "Pakistán", "value": "Pakistán" },
{ "label": "Palaos", "value": "Palaos" },
{ "label": "Palestina", "value": "Palestina" },
{ "label": "Panamá", "value": "Panamá" },
{ "label": "Papúa Nueva Guinea", "value": "Papúa Nueva Guinea" },
{ "label": "Paraguay", "value": "Paraguay" },
{ "label": "Perú", "value": "Perú" },
{ "label": "Polonia", "value": "Polonia" },
{ "label": "Portugal", "value": "Portugal" },
{ "label": "Reino Unido", "value": "Reino Unido" },
{ "label": "República Centroafricana", "value": "República Centroafricana" },
{ "label": "República Checa", "value": "República Checa" },
{ "label": "República Dominicana", "value": "República Dominicana" },
{ "label": "República del Congo", "value": "República del Congo" },
{ "label": "Ruanda", "value": "Ruanda" },
{ "label": "Rumania", "value": "Rumania" },
{ "label": "Rusia", "value": "Rusia" },
{ "label": "Samoa", "value": "Samoa" },
{ "label": "San Cristóbal y Nieves", "value": "San Cristóbal y Nieves" },
{ "label": "San Marino", "value": "San Marino" },
{ "label": "San Vicente y las Granadinas", "value": "San Vicente y las Granadinas" },
{ "label": "Santa Lucía", "value": "Santa Lucía" },
{ "label": "Santo Tomé y Príncipe", "value": "Santo Tomé y Príncipe" },
{ "label": "Senegal", "value": "Senegal" },
{ "label": "Serbia", "value": "Serbia" },
{ "label": "Seychelles", "value": "Seychelles" },
{ "label": "Sierra Leona", "value": "Sierra Leona" },
{ "label": "Singapur", "value": "Singapur" },
{ "label": "Siria", "value": "Siria" },
{ "label": "Somalia", "value": "Somalia" },
{ "label": "Sri Lanka", "value": "Sri Lanka" },
{ "label": "Suazilandia", "value": "Suazilandia" },
{ "label": "Sudáfrica", "value": "Sudáfrica" },
{ "label": "Sudán", "value": "Sudán" },
{ "label": "Sudán del Sur", "value": "Sudán del Sur" },
{ "label": "Suecia", "value": "Suecia" },
{ "label": "Suiza", "value": "Suiza" },
{ "label": "Surinam", "value": "Surinam" },
{ "label": "Tailandia", "value": "Tailandia" },
{ "label": "Tanzania", "value": "Tanzania" },
{ "label": "Tayikistán", "value": "Tayikistán" },
{ "label": "Timor Oriental", "value": "Timor Oriental" },
{ "label": "Togo", "value": "Togo" },
{ "label": "Tonga", "value": "Tonga" },
{ "label": "Trinidad y Tobago", "value": "Trinidad y Tobago" },
{ "label": "Túnez", "value": "Túnez" },
{ "label": "Turkmenistán", "value": "Turkmenistán" },
{ "label": "Turquía", "value": "Turquía" },
{ "label": "Tuvalu", "value": "Tuvalu" },
{ "label": "Ucrania", "value": "Ucrania" },
{ "label": "Uganda", "value": "Uganda" },
{ "label": "Uruguay", "value": "Uruguay" },
{ "label": "Uzbekistán", "value": "Uzbekistán" },
{ "label": "Vanuatu", "value": "Vanuatu" },
{ "label": "Vaticano", "value": "Vaticano" },
{ "label": "Venezuela", "value": "Venezuela" },
{ "label": "Vietnam", "value": "Vietnam" },
{ "label": "Yemen", "value": "Yemen" },
{ "label": "Yibuti", "value": "Yibuti" },
{ "label": "Zambia", "value": "Zambia" },
{ "label": "Zimbabue", "value": "Zimbabue" }
]

View File

@ -225,6 +225,44 @@ const handleSubmit = async () => {
const getFieldError = (key) => stepErrors[key] || false; const getFieldError = (key) => stepErrors[key] || false;
const suggestionsCache = ref({});
const showSuggestions = reactive({});
const loadSuggestions = async (source) => {
if (suggestionsCache.value[source]) return;
try {
const res = await fetch(source);
suggestionsCache.value[source] = await res.json();
} catch {}
};
const filteredSuggestions = (field) => {
const val = formData[field.key]?.toLowerCase().trim() || "";
if (!val) return [];
const list = suggestionsCache.value[field.source] || [];
return list.filter(item => item.label.toLowerCase().includes(val));
};
const selectSuggestion = (field, item) => {
formData[field.key] = item.value;
showSuggestions[field.key] = false;
stepErrors[field.key] = false;
};
const onAutocompleteFocus = (field) => {
loadSuggestions(field.source);
showSuggestions[field.key] = true;
};
const onAutocompleteBlur = (field) => {
setTimeout(() => { showSuggestions[field.key] = false; }, 200);
};
const getSelectOptions = (field) => {
loadSuggestions(field.source);
return suggestionsCache.value[field.source] || [];
};
const findField = (key) => { const findField = (key) => {
if (!config.value) return null; if (!config.value) return null;
for (const step of config.value.steps) { for (const step of config.value.steps) {
@ -325,9 +363,9 @@ const getFieldInputType = (field) => {
<div v-else-if="!config" class="flex justify-center py-12"> <div v-else-if="!config" class="flex justify-center py-12">
<span class="w-8 h-8 border-2 border-[#22523F] font-secondary border-t-transparent rounded-full animate-spin"></span> <span class="w-8 h-8 border-2 border-[#22523F] font-secondary border-t-transparent rounded-full animate-spin"></span>
</div> </div>
<div v-else class="w-full max-w-3xl mx-auto md:px-4 sm:px-0">
<template v-if="!isSuccess"> <template v-else>
<div v-if="!isSuccess" class="w-full max-w-3xl mx-auto md:px-4 sm:px-0">
<div class="flex flex-wrap sm:flex-nowrap items-center justify-center gap-x-1 md:gap-x-2 gap-y-2 sm:gap-y-3 mb-4 sm:mb-6 lg:mb-8"> <div class="flex flex-wrap sm:flex-nowrap items-center justify-center gap-x-1 md:gap-x-2 gap-y-2 sm:gap-y-3 mb-4 sm:mb-6 lg:mb-8">
<template v-for="(step, index) in config.steps" :key="index"> <template v-for="(step, index) in config.steps" :key="index">
<button <button
@ -428,6 +466,57 @@ const getFieldInputType = (field) => {
/> />
</label> </label>
<div v-else-if="field.type === 'select'" class="relative">
<label
class="input w-full bg-white rounded-xl focus-within:outline-2 focus-within:outline-[#22523F]"
:class="{ 'input-error border-red-500 focus-within:outline-red-500': getFieldError(field.key) }"
>
<Icon :icon="getFieldIcon(field)" class="shrink-0 opacity-50 text-gray-400 text-lg" />
<select
class="grow text-[#1a1a1a] bg-transparent"
v-model="formData[field.key]"
@change="stepErrors[field.key] = false"
>
<option value="" disabled>{{ tl("form.select_placeholder", { label: tl(field.label) }) }}</option>
<option v-for="item in getSelectOptions(field)" :key="item.value" :value="item.value">
{{ item.label }}
</option>
</select>
</label>
</div>
<div v-else-if="field.type === 'autocomplete'" class="relative">
<label
class="input w-full bg-white rounded-xl focus-within:outline-2 focus-within:outline-[#22523F]"
:class="{ 'input-error border-red-500 focus-within:outline-red-500': getFieldError(field.key) }"
>
<Icon :icon="getFieldIcon(field)" class="shrink-0 opacity-50 text-gray-400 text-lg" />
<input
class="grow text-[#1a1a1a]"
type="text"
v-model="formData[field.key]"
:placeholder="tl(field.placeholder || field.label)"
autocomplete="off"
@input="stepErrors[field.key] = false; showSuggestions[field.key] = true"
@focus="onAutocompleteFocus(field)"
@blur="onAutocompleteBlur(field)"
/>
</label>
<ul
v-if="showSuggestions[field.key] && filteredSuggestions(field).length"
class="absolute left-0 right-0 z-50 bg-white shadow-lg rounded-b-xl border border-gray-200 border-t-0 max-h-[200px] overflow-y-auto"
>
<li
v-for="item in filteredSuggestions(field)"
:key="item.value"
class="px-4 py-2.5 cursor-pointer text-sm text-[#1a1a1a] hover:bg-[#22523F]/10 transition-colors"
@mousedown.prevent="selectSuggestion(field, item)"
>
{{ item.label }}
</li>
</ul>
</div>
<div v-else-if="field.type === 'textarea'" class="relative"> <div v-else-if="field.type === 'textarea'" class="relative">
<Icon icon="ph:note-pencil" class="absolute left-3 top-3.5 text-gray-400 opacity-50 shrink-0 pointer-events-none text-lg" /> <Icon icon="ph:note-pencil" class="absolute left-3 top-3.5 text-gray-400 opacity-50 shrink-0 pointer-events-none text-lg" />
<textarea <textarea
@ -592,9 +681,10 @@ const getFieldInputType = (field) => {
</div> </div>
</form> </form>
</template> </div>
<div v-else class="bg-[#EBE6D2] p-8 sm:p-12 rounded-none text-center"> <div v-else class="w-full max-w-3xl mx-auto md:px-4 sm:px-0">
<div class="bg-[#EBE6D2] p-8 sm:p-12 rounded-none text-center">
<img <img
src="/img/logo-metalico.webp" src="/img/logo-metalico.webp"
alt="Logo Centro del Reino de Paz y Justicia" alt="Logo Centro del Reino de Paz y Justicia"
@ -616,8 +706,9 @@ const getFieldInputType = (field) => {
{{ tl("form.back_home") }} {{ tl("form.back_home") }}
</a> </a>
</div> </div>
</div> </div>
</template>
</template> </template>
<style> <style>

View File

@ -325,6 +325,7 @@
"form.config_error": "Error al cargar el formulario. Intentá de nuevo más tarde.", "form.config_error": "Error al cargar el formulario. Intentá de nuevo más tarde.",
"form.si": "Sí", "form.si": "Sí",
"form.no": "No", "form.no": "No",
"form.select_placeholder": "Seleccioná {label}",
"form.step_counter": "Paso {current} de {total}", "form.step_counter": "Paso {current} de {total}",
"form.success_title": "¡Registro completado con éxito!", "form.success_title": "¡Registro completado con éxito!",
"form.success_info": "Tu solicitud de inscripción como voluntario ha sido recibida correctamente.", "form.success_info": "Tu solicitud de inscripción como voluntario ha sido recibida correctamente.",