Compare commits

...

7 Commits

Author SHA1 Message Date
Alexandro Uc
e243e8397d fix: translate contactCard 2026-04-03 14:47:35 -06:00
Alexandro Uc
4570526ceb fix: sidebar label privacy list 2026-04-03 14:45:01 -06:00
Alexandro Uc
a9f7349039 add: translate texts of privacy list module 2026-04-03 14:41:44 -06:00
Alexandro Uc
0fbe83d737 local search & fix texts 2026-04-01 20:41:09 -06:00
Alexandro Uc
974c34ad1c add: private load actions & delete contact 2026-04-01 20:41:09 -06:00
Alexandro Uc
9e6e0948d4 add: services 2026-04-01 20:41:09 -06:00
Alexandro Uc
291dbd2f35 add: contacts module & actions to private list 2026-04-01 20:41:09 -06:00
28 changed files with 806 additions and 44 deletions

View File

@@ -3,6 +3,11 @@ body {
padding: 0px;
}
/*Colors*/
.primary-color-eta {
color: #FBBA33;
}
.flex-d-column {
display: flex;
flex-direction: column;
@@ -35,12 +40,6 @@ body {
border-radius: 3rem !important;
}
/* Fuentes */
.font-bold {
font-weight: bold;
}
/* *********** */
.divider {
display: block;
height: 2px;
@@ -130,6 +129,12 @@ body {
color: #FBBA33;
}
.text-tertiary {
font-size: 1rem !important;
font-weight: 500 !important;
color: rgb(181, 168, 168) !important;
}
.card-info,
.card-fixed {
width: 100%;

View File

@@ -0,0 +1,15 @@
.font-bold {
font-weight: bold;
}
.fsize-1 {
font-size: 1rem;
}
.fsize-1-5 {
font-size: 1.5rem;
}
.fsize-2 {
font-size: 2rem;
}

View File

@@ -121,6 +121,9 @@
<template>
<div class="card-fixed card-load mt-4">
<div class="row">
<p v-if="load?.privacy === true && load?.company?._id === authStore?.user?.company._id && !share">
<i class="fa-solid fa-lock primary-color-eta fsize-1-5"></i> <span class="text-tertiary fsize-1">Carga privada</span>
</p>
<div class="col-lg-6 col-sm-12">
<p>
<span>{{t('loads.origin')}}: </span>

View File

@@ -0,0 +1,43 @@
<script setup>
const props = defineProps({
saerch: {
type: [String]
},
placeholder: {
type: String,
}
})
defineEmits(['update:saerch'])
</script>
<template>
<div class="mb-4">
<input
class="custom-search"
type="search"
name="search"
:placeholder="placeholder"
:value="saerch"
@input="$event => $emit('update:saerch', $event.target.value)"
/>
</div>
</template>
<style scoped>
.custom-search {
width: 100%;
padding: 12px 16px;
border: 1px solid rgb(222, 214, 214);
border-radius: 5px;
}
.custom-search:focus {
outline: none;
border-color: rgb(217, 202, 202);
}
</style>

View File

@@ -0,0 +1,96 @@
<script setup>
defineProps({
modelValue: {
type: Boolean,
default: false
},
label: {
type: String,
required: false
},
name: {
type: String,
required: true
},
value: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['update:modelValue'])
const toggle = (event) => {
emit('update:modelValue', event.target.checked)
}
</script>
<template>
<label class="switch-container">
<span v-if="label" class="custom-label label-text flex1">{{ label }}</span>
<input
type="checkbox"
:name="name"
:checked="modelValue"
@change="toggle"
>
<span class="slider"></span>
</label>
</template>
<style scoped>
.flex1 {
flex: 1;
}
.switch-container {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-size: 16px;
}
/* ocultar input */
.switch-container input {
display: none;
}
/* fondo del switch */
.slider {
position: relative;
width: 45px;
height: 25px;
background-color: #ccc;
border-radius: 25px;
transition: 0.3s;
}
/* circulo */
.slider::before {
content: "";
position: absolute;
width: 18px;
height: 18px;
left: 4px;
top: 3.5px;
background-color: white;
border-radius: 50%;
transition: 0.3s;
}
/* estado activo */
input:checked + .slider {
background-color: #5A67DD; /* tu color */
}
input:checked + .slider::before {
transform: translateX(20px);
}
/* hover */
.switch-container:hover .slider {
opacity: 0.8;
}
</style>

View File

@@ -198,7 +198,7 @@
<option v-for="vehicle in vehiclesAvailable" :value="vehicle._id">
{{vehicle.vehicle_code?.toUpperCase()}} - {{ vehicle.truck_type }}
<span v-if="vehicle?.driver">- {{ vehicle?.driver?.first_name + ' ' + vehicle?.driver?.last_name }}</span>
<span v-else>- Sin conductor</span>
<span v-else>- Sin conductor -</span>
</option>
</select>
</div>

View File

@@ -159,7 +159,10 @@ const en = {
loading: 'Please wait!',
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.'
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.',
notDriverAssign: 'This offer does not include an assigned driver. Please request that the carrier assign an operator to continue the loading process.',
changeSuccess: 'Changes successfully implemented!',
actionSuccess: 'Action successfully completed!',
},
global: {
signIn: "Sign In",
@@ -208,6 +211,9 @@ const en = {
delivered: 'Delivered',
downloading: 'Downloading',
add: 'Add',
dashboard: 'Dashboard',
notDriver: 'Without driver',
mistake: 'Mistake'
},
login: {
title: 'Sign in',
@@ -495,6 +501,19 @@ const en = {
},
store: {
title: "Warehouse"
},
contacts: {
privacyList: 'Private list',
truckTypesUsed: 'Types of Used Trucks',
deleteCarrier: 'Delete carrier',
QuestionDeleteCarrier: 'Are you sure you want to remove this carrier from the private list?',
QuestionAddCarrier: 'Are you sure you want to add this carrier to the private list?',
deletedCarrier: 'Carrier removed',
deletedCarrierSuccess: 'The carrier has been removed from the private list',
deletedCarrierError: 'The removal of the carrier from the private list could not be completed.',
settingsCompany: 'Company settings',
privacyCompanyLabel: 'Activate company privacy settings',
privacyLoadLabel: 'Post as private',
}
};

View File

@@ -162,7 +162,10 @@ const es = {
loading: 'Por favor espere!',
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.'
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.',
notDriverAssign: 'Oferta sin conductor asignado. Solicite al transportista que asigne un operador para continuar con el proceso de carga.',
changeSuccess: 'Cambios aplicados con exito!',
actionSuccess: 'Acción realizada con exito!',
},
global: {
signIn: 'Ingresar',
@@ -211,6 +214,9 @@ const es = {
delivered: 'Entregado',
downloading: 'Descargando',
add: 'Agregar',
dashboard: 'Panel',
notDriver: 'Sin conductor',
mistake: 'Error'
},
login: {
title: 'Iniciar sesión',
@@ -504,6 +510,19 @@ const es = {
},
store: {
title: "Bodega"
},
contacts: {
privacyList: 'Lista privada',
truckTypesUsed: 'Tipos de camiones usados',
deleteCarrier: 'Eliminar transportista',
QuestionDeleteCarrier: '¿Estas seguro de eliminar este transportista de la lista privada?',
QuestionAddCarrier: '¿Estas seguro de añadir a este transportista de la lista privada?',
deletedCarrier: 'Transportista eliminado',
deletedCarrierSuccess: 'Se ha eliminado el transportista de la lista privada',
deletedCarrierError: 'No se pudo completar la eliminación del transportista de la lista privada',
settingsCompany: 'Configuraciones de empresa',
privacyCompanyLabel: 'Activar configuración de privacidad de la empresa',
privacyLoadLabel: 'Publicar como privada',
}
};

View File

@@ -1,10 +1,9 @@
<script setup>
import LoadingModal from '../components/ui/LoadingModal.vue';
import NavBar from '../components/NavBar.vue';
import Sidebar from '../components/Sidebar.vue';
import NavBar from './components/NavBar.vue';
import Sidebar from './components/Sidebar.vue';
import ProfilePopup from '../views/profile/modals/ProfilePopup.vue';
import NotificationsPopup from '../components/NotificationsPopup.vue';
import NotificationsPopup from './components/NotificationsPopup.vue';
</script>
<template>
@@ -19,6 +18,7 @@
</div>
<LoadingModal/>
<ProfilePopup/>
<!-- <ConfigPopup/> -->
<NotificationsPopup/>
</template>

View File

@@ -0,0 +1,99 @@
<script setup>
import { useNotificationsStore } from '../../stores/notifications';
import CustomSwitch from '../../components/CustomSwitch.vue'
import { useI18n } from 'vue-i18n';
import { ref } from 'vue';
import { onMounted } from 'vue';
import { watch } from 'vue';
import { usePrivacyStore } from '../../stores/privacy';
const lang = ref(null);
const privacy = ref(false);
const noty = useNotificationsStore();
const privacyStore = usePrivacyStore();
const { t, locale } = useI18n();
onMounted(() => {
lang.value = localStorage.getItem('lang') ?? 'es';
locale.value = lang.value;
privacy.value = privacyStore.privacy;
});
watch(lang, () => {
locale.value = lang.value
localStorage.setItem('lang', lang.value)
})
watch(privacy, () => {
privacyStore.updatePrivacy(privacy.value);
})
const closePopup = () => {
noty.toggleConfig();
}
</script>
<template>
<div
v-if="noty.openConfig"
>
<div
class="content-popup"
@click="closePopup()"
>
</div>
<div
class="profile-card">
<i class="fa-solid fa-xmark close-icon" @click="closePopup()"></i>
<br>
<CustomSwitch
label="Activar configuracion de privacidad"
v-model="privacy"
name="privacity"
/>
</div>
</div>
</template>
<style scoped>
.content-popup {
position: fixed;
z-index: 1000;
width: 100wv;
right: 0px;
top: 0px;
left: 0px;
cursor: pointer;
bottom: 0px;
background-color: #000;
opacity: 0.2;
}
.profile-card {
position: fixed;
flex: 1;
right: 20px;
top: 70px;
z-index: 2000;
width: 340px;
background-color: #FFF;
opacity: 1;
border-radius: 13px;
padding: 20px 20px;
display: flex;
flex-direction: column;
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.10));
}
.close-icon {
display: flex;
position: absolute;
right: 20px;
top: 16px;
cursor: pointer;
font-size: 24px;
}
</style>

View File

@@ -1,10 +1,10 @@
<script setup>
import { RouterLink } from 'vue-router';
import { useAuthStore } from '../stores/auth';
import { useNotificationsStore } from '../stores/notifications';
import { useAuthStore } from '../../stores/auth';
import { useNotificationsStore } from '../../stores/notifications';
import { useI18n } from 'vue-i18n';
import { onMounted, ref } from 'vue';
import { getNotificationsCompany } from '../services/company';
import { getNotificationsCompany } from '../../services/company';
const auth = useAuthStore();
const noty = useNotificationsStore();
@@ -75,7 +75,16 @@
<a
active-class="router-link-active"
@click="noty.toggleProfile"
class="nav-link"><i class="fa-regular fa-user"></i></a>
class="nav-link">
<i class="fa-regular fa-user"></i>
</a>
<!-- <a
v-if="permission === 'role_shipper'"
active-class="router-link-active"
@click="noty.toggleConfig"
class="nav-link">
<i class="fa-solid fa-gear"></i>
</a> -->
</div>
</div>
</nav>

View File

@@ -2,10 +2,10 @@
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import {getDateTime} from '../helpers/date_formats';
import { deleteNotification } from '../services/company';
import { useNotificationsStore } from '../stores/notifications';
import Spiner from './ui/Spiner.vue';
import {getDateTime} from '../../helpers/date_formats';
import { deleteNotification } from '../../services/company';
import { useNotificationsStore } from '../../stores/notifications';
import Spiner from '../../components/ui/Spiner.vue';
const props = defineProps({
noty: {

View File

@@ -1,5 +1,5 @@
<script setup>
import { useNotificationsStore } from '../stores/notifications';
import { useNotificationsStore } from '../../stores/notifications';
import { useI18n } from 'vue-i18n';
import NotificationCard from './NotificationCard.vue';

View File

@@ -1,12 +1,12 @@
<script setup>
import { RouterLink, useRoute, useRouter } from 'vue-router';
import { useAuthStore } from '../stores/auth';
import { useAuthStore } from '../../stores/auth';
import Swal from 'sweetalert2';
import { useI18n } from 'vue-i18n';
import { useCompanyStore } from '../stores/company';
import { useVehiclesStore } from '../stores/vehicles';
import { useLoadsStore } from '../stores/loads';
import { useNotificationsStore } from '../stores/notifications';
import { useCompanyStore } from '../../stores/company';
import { useVehiclesStore } from '../../stores/vehicles';
import { useLoadsStore } from '../../stores/loads';
import { useNotificationsStore } from '../../stores/notifications';
const route = useRoute();
const auth = useAuthStore();
@@ -65,7 +65,7 @@
<i class="fa-solid fa-gauge-high" :class="[route.name === 'home' ? 'router-link-active' : '']"></i>
<RouterLink
active-class="router-link-active"
class="nav-link" :to="{name: 'home'}">Dashboard</RouterLink>
class="nav-link" :to="{name: 'home'}">{{ t('global.dashboard') }}</RouterLink>
</div>
</li>
<li
@@ -159,6 +159,16 @@
class="nav-link" :to="{name: 'calculator'}">{{ t('global.calculator') }}</RouterLink>
</div>
</li>
<li
v-if="permission === 'role_shipper' && jobRole !== roleCheck"
:class="[route.name === 'groups' ? 'bg-nav-active' : '']">
<div>
<i class="fa-regular fa-address-book" :class="[route.name === 'groups' ? 'router-link-active' : '']"></i>
<RouterLink
active-class=""
class="nav-link" :to="{name: 'groups'}">{{ t('contacts.privacyList') }}</RouterLink>
</div>
</li>
</ul>
<div class="eta-info">
<div class="divider"></div>

View File

@@ -1,4 +1,5 @@
import './assets/main.css'
import './assets/styles/fonts.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'

View File

@@ -200,7 +200,16 @@ const router = createRouter({
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('../views/dashboard/HomeView.vue'),
}
},
{
path: 'lista-privada',
name: 'groups',
meta: {
permissions: ['role_shipper'],
roles: ['staff', 'manager', 'owner']
},
component: () => import('../views/contacts/ContactsView.vue'),
},
]
}
]

View File

@@ -202,3 +202,67 @@ export const deleteNotification = async(id) => {
};
}
}
export const enablePrivacyCompany = async(formData) => {
try {
const endpoint = '/v1/companies/own';
const {data} = await api.patch(endpoint, formData);
return {
msg: 'success',
data: data
};
} catch (error) {
return {
msg: error.response.data.error ?? "Algo salió mal, intente nás tarde",
data: null
};
}
}
export const getPrivateListService = async() => {
try {
const endpoint = `/v1/groups/private`;
const {data} = await api.get(endpoint);
return {
msg: 'success',
data: data
};
} catch (error) {
return {
msg: error.response.data.error ?? "Algo salió mal, intente nás tarde",
data: null
};
}
}
export const addCompanyPrivicyListService = async(id) => {
try {
const endpoint = `/v1/groups/private/${id}`;
const {data} = await api.patch(endpoint);
return {
msg: 'success',
data: data
};
} catch (error) {
return {
msg: error.response.data.error ?? "Algo salió mal, intente nás tarde",
data: null
};
}
}
export const deleteCompanyPrivicyListService = async(id) => {
try {
const endpoint = `/v1/groups/private/${id}`;
const {data} = await api.delete(endpoint);
return {
msg: 'success',
data: data
};
} catch (error) {
return {
msg: error.response.data.error ?? "Algo salió mal, intente nás tarde",
data: null
};
}
}

View File

@@ -5,12 +5,14 @@ import { renewToken } from '../services/auth';
import {useNotificationsStore} from './notifications';
import { useLoadsStore } from "./loads";
import { updateMyUserProfile } from "../services/company";
import { usePrivacyStore } from "./privacy";
export const useAuthStore = defineStore('auth', () => {
const router = useRouter();
const route = useRoute();
const noty = useNotificationsStore();
const privicyStore = usePrivacyStore();
const loadStore = useLoadsStore();
const sesion = ref('')
const checking = ref(false);
@@ -39,6 +41,7 @@ export const useAuthStore = defineStore('auth', () => {
if(resp.msg === 'success') {
user.value = resp.data.user;
sesion.value = resp.data.session_token;
privicyStore.privacy = resp.data.user?.company?.privacy || false;
token.value = resp.data.accessToken;
localStorage.setItem('session', resp.data.session_token);
localStorage.setItem('access', resp.data.accessToken);

View File

@@ -8,6 +8,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
const error = ref(false)
const show = ref(false);
const openProfile = ref(false);
const openConfig = ref(false);
const openNotifications = ref(false);
const notifications = ref([]);
const newNoty = ref(false);
@@ -26,6 +27,10 @@ export const useNotificationsStore = defineStore('notifications', () => {
openProfile.value = !openProfile.value;
}
const toggleConfig = () => {
openConfig.value = !openConfig.value;
}
const toggleNotifications = () => {
openNotifications.value = !openNotifications.value;
}
@@ -39,6 +44,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
newNoty.value = false;
show.value = false;
openNotifications.value = false;
openConfig.value = false;
toggleProfile.value = false;
}
@@ -51,8 +57,10 @@ export const useNotificationsStore = defineStore('notifications', () => {
notifications,
newNoty,
openProfile,
openConfig,
removeNoty,
toggleProfile,
toggleConfig,
openNotifications,
toggleNotifications,
clear

76
src/stores/privacy.js Normal file
View File

@@ -0,0 +1,76 @@
import { defineStore } from "pinia"
import { ref } from "vue";
import { addCompanyPrivicyListService, deleteCompanyPrivicyListService, enablePrivacyCompany, getPrivateListService } from "../services/company";
export const usePrivacyStore = defineStore('privacy', () => {
const privacy = ref(false);
const privateListRef = ref([]);
const loading = ref(false);
const privateList = ref([]);
const updatePrivacy = async (value) => {
const data = {
"privacy": value
};
const response = await enablePrivacyCompany(data);
if(response.msg == 'success') {
privacy.value = response.data.privacy || false;
}
}
const getPrivateList = async (id) => {
loading.value = true;
const response = await getPrivateListService();
loading.value = false;
if(response.msg == 'success') {
privateList.value = response.data.allowedCompanies || [];
privateListRef.value = privateList.value.map((e) => e._id);
return 'success';
} else {
return response.msg;
}
}
const addCompanyToPrivateList = async (id) => {
const response = await addCompanyPrivicyListService(id);
if(response.msg == 'success') {
privateListRef.value = [
...privateListRef.value,
id
];
return 'success';
} else {
return response.msg;
}
}
const deleteCompanyToPrivateList = async (id) => {
const response = await deleteCompanyPrivicyListService(id);
if(response.msg == 'success') {
removeContact(id);
return 'success';
} else {
return response.msg;
}
}
/// State
const removeContact = (id) => {
privateList.value = privateList.value.filter((e) => e._id !== id);
privateListRef.value = privateListRef.value.filter((e) => e !== id);
}
return {
loading,
privacy,
privateListRef,
privateList,
removeContact,
updatePrivacy,
getPrivateList,
deleteCompanyToPrivateList,
addCompanyToPrivateList
}
});

View File

@@ -98,7 +98,7 @@
localStorage.setItem('id', result.data._id);
localStorage.setItem('session', auth.sesion);
localStorage.setItem('access', auth.accessToken);
localStorage.setItem('access', auth.token);
const userData = {
"first_name" : user.name,

View File

@@ -8,16 +8,21 @@
import EditCompanyModal from './modals/EditCompanyModal.vue';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import CustomSwitch from '../../components/CustomSwitch.vue';
import { usePrivacyStore } from '../../stores/privacy';
const auth = useAuthStore()
const privacyStore = usePrivacyStore();
const company = useCompanyStore();
const { user } = storeToRefs(auth);
const { t, locale } = useI18n()
const privacy = ref(false);
onMounted(() => {
if(user.value) {
getInitialData()
}
privacy.value = privacyStore.privacy;
});
watch(user, () => {
@@ -26,6 +31,10 @@
}
})
watch(privacy, () => {
privacyStore.updatePrivacy(privacy.value);
})
const getInitialData = async() => {
await company.getCompanyData();
}
@@ -36,7 +45,7 @@
<template>
<EditCompanyModal v-if="company.loading === false"/>
<div>
<h2 class="title my-5">{{ t('company.title') }}</h2>
<h2 class="title my-2">{{ t('company.title') }}</h2>
<div class="card-info">
<Spiner v-if="company.loading"/>
<div v-else class="view">
@@ -93,6 +102,16 @@
</div>
</div>
</div>
<div class="card-fixed flex-d-column"
v-if="auth.user?.job_role === 'owner' || auth.user?.job_role === 'manager'">
<h3> {{ t('contacts.settingsCompany') }} </h3>
<hr>
<CustomSwitch
:label="t('contacts.privacyCompanyLabel')"
v-model="privacy"
name="privacity"
/>
</div>
</div>
</template>

View File

@@ -0,0 +1,69 @@
<script setup>
import { onMounted, ref, watch } from 'vue';
import { usePrivacyStore } from '../../stores/privacy';
import ContactCard from './components/ContactCard.vue';
import { useI18n } from 'vue-i18n';
import CustomSearchInput from '../../components/CustomSearchInput.vue';
import Spiner from '../../components/ui/Spiner.vue';
const privateStore = usePrivacyStore();
const contacts = ref([]);
const { t } = useI18n();
const query = ref('');
let timeout = null;
onMounted( async () => {
await privateStore.getPrivateList();
contacts.value = privateStore.privateList;
});
watch(query, () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
search();
}, 400);
});
watch(
() => privateStore.privateList,
(newValue) => {
contacts.value = newValue;
}
);
const search = () => {
if(query.value.length > 0) {
contacts.value = privateStore.privateList.filter(
(e) => e.company_name
.toLowerCase()
.includes(query.value.toLocaleLowerCase()));
} else {
contacts.value = privateStore.privateList;
}
};
</script>
<template>
<CustomSearchInput
:placeholder="t('carriers.searchByCarrier')"
v-model:saerch="query"
/>
<div class="row">
<div v-if="privateStore.loading" class="d-flex justify-content-center">
<Spiner />
</div>
<ContactCard
v-else
v-for="contact in contacts"
:key="contact.id"
:contact="contact"
/>
</div>
</template>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,108 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useAuthStore } from '../../../stores/auth';
import Swal from 'sweetalert2';
import { usePrivacyStore } from '../../../stores/privacy';
import { computed } from 'vue';
const props = defineProps({
contact: {
type: Object,
required: true
}
})
const { t } = useI18n();
const authStore = useAuthStore();
const contactsStore = usePrivacyStore();
const handleDeleteContact = async() => {
Swal.fire({
title: t('contacts.deleteCarrier'),
text: t('contacts.QuestionDeleteCarrier'),
icon: 'warning',
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonText: t('buttons.delete'),
cancelButtonText: t('buttons.cancel'),
}).then(async(result) => {
const id = props.contact._id;
if(result.isConfirmed) {
Swal.fire({
title: t('messages.loading'),
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading()
},
});
const resp = await contactsStore.deleteCompanyToPrivateList(id);
Swal.close();
if(resp === 'success') {
Swal.fire({
text: t('messages.actionSuccess'),
icon: "success"
});
} else {
Swal.fire({
title: t('global.mistake'),
text: resp,
icon: "error"
});
}
}
});
}
const categories = computed(() => (props.contact.categories)
? props.contact.categories.map((e) => e.name).join(', ')
: ''
)
const truckTypes = computed(() => (props.contact.truck_type)
? props.contact.truck_type.join(', ')
: ''
)
const states = computed(() => (props.contact.company_state)
? props.contact.company_state.join(', ')
: ''
)
</script>
<template>
<div class="col-lg-6 col-12">
<div class="card-fixed flex-d-column">
<div class="d-flex">
<h2 class="flex1">{{ contact.company_name }}</h2>
<button
v-if="(authStore.user?.job_role === 'owner' || authStore.user?.job_role === 'manager')"
class="btn-primary-sm bg-danger sizeBtn"
@click="handleDeleteContact"
>
<i class="fa-solid fa-trash"></i>
</button>
</div>
<p><span class="font-bold">RFC: </span> {{ contact.rfc}}</p>
<p><span class="font-bold">{{ t('global.segments') }}:</span> {{ categories }}</p>
<p><span class="font-bold">{{ t('contacts.truckTypesUsed') }}: </span> {{ truckTypes }}</p>
<p><span class="font-bold">{{ t('global.states') }}: </span> {{ states }}</p>
</div>
</div>
</template>
<style lang="scss" scoped>
.flex1 {
flex: 1;
}
.sizeBtn {
width: 46px;
height: 40px;
margin-left: 16px;
}
</style>

View File

@@ -14,15 +14,17 @@
import { useCompanyStore } from '../../../stores/company';
import { useI18n } from 'vue-i18n';
import { computed } from 'vue';
import {getDateToLocal } from '../../../helpers/date_formats';
import { validateEmail } from '../../../helpers/validations';
import AddressPreview from '../../../components/AddressPreview.vue';
import CustomSwitch from '../../../components/CustomSwitch.vue';
import { usePrivacyStore } from '../../../stores/privacy';
const loadStore = useLoadsStore();
const notyStore = useNotificationsStore();
const auth = useAuthStore();
const companyStore = useCompanyStore()
const privacyStore = usePrivacyStore()
const windowWidth = ref(window.innerWidth);
const zoom = ref(6);
const heightMap = ref(768);
@@ -42,7 +44,8 @@
const destinationRef = ref('')
const emails = ref([]);
const emailInput = ref('');
const isPrivate = ref(false);
const errors = ref({
segment: null,
truckType: null,
@@ -76,10 +79,12 @@
zoom.value = 4;
heightMap.value = 420;
}
isPrivate.value = privacyStore.privacy;
getLocations('unloading');
getLocations('loading');
formLoad.owner = auth.user?.first_name + ' ' + auth.user?.last_name;
if(loadStore.currentLoad){
isPrivate.value = loadStore.currentLoad.privacy || false
const dateStart = loadStore.currentLoad.est_loading_date;
const dateEnd = loadStore.currentLoad.est_unloading_date;
formLoad.price = loadStore.currentLoad.actual_cost;
@@ -257,7 +262,8 @@
posted_by_name: formLoad.owner,
origin_warehouse: locationLoadSelected.value?._id || null,
destination_warehouse: locationDownloadSelected.value?._id || null,
alert_list: emails.value.length > 0 ? emails.value : null
alert_list: emails.value.length > 0 ? emails.value : null,
privacy: isPrivate.value
};
return loadData;
}
@@ -605,6 +611,13 @@
<div class="modal-footer custom-footer">
<Spiner v-if="isLoading"/>
<div v-else class="btns-footer">
<div class="switch-container">
<CustomSwitch
:label="t('contacts.privacyLoadLabel')"
v-model="isPrivate"
name="contacts"
/>
</div>
<button
type="button"
class="btn btn-danger"
@@ -651,20 +664,22 @@
width: 100%;
}
.custom-footer {
display: flex;
justify-content: center;
}
.error-msg {
color: red;
font-size: 12px;
font-weight: 300;
}
.custom-footer {
display: flex;
justify-content: flex-end;
}
.btns-footer {
display: flex;
gap: 1rem;
justify-content: flex-end;
align-items: center;
}
.chekmark {
@@ -690,5 +705,15 @@
flex-direction: column;
gap: 2rem;
}
.btns-footer {
flex-wrap: wrap;
}
.switch-container {
flex: 1 1 100%;
justify-content: flex-end;
margin-bottom: 0.3rem;
}
}
</style>

View File

@@ -215,7 +215,7 @@
<div v-if="!proposal.vehicle?.driver"
class="box-note bg-warning mb-3"
>
<i class="fa-solid fa-triangle-exclamation"></i> Oferta sin conductor asignado. Solicite al transportista que asigne un operador para continuar con el proceso de carga.
<i class="fa-solid fa-triangle-exclamation"></i> {{ t('messages.notDriverAssign') }}
</div>
<Spiner v-if="isLoadingActions"/>
<div class="d-flex justify-content-end gap-3" v-else>

View File

@@ -9,7 +9,9 @@
import Cities from '../../components/ui/Cities.vue';
import Pagination from '../../components/Pagination.vue';
import { useI18n } from 'vue-i18n';
import { usePrivacyStore } from '../../stores/privacy';
const privacyStore = usePrivacyStore();
const {loading, companies, getCompaniesData, companiesTotal, currentCompaniesPage} = useDirectory();
const query = ref('');
const selectedTruckType = ref([]);
@@ -22,10 +24,11 @@
const limit = 10;
onMounted(() => {
onMounted( async () => {
filterQuery.value.company_type = 'carrier';
filterQuery.value.limit = 'elements=' + limit;
filterQuery.value.page = "page=0";
await privacyStore.getPrivateList();
getCompaniesData(filterQuery.value);
});

View File

@@ -2,13 +2,65 @@
import { RouterLink } from 'vue-router';
import { getDateMonthDay } from '../../../helpers/date_formats';
import { useI18n } from 'vue-i18n';
import Swal from 'sweetalert2';
import { onMounted, ref } from 'vue';
import { useAuthStore } from '../../../stores/auth';
import { usePrivacyStore } from '../../../stores/privacy';
defineProps({
const props = defineProps({
company: {
type: Object,
required: true,
}
});
const authStore = useAuthStore();
const privacyStore = usePrivacyStore();
const existInPrivateList = ref(false);
onMounted(() => {
const id = props.company._id;
existInPrivateList.value = privacyStore.privateListRef.includes(id);
})
const handleAddPrivateList = async() => {
Swal.fire({
title: t('contacts.privacyList'),
text: t('contacts.QuestionAddCarrier'),
icon: 'info',
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonText: t('buttons.yes'),
cancelButtonText: t('buttons.no'),
}).then(async(result) => {
if(result.isConfirmed) {
Swal.fire({
title: t('messages.loading'),
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading()
},
});
const response = await privacyStore.addCompanyToPrivateList(props.company._id);
Swal.close();
if(response === 'success') {
existInPrivateList.value = true;
Swal.fire({
text: t('messages.actionSuccess'),
icon: "success"
});
} else {
Swal.fire({
title: 'Error',
text: response,
icon: "error"
});
}
}
});
}
const { t } = useI18n()
</script>
@@ -30,9 +82,16 @@
<p><span>{{ t('labels.infoCompany') }}: </span>{{company.company_description}}</p>
</div>
</div>
<div class="d-flex justify-content-end">
<div class="d-flex justify-content-end gap-2">
<button
v-if="authStore.user?.permissions === 'role_shipper' && !existInPrivateList"
class="btn-primary-sm bg-dark radius-sm"
@click="handleAddPrivateList"
>
Añadir a lista privada
</button>
<RouterLink
class="btn-primary-sm"
class="btn-primary-sm radius-sm"
:to="{name: 'public-users', params: {id: company._id}}"
>{{ t('buttons.profile') }}</RouterLink>
</div>