add: contacts module & actions to private list
This commit is contained in:
43
src/components/CustomSearchInput.vue
Normal file
43
src/components/CustomSearchInput.vue
Normal 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>
|
||||||
93
src/components/CustomSwitch.vue
Normal file
93
src/components/CustomSwitch.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<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">{{ label }}</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:name="name"
|
||||||
|
:checked="modelValue"
|
||||||
|
@change="toggle"
|
||||||
|
>
|
||||||
|
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.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>
|
||||||
@@ -159,6 +159,16 @@
|
|||||||
class="nav-link" :to="{name: 'calculator'}">{{ t('global.calculator') }}</RouterLink>
|
class="nav-link" :to="{name: 'calculator'}">{{ t('global.calculator') }}</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</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'}">Lista privada</RouterLink>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="eta-info">
|
<div class="eta-info">
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|||||||
@@ -200,7 +200,16 @@ const router = createRouter({
|
|||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
name: 'not-found',
|
name: 'not-found',
|
||||||
component: () => import('../views/dashboard/HomeView.vue'),
|
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'),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
66
src/stores/contacts.js
Normal file
66
src/stores/contacts.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { defineStore } from "pinia"
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
export const useContactsStore = defineStore('contacts', () => {
|
||||||
|
|
||||||
|
const contacts = ref([
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"company": "Altos",
|
||||||
|
"companyId": 929,
|
||||||
|
"rfc": "USKSK00101",
|
||||||
|
"category": "Agricola"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"company": "Altos logos",
|
||||||
|
"companyId": 2018,
|
||||||
|
"rfc": "USKSK0010a",
|
||||||
|
"category": "Agricola"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"company": "Bravos SA",
|
||||||
|
"companyId": 199,
|
||||||
|
"rfc": "UJSK8991",
|
||||||
|
"category": "Materiales"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"company": "Kolo",
|
||||||
|
"companyId": 1993,
|
||||||
|
"rfc": "JKDKD91001",
|
||||||
|
"category": "Servicios"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 39,
|
||||||
|
"company": "Altos",
|
||||||
|
"companyId": 929,
|
||||||
|
"rfc": "USKSK00101",
|
||||||
|
"category": "Agricola"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"company": "Altos",
|
||||||
|
"companyId": 929,
|
||||||
|
"rfc": "USKSK00101",
|
||||||
|
"category": "Agricola"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 934,
|
||||||
|
"company": "Altos",
|
||||||
|
"companyId": 929,
|
||||||
|
"rfc": "USKSK00101",
|
||||||
|
"category": "Agricola"
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const removeContact = (id) => {
|
||||||
|
contacts.value = contacts.value.filter((e) => e.id !== id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
contacts,
|
||||||
|
removeContact
|
||||||
|
}
|
||||||
|
});
|
||||||
54
src/views/contacts/ContactsView.vue
Normal file
54
src/views/contacts/ContactsView.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { useContactsStore } from '../../stores/contacts';
|
||||||
|
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 contactsStore = useContactsStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const loading = ref(false);
|
||||||
|
const query = ref('');
|
||||||
|
let timeout = null;
|
||||||
|
|
||||||
|
|
||||||
|
const search = () => {
|
||||||
|
console.log('Searching:', query.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
watch(query, (newValue) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
if (newValue.length >= 2) {
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
}, 400);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CustomSearchInput
|
||||||
|
:placeholder="t('carriers.searchByCarrier')"
|
||||||
|
v-model:saerch="query"
|
||||||
|
/>
|
||||||
|
<div class="row">
|
||||||
|
<div v-if="loading" class="d-flex justify-content-center">
|
||||||
|
<Spiner />
|
||||||
|
</div>
|
||||||
|
<ContactCard
|
||||||
|
v-else
|
||||||
|
v-for="contact in contactsStore.contacts"
|
||||||
|
:key="contact.id"
|
||||||
|
:contact="contact"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
90
src/views/contacts/components/ContactCard.vue
Normal file
90
src/views/contacts/components/ContactCard.vue
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useAuthStore } from '../../../stores/auth';
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
import { useContactsStore } from '../../../stores/contacts';
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
contact: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const contactsStore = useContactsStore();
|
||||||
|
|
||||||
|
const handleDeleteContact = async() => {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Eliminar transportista',
|
||||||
|
text: '¿Estas seguro de eliminar este transportista de la lista privada?',
|
||||||
|
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()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let resp = null;
|
||||||
|
setTimeout(() => {
|
||||||
|
resp = true
|
||||||
|
Swal.close();
|
||||||
|
if(resp != null) {
|
||||||
|
contactsStore.removeContact(id);
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Transportista eliminado',
|
||||||
|
text: 'Se ha eliminado el transportista de la lista privada',
|
||||||
|
icon: "success"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
title: t('errors.msgTitleNotDel'),
|
||||||
|
text: 'No se pudo completar la eliminación del transportista de la lista privada',
|
||||||
|
icon: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</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 }}</h2>
|
||||||
|
<button
|
||||||
|
v-if="(authStore.user?.job_role === 'owner' || authStore.user?.job_role === 'manager')"
|
||||||
|
class="btn-primary-sm bg-danger"
|
||||||
|
@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> {{contact.category}}</p>
|
||||||
|
<p><span class="font-bold">{{ t('directory.typeTruckNeed') }}: </span> Torton</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.flex1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -14,9 +14,9 @@
|
|||||||
import { useCompanyStore } from '../../../stores/company';
|
import { useCompanyStore } from '../../../stores/company';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import {getDateToLocal } from '../../../helpers/date_formats';
|
|
||||||
import { validateEmail } from '../../../helpers/validations';
|
import { validateEmail } from '../../../helpers/validations';
|
||||||
import AddressPreview from '../../../components/AddressPreview.vue';
|
import AddressPreview from '../../../components/AddressPreview.vue';
|
||||||
|
import CustomSwitch from '../../../components/CustomSwitch.vue';
|
||||||
|
|
||||||
|
|
||||||
const loadStore = useLoadsStore();
|
const loadStore = useLoadsStore();
|
||||||
@@ -42,7 +42,8 @@
|
|||||||
const destinationRef = ref('')
|
const destinationRef = ref('')
|
||||||
const emails = ref([]);
|
const emails = ref([]);
|
||||||
const emailInput = ref('');
|
const emailInput = ref('');
|
||||||
|
const isPrivate = ref(false);
|
||||||
|
|
||||||
const errors = ref({
|
const errors = ref({
|
||||||
segment: null,
|
segment: null,
|
||||||
truckType: null,
|
truckType: null,
|
||||||
@@ -605,6 +606,14 @@
|
|||||||
<div class="modal-footer custom-footer">
|
<div class="modal-footer custom-footer">
|
||||||
<Spiner v-if="isLoading"/>
|
<Spiner v-if="isLoading"/>
|
||||||
<div v-else class="btns-footer">
|
<div v-else class="btns-footer">
|
||||||
|
<div class="switch-container">
|
||||||
|
<CustomSwitch
|
||||||
|
v-if="loadStore?.currentLoad == null || loadStore.currentLoad?.status === 'Draft'"
|
||||||
|
label='Publicar como privada'
|
||||||
|
v-model="isPrivate"
|
||||||
|
name="contacts"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-danger"
|
class="btn btn-danger"
|
||||||
@@ -651,20 +660,22 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-msg {
|
.error-msg {
|
||||||
color: red;
|
color: red;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.btns-footer {
|
.btns-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chekmark {
|
.chekmark {
|
||||||
@@ -690,5 +701,15 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btns-footer {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-container {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
import { RouterLink } from 'vue-router';
|
import { RouterLink } from 'vue-router';
|
||||||
import { getDateMonthDay } from '../../../helpers/date_formats';
|
import { getDateMonthDay } from '../../../helpers/date_formats';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useAuthStore } from '../../../stores/auth';
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
company: {
|
company: {
|
||||||
@@ -10,6 +13,53 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
let existInPrivateList = ref(false);
|
||||||
|
|
||||||
|
const handleAddPrivateList = async() => {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Lista privadad',
|
||||||
|
text: '¿Estas seguro de añadir a este transportista de la lista privada?',
|
||||||
|
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()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let resp = null;
|
||||||
|
setTimeout(() => {
|
||||||
|
resp = true
|
||||||
|
Swal.close();
|
||||||
|
if(resp != null) {
|
||||||
|
// contactsStore.removeContact(id);
|
||||||
|
existInPrivateList.value = true;
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Transportista añadido',
|
||||||
|
text: 'Se ha añadido este transportista a la lista privada',
|
||||||
|
icon: "success"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
title: t('errors.msgTitleNotDel'),
|
||||||
|
text: 'No se pudo completar la añadir este transportista a la lista privada',
|
||||||
|
icon: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -30,9 +80,18 @@
|
|||||||
<p><span>{{ t('labels.infoCompany') }}: </span>{{company.company_description}}</p>
|
<p><span>{{ t('labels.infoCompany') }}: </span>{{company.company_description}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end">
|
<div
|
||||||
|
class="d-flex justify-content-end gap-2"
|
||||||
|
v-if="authStore.user?.permissions === 'role_shipper'">
|
||||||
|
<button
|
||||||
|
v-if="!existInPrivateList"
|
||||||
|
class="btn-primary-sm bg-dark radius-sm"
|
||||||
|
@click="handleAddPrivateList"
|
||||||
|
>
|
||||||
|
Añadir a lista privada
|
||||||
|
</button>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
class="btn-primary-sm"
|
class="btn-primary-sm radius-sm"
|
||||||
:to="{name: 'public-users', params: {id: company._id}}"
|
:to="{name: 'public-users', params: {id: company._id}}"
|
||||||
>{{ t('buttons.profile') }}</RouterLink>
|
>{{ t('buttons.profile') }}</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user