add: observer input & tooltips
This commit is contained in:
@@ -231,6 +231,47 @@ td {
|
|||||||
border: 1px solid #FBBA33 !important;
|
border: 1px solid #FBBA33 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.box-observers {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.box-input {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.input-observer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.box-emails {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: #FFF;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
.observer-email {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #e0f7fa;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.icon-delete {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #FF0000;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
th {
|
th {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
import { useCompanyStore } from '../stores/company';
|
import { useCompanyStore } from '../stores/company';
|
||||||
import Swal from 'sweetalert2';
|
import Swal from 'sweetalert2';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { validateEmail } from '../helpers/validations';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
location: {
|
location: {
|
||||||
@@ -27,6 +28,8 @@
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const emails = ref([]);
|
||||||
|
const emailInput = ref('');
|
||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
return (props.location) ? t('directory.editLocation') : t('directory.createLocation');
|
return (props.location) ? t('directory.editLocation') : t('directory.createLocation');
|
||||||
@@ -139,6 +142,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addObserver = () => {
|
||||||
|
const trimmedEmail = emailInput.value.trim()
|
||||||
|
|
||||||
|
if (trimmedEmail && validateEmail(trimmedEmail)) {
|
||||||
|
if(emails.value.includes(trimmedEmail)) {
|
||||||
|
Swal.fire({
|
||||||
|
title: t('errors.emailExist'),
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emails.value.push(trimmedEmail)
|
||||||
|
emailInput.value = ''
|
||||||
|
} else if (trimmedEmail) {
|
||||||
|
Swal.fire({
|
||||||
|
title: t('errors.email'),
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeObserver = (email) => {
|
||||||
|
emails.value = emails.value.filter((e) => e !== email)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const validations = () => {
|
const validations = () => {
|
||||||
errors.value = {
|
errors.value = {
|
||||||
branch_name: locationForm.branch_name.length < 2 ? t('errors.nameRequired') : null,
|
branch_name: locationForm.branch_name.length < 2 ? t('errors.nameRequired') : null,
|
||||||
@@ -251,6 +281,42 @@
|
|||||||
v-model="locationForm.description"
|
v-model="locationForm.description"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="divider mt-4"></div>
|
||||||
|
<div class="box-observers mt-4">
|
||||||
|
<div class="box-input">
|
||||||
|
<div class="input-observer">
|
||||||
|
<CustomInput
|
||||||
|
label="Agregar observador de bodega"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
v-model:field="emailInput"
|
||||||
|
:filled="false"
|
||||||
|
:tooltip="t('messages.observerWarehouse')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="addObserver"
|
||||||
|
class="btn btn-dark"
|
||||||
|
>
|
||||||
|
Agregar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h5 v-if="emails.length > 0">Lista de observadores:</h5>
|
||||||
|
<div class="box-emails" v-if="emails.length > 0">
|
||||||
|
<div
|
||||||
|
v-for="(email, index) in emails"
|
||||||
|
:key="email"
|
||||||
|
class="observer-email"
|
||||||
|
>
|
||||||
|
<span>{{ email }}</span>
|
||||||
|
<i
|
||||||
|
@click="removeObserver(email)"
|
||||||
|
class="fa-solid fa-xmark icon-delete">
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="mt-4 text-center">
|
<div class="mt-4 text-center">
|
||||||
<Spiner v-if="loading"/>
|
<Spiner v-if="loading"/>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import {getDateTime } from '../helpers/date_formats';
|
import {getDateTime } from '../helpers/date_formats';
|
||||||
|
import { validateEmail } from '../helpers/validations';
|
||||||
|
|
||||||
|
|
||||||
const loadStore = useLoadsStore();
|
const loadStore = useLoadsStore();
|
||||||
@@ -43,6 +44,8 @@
|
|||||||
const locationDownloadSelected = ref(null)
|
const locationDownloadSelected = ref(null)
|
||||||
const originRef = ref('')
|
const originRef = ref('')
|
||||||
const destinationRef = ref('')
|
const destinationRef = ref('')
|
||||||
|
const emails = ref([]);
|
||||||
|
const emailInput = ref('');
|
||||||
|
|
||||||
const errors = ref({
|
const errors = ref({
|
||||||
segment: null,
|
segment: null,
|
||||||
@@ -362,6 +365,31 @@
|
|||||||
? t('loads.create')
|
? t('loads.create')
|
||||||
: t('loads.edit')
|
: t('loads.edit')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const addObserver = () => {
|
||||||
|
const trimmedEmail = emailInput.value.trim()
|
||||||
|
|
||||||
|
if (trimmedEmail && validateEmail(trimmedEmail)) {
|
||||||
|
if(emails.value.includes(trimmedEmail)) {
|
||||||
|
Swal.fire({
|
||||||
|
title: t('errors.emailExist'),
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emails.value.push(trimmedEmail)
|
||||||
|
emailInput.value = ''
|
||||||
|
} else if (trimmedEmail) {
|
||||||
|
Swal.fire({
|
||||||
|
title: t('errors.email'),
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeObserver = (email) => {
|
||||||
|
emails.value = emails.value.filter((e) => e !== email)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -425,14 +453,14 @@
|
|||||||
:error="(submited && !formLoad.weight) ? t('errors.weight') : null"
|
:error="(submited && !formLoad.weight) ? t('errors.weight') : null"
|
||||||
v-model:field="formLoad.weight"
|
v-model:field="formLoad.weight"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div class="form-section">
|
|
||||||
<div class="mb-4 mt-3">
|
<div class="mb-4 mt-3">
|
||||||
<label class="custom-label">{{ t('loads.product') }}</label>
|
<label class="custom-label">{{ t('loads.product') }}</label>
|
||||||
<Products
|
<Products
|
||||||
v-model="formLoad.terms"
|
v-model="formLoad.terms"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-section">
|
||||||
<Custominput
|
<Custominput
|
||||||
:label="t('loads.labelPrice')"
|
:label="t('loads.labelPrice')"
|
||||||
type="Number"
|
type="Number"
|
||||||
@@ -455,6 +483,41 @@
|
|||||||
name="owner"
|
name="owner"
|
||||||
v-model:field="formLoad.owner"
|
v-model:field="formLoad.owner"
|
||||||
/>
|
/>
|
||||||
|
<div class="box-observers mt-4">
|
||||||
|
<div class="box-input">
|
||||||
|
<div class="input-observer">
|
||||||
|
<Custominput
|
||||||
|
label="Agregar cliente observador"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
v-model:field="emailInput"
|
||||||
|
:filled="false"
|
||||||
|
:tooltip="t('messages.observerClient')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="addObserver"
|
||||||
|
class="btn btn-dark"
|
||||||
|
>
|
||||||
|
Agregar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h5 v-if="emails.length > 0">Lista de observadores:</h5>
|
||||||
|
<div class="box-emails" v-if="emails.length > 0">
|
||||||
|
<div
|
||||||
|
v-for="(email, index) in emails"
|
||||||
|
:key="email"
|
||||||
|
class="observer-email"
|
||||||
|
>
|
||||||
|
<span>{{ email }}</span>
|
||||||
|
<i
|
||||||
|
@click="removeObserver(email)"
|
||||||
|
class="fa-solid fa-xmark icon-delete">
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-box">
|
<div class="form-box">
|
||||||
|
|||||||
@@ -39,6 +39,9 @@
|
|||||||
step: {
|
step: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0.1
|
default: 0.1
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: String,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -48,11 +51,24 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="d-flex flex-column gap-2 mb-4">
|
<div class="d-flex flex-column gap-2 mb-4">
|
||||||
<label
|
<!-- <label
|
||||||
class="custom-label"
|
class="custom-label"
|
||||||
:class="[label.includes('*') ? 'required' : '']"
|
:class="[label.includes('*') ? 'required' : '']"
|
||||||
:for="name"
|
:for="name"
|
||||||
>{{ label }}</label>
|
>{{ label }}</label> -->
|
||||||
|
<div class="label-box">
|
||||||
|
<label
|
||||||
|
class="custom-label label-text"
|
||||||
|
:class="[label.includes('*') ? 'required' : '']"
|
||||||
|
:for="name"
|
||||||
|
>{{ label }}</label>
|
||||||
|
<div class="help-icon" v-if="tooltip">
|
||||||
|
❓
|
||||||
|
<div class="tooltip">
|
||||||
|
{{ tooltip }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
class="custom-input"
|
class="custom-input"
|
||||||
:class="[
|
:class="[
|
||||||
@@ -86,4 +102,50 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.label-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilo del ícono de ayuda */
|
||||||
|
.help-icon {
|
||||||
|
position: relative;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltip oculto inicialmente */
|
||||||
|
.tooltip {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
width: 180px;
|
||||||
|
padding: 8px;
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
margin-top: 8px;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mostrar tooltip al pasar el mouse */
|
||||||
|
.help-icon:hover .tooltip,
|
||||||
|
.help-icon:active .tooltip {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -113,6 +113,7 @@ const en = {
|
|||||||
required: 'Field is required',
|
required: 'Field is required',
|
||||||
requireds: "All fields required",
|
requireds: "All fields required",
|
||||||
email: "Email is not valid",
|
email: "Email is not valid",
|
||||||
|
emailExist: 'Email already exists',
|
||||||
weakPassword: "Weak password",
|
weakPassword: "Weak password",
|
||||||
matchPassword: "Passwords do not match",
|
matchPassword: "Passwords do not match",
|
||||||
code: 'Enter valid code',
|
code: 'Enter valid code',
|
||||||
@@ -156,6 +157,8 @@ const en = {
|
|||||||
msgCreatedUser: 'When creating a new user, you inform the account beneficiary that they must use their email to set a password in the <span class="font-bold">Forgot my password</span> section so they can log in.',
|
msgCreatedUser: 'When creating a new user, you inform the account beneficiary that they must use their email to set a password in the <span class="font-bold">Forgot my password</span> section so they can log in.',
|
||||||
loading: 'Please wait!',
|
loading: 'Please wait!',
|
||||||
savingChanes: 'Saving changes',
|
savingChanes: 'Saving changes',
|
||||||
|
observerWarehouse: 'Warehouse observers will receive notifications about the load location, including arrival and departure times, and will be able to view the cargo on the warehouse dashboard.',
|
||||||
|
observerClient: 'Observers will receive notifications about the load, including the loading and unloading date, as well as any changes in the load status. They can monitor the cargo on their customer dashboard.'
|
||||||
},
|
},
|
||||||
global: {
|
global: {
|
||||||
signIn: "Sign In",
|
signIn: "Sign In",
|
||||||
@@ -177,7 +180,7 @@ const en = {
|
|||||||
footer: 'ETA VIAPORTE ALL RIGHTS RESERVED',
|
footer: 'ETA VIAPORTE ALL RIGHTS RESERVED',
|
||||||
company: 'Company',
|
company: 'Company',
|
||||||
users: 'Users',
|
users: 'Users',
|
||||||
directory: 'Internal directory',
|
directory: 'Warehouses',
|
||||||
publications: 'Publications',
|
publications: 'Publications',
|
||||||
calendar: 'Calendar',
|
calendar: 'Calendar',
|
||||||
carriers: 'Carriers',
|
carriers: 'Carriers',
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ const es = {
|
|||||||
required: 'Campo es requerido',
|
required: 'Campo es requerido',
|
||||||
requireds: 'Todos los campos con obligatorios',
|
requireds: 'Todos los campos con obligatorios',
|
||||||
email: 'Correo electrónico no es valido',
|
email: 'Correo electrónico no es valido',
|
||||||
|
emailExist: 'Este correo ya fue agregado',
|
||||||
weakPassword: 'Contraseña poco segura',
|
weakPassword: 'Contraseña poco segura',
|
||||||
matchPassword: 'Las contraseñas no coinciden',
|
matchPassword: 'Las contraseñas no coinciden',
|
||||||
code: 'Ingresa código valido',
|
code: 'Ingresa código valido',
|
||||||
@@ -159,6 +160,8 @@ const es = {
|
|||||||
msgCreatedUser: 'Al crear un nuevo usuario, informa al beneficiario de la cuenta, que debe utilizar su correo electrónico para establecer una contraseña en la sección <span class="font-bold">Olvidé mi contraseña</span> y así poder iniciar sesión.',
|
msgCreatedUser: 'Al crear un nuevo usuario, informa al beneficiario de la cuenta, que debe utilizar su correo electrónico para establecer una contraseña en la sección <span class="font-bold">Olvidé mi contraseña</span> y así poder iniciar sesión.',
|
||||||
loading: 'Por favor espere!',
|
loading: 'Por favor espere!',
|
||||||
savingChanes: 'Guardando cambios',
|
savingChanes: 'Guardando cambios',
|
||||||
|
observerWarehouse: 'Los observadores de bodega recibirán notificaciones sobre la ubicación de la carga, incluyendo la hora de llegada y salida, podran visualizar la carga en el panel de bodega.',
|
||||||
|
observerClient: 'Los observadores recibirán notificaciones sobre la carga, incluyendo la fecha de carga y descarga, así como cualquier cambio en el estado de la carga. podran monitorer la carga en el panel de clientes.'
|
||||||
},
|
},
|
||||||
global: {
|
global: {
|
||||||
signIn: 'Ingresar',
|
signIn: 'Ingresar',
|
||||||
@@ -180,7 +183,7 @@ const es = {
|
|||||||
footer: 'ETA VIAPORTE TODOS LOS DERECHOS RESERVADOS',
|
footer: 'ETA VIAPORTE TODOS LOS DERECHOS RESERVADOS',
|
||||||
company: 'Empresa',
|
company: 'Empresa',
|
||||||
users: 'Usuarios',
|
users: 'Usuarios',
|
||||||
directory: 'Directorio interno',
|
directory: 'Bodegas',
|
||||||
publications: 'Publicaciones',
|
publications: 'Publicaciones',
|
||||||
calendar: 'Calendario',
|
calendar: 'Calendario',
|
||||||
carriers: 'Transportistas',
|
carriers: 'Transportistas',
|
||||||
|
|||||||
Reference in New Issue
Block a user