reorganizations of views & components
This commit is contained in:
@@ -1,86 +0,0 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { Bar } from 'vue-chartjs'
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale, ArcElement } from 'chart.js'
|
||||
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, ArcElement)
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
dataModel: {
|
||||
type: Array,
|
||||
},
|
||||
targetFind: {
|
||||
type: String
|
||||
},
|
||||
targetLabel: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
const chartData = ref(null);
|
||||
const dataMap = ref([]);
|
||||
onMounted(() => {
|
||||
|
||||
props.data.forEach(item => {
|
||||
const index = dataMap.value.findIndex((e) => e.label === item);
|
||||
|
||||
if(index === -1) {
|
||||
if(props.dataModel) {
|
||||
const itemModel = props.dataModel.find((e) => e[props.targetFind] === item);
|
||||
dataMap.value.push({
|
||||
label: (props.targetLabel) ? itemModel[props.targetLabel] : item,
|
||||
data: 1,
|
||||
...itemModel
|
||||
})
|
||||
} else {
|
||||
dataMap.value.push({
|
||||
label: item,
|
||||
data: 1,
|
||||
color: 'green'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
dataMap.value[index].data += 1;
|
||||
}
|
||||
});
|
||||
|
||||
chartData.value = {
|
||||
labels: dataMap.value.map((e) => (e.label.length > 12) ? e.label.substring(0, 11) : e.label),
|
||||
position: 'bottom',
|
||||
datasets: [{
|
||||
label: props.label,
|
||||
data: dataMap.value.map((e) => e.data),
|
||||
backgroundColor: dataMap.value.map((e) => e.color),
|
||||
}],
|
||||
}
|
||||
})
|
||||
|
||||
const id = computed(() => {
|
||||
return `my-chart-${props.label}`
|
||||
})
|
||||
|
||||
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Bar
|
||||
:id=id
|
||||
v-if="chartData"
|
||||
:options="chartOptions"
|
||||
:data="chartData"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,137 +0,0 @@
|
||||
<script setup>
|
||||
import Swal from 'sweetalert2';
|
||||
import { useCompanyStore } from '../stores/company';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed } from 'vue';
|
||||
import { formatCurrency } from '../helpers/format_currency';
|
||||
|
||||
const props = defineProps({
|
||||
budget: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
defineEmits(['set-budget'])
|
||||
|
||||
const companyStore = useCompanyStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const handleDeleteBudget = async() => {
|
||||
Swal.fire({
|
||||
title: t('calculator.titleDel'),
|
||||
text: t('calculator.textDel'),
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: t('buttons.delete'),
|
||||
cancelButtonText: t('buttons.cancel'),
|
||||
}).then(async(result) => {
|
||||
if(result.isConfirmed) {
|
||||
Swal.fire({
|
||||
title: t('messages.loading'),
|
||||
html: t('calculator.loadingDel'),// add html attribute if you want or remove
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading()
|
||||
},
|
||||
});
|
||||
const resp = await companyStore.deleteBudgetCompany(props.budget._id);
|
||||
Swal.close();
|
||||
if(resp !== null) {
|
||||
Swal.fire({
|
||||
title: t('calculator.msgTitleDel'),
|
||||
text: t('calculator.msgDel'),
|
||||
icon: "success"
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: t('calculator.msgTitleNotDel'),
|
||||
text: t('calculator.msgNotDel'),
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const totalPercentage = computed(() => {
|
||||
const percent = props.budget.total_profit / props.budget.total_before_tax * 100;
|
||||
const result = isNaN(percent) ? 0.0 : percent;
|
||||
return parseFloat(result).toFixed(2) + "%";
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card-fixed card-budget">
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-12">
|
||||
<p><span>{{ t('calculator.client') }}:</span> {{budget.client}}</p>
|
||||
<p v-if="budget.material"><span>{{ t('loads.product') }}:</span> {{budget.material.name}}</p>
|
||||
<p><span>{{ t('loads.origin') }}:</span> {{budget.origin}}</p>
|
||||
<p><span>{{ t('loads.destination') }}:</span> {{budget.destination}}</p>
|
||||
<p><span>{{ t('loads.truckType') }}:</span> {{budget.truck_type}}</p>
|
||||
<p><span>{{ t('calculator.totalkm') }}:</span> {{budget.total_km_travel}}</p>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-12">
|
||||
<p><span>{{ t('calculator.totallt') }}: </span> {{parseFloat( budget.total_fuel_consumed).toFixed(2)}}</p>
|
||||
<p><span>{{ t('calculator.totalcostDiesel') }}:</span> {{ formatCurrency( budget.total_cost_fuel) }}</p>
|
||||
<p><span>{{ t('calculator.totalBeforeIva') }}:</span> {{ formatCurrency(budget.total_before_tax) }}</p>
|
||||
<p><span>{{ t('calculator.totalUtityByKm') }}:</span> {{ formatCurrency( budget.total_utility_per_km) }}</p>
|
||||
<p><span>{{ t('calculator.totalUtity') }}:</span> {{ formatCurrency( budget.total_profit ) }}</p>
|
||||
<p><span>{{ t('calculator.percentUtility') }}:</span> {{ totalPercentage }}</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button
|
||||
class="btn btn-danger radius-sm"
|
||||
@click="handleDeleteBudget"
|
||||
>
|
||||
<i class="fa-solid fa-trash" /> <span class="clear-xsm">{{ t('buttons.delete') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn-primary-sm radius-sm"
|
||||
@click="$emit('set-budget', {budget: budget, print: false})"
|
||||
data-toggle="modal" data-target="#budgetModal"
|
||||
>
|
||||
<i class="fa-solid fa-pen-to-square" /> <span class="clear-xsm">{{ t('buttons.edit') }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-dark"
|
||||
@click="$emit('set-budget', {budget: budget, print: true})"
|
||||
data-toggle="modal" data-target="#budgetModal"
|
||||
>
|
||||
<i class="fa-solid fa-print" /> <span class="clear-xsm">{{ t('calculator.print') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-budget {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
p span {
|
||||
font-weight: bold;
|
||||
color: rgb(66, 46, 46);
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,77 +0,0 @@
|
||||
<script setup>
|
||||
import { RouterLink } from 'vue-router';
|
||||
import { getDateMonthDay } from '../helpers/date_formats';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
defineProps({
|
||||
company: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card-fixed flex-column">
|
||||
<h2>{{ company.company_name }}</h2>
|
||||
<div class="divider"></div>
|
||||
<div class="row mt-4">
|
||||
<div class="col-lg-6 col-sm-12">
|
||||
<p><span>{{ t('labels.codeId') }}:</span> {{ company.company_code }}</p>
|
||||
<p><span>{{ t('labels.dateMembership') }}: </span>{{getDateMonthDay(company.createdAt)}}</p>
|
||||
<p><span>{{ t('company.segment') }}:</span> {{ company._categories }}</p>
|
||||
</div>
|
||||
<div class="col-lg-6 col-sm-12">
|
||||
<p><span>{{ t('labels.locationLoadState') }}: </span>{{company._company_state}}</p>
|
||||
<p><span>{{ t('labels.locationLoadCity') }}: </span>{{company._company_city}}</p>
|
||||
<p><span>{{ t('labels.truckUsed') }}: </span>{{company._truck_types}}</p>
|
||||
<p><span>{{ t('labels.infoCompany') }}: </span>{{company.company_description}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<RouterLink
|
||||
class="btn-primary-sm"
|
||||
:to="{name: 'public-users', params: {id: company._id}}"
|
||||
>{{ t('buttons.profile') }}</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
h2{
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
color: #323030;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: #323032;
|
||||
}
|
||||
|
||||
p span {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
h2{
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #323030;
|
||||
}
|
||||
|
||||
|
||||
p {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 400;
|
||||
color: #323032;
|
||||
}
|
||||
p span {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,85 +0,0 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
defineProps({
|
||||
faq: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const open = ref(false);
|
||||
|
||||
const toggle = () => {
|
||||
open.value = !open.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card-faq">
|
||||
<div class="question-box" @click="toggle">
|
||||
<h3 class="question">{{ faq.question }}</h3>
|
||||
<i v-if="!open" class="fa-solid fa-chevron-down icon-indicator"></i>
|
||||
<i v-else class="fa-solid fa-chevron-up icon-indicator"></i>
|
||||
</div>
|
||||
<div v-if="open">
|
||||
<p
|
||||
v-if="faq.answer"
|
||||
class="answer" v-html="faq.answer"></p>
|
||||
<ol>
|
||||
<li class="step" v-for="step in faq.steps" v-html="step"></li>
|
||||
</ol>
|
||||
<p
|
||||
class="asnwer"
|
||||
v-if="faq.notes"
|
||||
>
|
||||
{{ faq.notes }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card-faq {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
background-color: white;
|
||||
margin-bottom: 15px;
|
||||
filter: drop-shadow(0px 4px 4px rgba(0, 255, 255,0.3));
|
||||
border-radius: 13px;
|
||||
}
|
||||
.question-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-faq:hover {
|
||||
background-color: aqua;
|
||||
}
|
||||
|
||||
.icon-indicator {
|
||||
font-size: 20px;
|
||||
color: #FBBA33;
|
||||
}
|
||||
.question {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 500;
|
||||
color: #323030;
|
||||
}
|
||||
|
||||
.answer {
|
||||
margin-top: 1rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: normal;
|
||||
color: #323030;
|
||||
}
|
||||
.step {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: normal;
|
||||
color: #323030;
|
||||
}
|
||||
</style>
|
||||
@@ -1,120 +0,0 @@
|
||||
<script setup>
|
||||
import Swal from 'sweetalert2';
|
||||
import { useCompanyStore } from '../stores/company';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const props = defineProps({
|
||||
location: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
defineEmits(['set-location'])
|
||||
|
||||
const companyStore = useCompanyStore();
|
||||
const authStore = useAuthStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const handleDeleteLocation = async() => {
|
||||
Swal.fire({
|
||||
title: t('directory.titleDel'),
|
||||
text: t('directory.textDel'),
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: t('buttons.delete'),
|
||||
cancelButtonText: t('buttons.cancel'),
|
||||
}).then(async(result) => {
|
||||
if(result.isConfirmed) {
|
||||
Swal.fire({
|
||||
title: t('messages.loading'),
|
||||
html: t('directory.loadingDel') + '..',// add html attribute if you want or remove
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading()
|
||||
},
|
||||
});
|
||||
|
||||
const resp = await companyStore.deleteLocationCompany(props.location._id)
|
||||
|
||||
Swal.close();
|
||||
|
||||
if(resp != null) {
|
||||
Swal.fire({
|
||||
title: t('directory.msgTitleDel'),
|
||||
text: t('directory.msgDel'),
|
||||
icon: "success"
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: t('errors.msgTitleNotDel'),
|
||||
text: t('directory.msgNotDel'),
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card-fixed card-location">
|
||||
<div>
|
||||
<p><span>{{ t('directory.name') }}:</span> {{location.branch_name}}</p>
|
||||
<p><span>{{ t('directory.address') }}:</span> <template v-if="location.address">{{location.address}}, </template><template v-if="location.zipcode">CP. {{location.zipcode}}, </template><template v-if="location.city">{{location.city}}, </template><template v-if="location.state">{{location.state}}</template></p>
|
||||
<p><span>{{ t('labels.phone') }}:</span> {{location.phone}}</p>
|
||||
<p><span>{{ t('directory.typeTruckNeed') }}:</span> {{location.truck_type?.map((e) => e).join(', ')}}</p>
|
||||
<p><span>{{ t('global.segments') }}:</span> {{location.categories?.map((e) => e.name).join(', ')}}</p>
|
||||
<p v-if="location.description"><span>{{ t('directory.additionalInfoLocation') }}:</span></p>
|
||||
<div v-if="location.description" class="box-note mb-4">
|
||||
{{ location.description }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer" v-if="authStore.user?.job_role === 'owner' || authStore.user?.job_role === 'manager'">
|
||||
<button
|
||||
class="btn btn-dark radius-sm"
|
||||
@click="handleDeleteLocation"
|
||||
>
|
||||
<i class="fa-solid fa-trash" /> <span class="clear-xsm">{{ t('buttons.delete') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn-primary-sm radius-sm"
|
||||
@click="$emit('set-location')"
|
||||
data-toggle="modal" data-target="#locationFormModal"
|
||||
>
|
||||
<i class="fa-solid fa-pen-to-square" /> <span class="clear-xsm">{{ t('buttons.edit') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card-location {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.box-note {
|
||||
padding: 12px 16px;
|
||||
background-color: aqua;
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
p span {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@@ -1,246 +0,0 @@
|
||||
<script setup>
|
||||
import Swal from 'sweetalert2';
|
||||
import { getDateOnly } from '../helpers/date_formats';
|
||||
import { getStatusLoad } from '../helpers/status';
|
||||
import { useCompanyStore } from '../stores/company';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useLoadsStore } from '../stores/loads';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const props = defineProps({
|
||||
proposal: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
})
|
||||
|
||||
const companyStore = useCompanyStore();
|
||||
const authStore = useAuthStore();
|
||||
const loadsStore = useLoadsStore();
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const handleWithdrawnProposal = async() => {
|
||||
|
||||
Swal.fire({
|
||||
title: t('proposals.titleRetireModal'),
|
||||
text: t('proposals.textRetireModal'),
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: t('proposals.confirmRetire'),
|
||||
cancelButtonText: 'No',
|
||||
}).then(async(result) => {
|
||||
if(result.isConfirmed) {
|
||||
Swal.fire({
|
||||
title: t('messages.loading'),
|
||||
html: t('proposals.loadingRetired') + '...',// add html attribute if you want or remove
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading()
|
||||
},
|
||||
});
|
||||
|
||||
let formData = {
|
||||
is_withdrawn : true
|
||||
}
|
||||
|
||||
let localData = {
|
||||
vehicle: props.proposal.vehicle,
|
||||
load: props.proposal.load
|
||||
}
|
||||
|
||||
const resp = await companyStore.updatePropsalLoad(props.proposal._id, formData, localData);
|
||||
|
||||
Swal.close();
|
||||
|
||||
if(resp != null) {
|
||||
Swal.fire({
|
||||
title: t('proposals.msgTitleRetire'),
|
||||
text: t('proposals.msgCancel'),
|
||||
icon: "success"
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: "Error",
|
||||
text: t('proposals.msgNotCancel'),
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
defineEmits(['set-proposal']);
|
||||
|
||||
const openAttachmentsModal = () => {
|
||||
loadsStore.currentLoad = props.proposal.load;
|
||||
loadsStore.openAttachmentsModal = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card-fixed card-load mt-4">
|
||||
<br>
|
||||
<div class="box-proposal">
|
||||
<div class="">
|
||||
<p v-if="proposal.vehicle"><span>{{ t('labels.codeId') }}:</span> {{proposal.vehicle.vehicle_code?.toUpperCase()}}</p>
|
||||
<p v-if="proposal.vehicle"><span>{{ t('global.segment') }}:</span> {{proposal.load.categories?.map((e) => e.name).join(', ')}}</p>
|
||||
<p v-if="proposal.vehicle"><span>{{ t('directory.typeTruck') }}:</span> {{proposal.vehicle.truck_type}}</p>
|
||||
<p v-if="proposal.vehicle?.published_date"><span>{{ t('labels.datePublished') }}:</span> {{ getDateOnly(proposal.vehicle.published_date) }}</p>
|
||||
<p v-if="proposal.vehicle?.is_available"><span>{{ t('labels.dateAvailable') }}:</span> {{ getDateOnly(proposal.vehicle.available_date) }}</p>
|
||||
<p v-if="proposal.vehicle?.is_available"><span>{{ t('vehicles.availableIn') }}:</span> {{proposal.vehicle.city}}<template v-if="proposal.vehicle.state">, {{proposal.vehicle.state}}</template></p>
|
||||
<p v-if="proposal.vehicle"><span>{{ t('loads.destination') }}:</span> {{proposal.vehicle?.destino.replace(';', ', ')}}</p>
|
||||
<p><span>{{ t('vehicles.truckPlates') }}:</span> {{ proposal.vehicle.circulation_serial_number }}</p>
|
||||
<p v-if="proposal.vehicle"><span>{{ t('vehicles.trailerPlates') }} 1:</span> {{proposal.vehicle.trailer_plate_1}}</p>
|
||||
<p v-if="proposal.vehicle"><span>{{ t('vehicles.trailerPlates') }} 2:</span> {{proposal.vehicle.trailer_plate_2}}</p>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<p v-if="proposal.load"> <span>{{ t('loads.loadCode') }}: </span>
|
||||
<span
|
||||
class="code-enruta"
|
||||
@click="$emit('set-proposal', {proposal: proposal, modal: 'detail'})"
|
||||
data-toggle="modal" data-target="#loadDetailModal"
|
||||
>{{proposal.load.shipment_code.toUpperCase()}}
|
||||
</span>
|
||||
</p>
|
||||
<p><span>{{ t('global.company') }}: </span>{{ proposal.load?.company?.company_name }}</p>
|
||||
<p>
|
||||
<span>{{t('loads.origin')}}: </span>
|
||||
<template v-if="proposal.load.origin?.company_name"> {{ proposal.load.origin?.company_name }}</template>
|
||||
<template v-if="proposal.load.origin?.street_address1">, {{ proposal.load.origin?.street_address1 }}</template>
|
||||
<template v-if="proposal.load.origin?.city">, {{ proposal.load.origin?.city }}</template>
|
||||
<template v-if="proposal.load.origin?.state">, {{ proposal.load.origin?.state }}</template>
|
||||
<template v-if="proposal.load.origin?.country">, {{ proposal.load.origin?.country }}</template>
|
||||
<template v-if="proposal.load.origin?.zipcode">, {{ proposal.load.origin?.zipcode }}</template>
|
||||
</p>
|
||||
<p>
|
||||
<span>{{t('loads.destination')}}: </span>
|
||||
<template v-if="proposal.load.destination?.company_name">{{ proposal.load.destination?.company_name }}</template>
|
||||
<template v-if="proposal.load.destination?.street_address1">, {{ proposal.load.destination?.street_address1 }}</template>
|
||||
<template v-if="proposal.load.destination?.city">, {{ proposal.load.destination?.city }}</template>
|
||||
<template v-if="proposal.load.destination?.state">, {{ proposal.load.destination?.state }}</template>
|
||||
<template v-if="proposal.load.destination?.country">, {{ proposal.load.destination?.country }}</template>
|
||||
<template v-if="proposal.load.destination?.zipcode">{{ proposal.load.destination?.zipcode }}</template>
|
||||
</p>
|
||||
<p v-if="proposal.vehicle" :style="{color: getStatusLoad(proposal.load, locale).color, fontWeight: 'bold'}"><span>{{ t('loads.loadStatus') }}:</span> {{ getStatusLoad(proposal.load, locale).status}}</p>
|
||||
<p v-if="proposal._driver">{{ t('labels.operator') }}: {{proposal._driver}}</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button
|
||||
type="button"
|
||||
data-toggle="modal" data-target="#attachmentModal"
|
||||
class="btn-primary-sm"
|
||||
@click="openAttachmentsModal"
|
||||
>
|
||||
<i class="fa-solid fa-image"></i>
|
||||
{{ t('evidence.evidence') }}
|
||||
</button>
|
||||
<div v-if="proposal.is_withdrawn" class="indicator-cancel">
|
||||
<i class="fa-solid fa-ban"></i>
|
||||
{{ t('proposals.retired') }}
|
||||
</div>
|
||||
<button
|
||||
v-else
|
||||
v-if="authStore.user?.job_role === 'owner' || authStore.user?.job_role === 'manager'"
|
||||
type="button"
|
||||
class="btn btn-danger radius-sm"
|
||||
@click="handleWithdrawnProposal"
|
||||
>
|
||||
<i class="fa-solid fa-ban"></i>
|
||||
{{ t('proposals.withdraw') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="authStore.user?.job_role === 'owner' || authStore.user?.job_role === 'manager'"
|
||||
class="btn-primary-sm radius-sm"
|
||||
@click="$emit('set-proposal', {proposal: proposal, modal: 'edit'})"
|
||||
data-toggle="modal" data-target="#makeProposalModal"
|
||||
><i class="fa-solid fa-pen-to-square"></i> {{ t('buttons.edit') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card-load {
|
||||
flex-direction: column;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.box-proposal {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.code-enruta {
|
||||
cursor: pointer;
|
||||
color: rgb(107, 107, 227);
|
||||
}
|
||||
|
||||
.code-enruta:hover{
|
||||
color: rgb(75, 75, 228);
|
||||
}
|
||||
|
||||
.tracking-icon {
|
||||
cursor: pointer;
|
||||
color: #f2a23f;
|
||||
}
|
||||
|
||||
.tracking-icon svg{
|
||||
height: 30px;
|
||||
}
|
||||
.tracking-icon:hover {
|
||||
color: #ddb380;;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.indicator-cancel {
|
||||
width: 120px;
|
||||
padding: 10px 12px;
|
||||
background: #FFF;
|
||||
/* border: 1px solid red; */
|
||||
border-radius: 50px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.tracking-icon svg:hover{
|
||||
height: 33px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: #323032;
|
||||
}
|
||||
|
||||
p span {
|
||||
color: #323032;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.btn-row {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: end;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.box-note {
|
||||
padding: 8px 16px;
|
||||
background-color: aqua;
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.box-proposal {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
.btn-row {
|
||||
gap: 0.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,155 +0,0 @@
|
||||
<script setup>
|
||||
import Swal from 'sweetalert2';
|
||||
import { getDateOnly } from '../helpers/date_formats';
|
||||
import { getTypeUser } from '../helpers/type_user';
|
||||
import { useCompanyStore } from '../stores/company';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import {computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
user: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
defineEmits(['set-user'])
|
||||
|
||||
const companyStore = useCompanyStore();
|
||||
const authStore = useAuthStore();
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
const handleDelete = async() => {
|
||||
Swal.fire({
|
||||
title: t('users.titleDel'),
|
||||
text: t('users.textDel'),
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: t('buttons.delete'),
|
||||
cancelButtonText: t('buttons.cancel'),
|
||||
}).then(async(result) => {
|
||||
if(result.isConfirmed) {
|
||||
Swal.fire({
|
||||
title: t('messages.loading'),
|
||||
html: t('users.loadingDel') + '...',// add html attribute if you want or remove
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading()
|
||||
},
|
||||
});
|
||||
const resp = await companyStore.deleteUserCompany(props.user._id)
|
||||
|
||||
Swal.close()
|
||||
|
||||
if(resp === 'success') {
|
||||
Swal.fire({
|
||||
title: t('users.msgTitleDel'),
|
||||
text: t('users.msgDel'),
|
||||
icon: "success"
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: t('users.msgTitleNotDel'),
|
||||
text: t('users.msgNotDel'),
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
const message = computed(() => `Hola, soy ${authStore.user.first_name} de ${authStore.user.company.company_name} y vi tu contacto en Eta Viaporte. Estoy interesado en tu oferta de ${authStore.user.company.company_type === 'Carrier' ? 'carga' : 'transporte'}. ${authStore.user.company.company_type === 'Carrier' ? '¿Podrías darme más detalles sobre el tipo de carga, el origen y destino?' : '¿Podrías proporcionarme más detalles sobre la disponibilidad, la ruta y las tarifas?'} Por favor, contáctame cuando puedas. ¡Gracias!`)
|
||||
|
||||
const whatsappLink = computed(() => `https://wa.me/${props.user.phone}?text=${message.value}`)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card-fixed flex-column">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-sm-12">
|
||||
<p><span>{{ t('labels.names') }}:</span> {{user.first_name}} {{user.last_name}}</p>
|
||||
<p v-if="user.employee_id"><span class="font-weight-bold mr-1">{{ t('labels.registryNumber') }}:</span> {{user.employee_id}}</p>
|
||||
<p><span>{{ t('labels.userRole') }}: </span>{{ getTypeUser(user.job_role, locale) }}</p>
|
||||
<p><span>{{ t('labels.phone1') }}: </span>
|
||||
{{user.phone}}
|
||||
<a v-if="readonly" :href="whatsappLink" target="_blank">
|
||||
<i class="fab fa-whatsapp whatsapp"></i> <!-- Usando FontAwesome para el icono de WhatsApp -->
|
||||
</a>
|
||||
</p>
|
||||
<!-- <p><span>Teléfono 2: </span>{{user.phone2}}</p> -->
|
||||
<p><span>{{ t('labels.email') }}: </span>{{user.email}}</p>
|
||||
</div>
|
||||
<div class="col-lg-6 col-sm-12" v-if="readonly">
|
||||
<p><span>{{ t('global.segments') }}: </span>{{user.categories?.map((e) => e.name).join(', ')}}</p>
|
||||
<p><span>{{ t('labels.locationLoadState') }}: </span>{{user._user_state}}</p>
|
||||
<p><span>{{ t('labels.locationLoadCity') }}: </span>{{user._user_city}}</p>
|
||||
<p v-if="user._truck_type"><span>{{ t('labels.truckUsed') }}: </span> {{user._truck_type}}</p>
|
||||
<p v-if="user.createAt"><span>{{ t('labels.memberSince') }}: </span>{{getDateOnly(user.createAt)}}</p>
|
||||
<p v-if="user.user_description"><span>{{ t('labels.userInfo') }}: </span> {{user.user_description}}</p>
|
||||
<!-- <p v-if="readonly && user.company.membership"><span>Tipo de afiliación: </span> {{user.company.membership}}</p> -->
|
||||
<!-- <p><span>{{ t('labels.userRole') }}: </span>{{user.job_role}}</p> -->
|
||||
</div>
|
||||
<div class="col-lg-6 col-sm-12" v-else>
|
||||
<p><span>{{ t('global.segments') }}: </span>{{user.categories?.map((e) => e.name).join(', ')}}</p>
|
||||
<p><span>{{ t('labels.locationLoadState') }}: </span>{{user.user_state?.join(', ')}}</p>
|
||||
<p><span>{{ t('labels.locationLoadCity') }}: </span>{{user.user_city?.join(', ')}}</p>
|
||||
<p v-if="user.truck_type"><span>{{ t('labels.truckUsed') }}: </span> {{user.truck_type?.join(', ')}}</p>
|
||||
<p v-if="user.createAt"><span>{{ t('labels.memberSince') }}: </span>{{getDateOnly(user.createAt)}}</p>
|
||||
<p v-if="user.user_description"><span>{{ t('labels.userInfo') }}: </span> {{user.user_description}}</p>
|
||||
<!-- <p v-if="readonly && user.company.membership" ><span>Tipo de afiliación: </span> {{user.company.membership}}</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row" v-if="readonly === false && (authStore.user?.job_role === 'owner' || authStore.user?.job_role === 'manager')">
|
||||
<button
|
||||
class="btn-primary-sm radius-sm"
|
||||
data-toggle="modal"
|
||||
data-target="#userModal"
|
||||
@click="$emit('set-user')"
|
||||
><i class="fa-solid fa-pen-to-square"></i> {{ t('buttons.edit') }}</button>
|
||||
<button
|
||||
class="btn btn-dark radius-sm"
|
||||
@click="handleDelete"
|
||||
><i class="fa-solid fa-trash"></i> {{ t('buttons.delete') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: #323032;
|
||||
}
|
||||
|
||||
p span {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.whatsapp {
|
||||
font-size: 1.45rem;
|
||||
align-items: center;
|
||||
color: green;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
p {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 400;
|
||||
color: #323032;
|
||||
}
|
||||
p span {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,601 +0,0 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import CustomInput from './ui/CustomInput.vue';
|
||||
import Products from './ui/Products.vue';
|
||||
import TruckTypes from './ui/TruckTypes.vue';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useCompanyStore } from '../stores/company';
|
||||
import Swal from 'sweetalert2';
|
||||
import Spiner from './ui/Spiner.vue';
|
||||
import Cities from './ui/Cities.vue';
|
||||
import html2pdf from 'html2pdf.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { formatCurrency } from '../helpers/format_currency';
|
||||
|
||||
const props = defineProps({
|
||||
budget: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
isPrint: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const companyStore = useCompanyStore();
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
onMounted(() => {
|
||||
if(props.budget) {
|
||||
budgetForm.budget_id = props.budget._id;
|
||||
budgetForm.client = props.budget.client;
|
||||
budgetForm.material = props.material?._id;
|
||||
budgetForm.origin = {
|
||||
city_name: props.budget.origin
|
||||
};
|
||||
budgetForm.destination = {
|
||||
city_name: props.budget.destination
|
||||
};
|
||||
budgetForm.budgetproduct = {
|
||||
name: props.budget.material?.name
|
||||
};
|
||||
|
||||
budgetForm.budgetTrucktypes = {
|
||||
meta_value: props.budget.truck_type
|
||||
}
|
||||
budgetForm.num_tons = props.budget.num_tons;
|
||||
budgetForm.price_per_ton = props.budget.price_per_ton;
|
||||
budgetForm.tonnage = props.budget.tonnage;
|
||||
budgetForm.pickup_distance = props.budget.pickup_distance;
|
||||
budgetForm.delivery_distance = props.budget.delivery_distance;
|
||||
budgetForm.warehouse_distance = props.budget.warehouse_distance;
|
||||
|
||||
budgetForm.cost_per_liter = props.budget.cost_per_liter;
|
||||
budgetForm.fuel_price_per_liter = props.budget.fuel_price_per_liter;
|
||||
budgetForm.other_fuel_expenses = props.budget.other_fuel_expenses;
|
||||
|
||||
budgetForm.driver_salary = props.budget.driver_salary;
|
||||
budgetForm.accomadation_allowance = props.budget.accomadation_allowance;
|
||||
budgetForm.other_administrative_expenses = props.budget.other_administrative_expenses;
|
||||
budgetForm.total_administrative_expenses = props.budget.total_administrative_expenses;
|
||||
|
||||
budgetForm.total_before_tax = props.budget.total_before_tax;
|
||||
budgetForm.total_utility_per_km = props.budget.total_utility_per_km;
|
||||
budgetForm.total_profit = props.budget.total_profit;
|
||||
budgetForm.profit_percentage = props.budget.profit_percentage;
|
||||
} else {
|
||||
Object.assign(budgetForm, initalState);
|
||||
}
|
||||
})
|
||||
|
||||
defineEmits(['reset-budget']);
|
||||
|
||||
const initalState = {
|
||||
client: '',
|
||||
material: '',
|
||||
origin: '',
|
||||
destination: '',
|
||||
num_tons: 0.0,
|
||||
price_per_ton: 0.0,
|
||||
tonnage: 0.0,
|
||||
pickup_distance: 0.0,
|
||||
delivery_distance: 0.0,
|
||||
warehouse_distance: 0.0,
|
||||
cost_per_liter: 0.0,
|
||||
fuel_price_per_liter: 0.0,
|
||||
other_fuel_expenses: 0.0,
|
||||
driver_salary: 0.0,
|
||||
accomadation_allowance: 0.0,
|
||||
other_administrative_expenses: 0.0,
|
||||
total_administrative_expenses: 0.0,
|
||||
total_before_tax: 0.0,
|
||||
total_utility_per_km: 0.0,
|
||||
total_profit: 0.0,
|
||||
profit_percentage: 0.0,
|
||||
total_travel: 0.0,
|
||||
total_fuel: 0.0,
|
||||
total_fuel_cost: 0.0,
|
||||
budgetproduct:[],
|
||||
budgetTrucktypes:[],
|
||||
}
|
||||
|
||||
const budgetForm = reactive({
|
||||
...initalState
|
||||
})
|
||||
|
||||
const pdfContent = ref(null);
|
||||
|
||||
const errors = ref({
|
||||
client: null
|
||||
})
|
||||
|
||||
const totalTravel = computed(() => {
|
||||
budgetForm.total_travel = budgetForm.warehouse_distance * 1 + budgetForm.delivery_distance * 1 + budgetForm.pickup_distance * 1;
|
||||
budgetForm.total_travel = isNaN(budgetForm.total_travel) ? 0.0 : budgetForm.total_travel;
|
||||
return budgetForm.total_travel;
|
||||
});
|
||||
|
||||
const totalFuel = computed(() => {
|
||||
budgetForm.total_fuel = isNaN(budgetForm.total_travel / budgetForm.cost_per_liter) ? 0.0 : (budgetForm.total_travel / budgetForm.cost_per_liter);
|
||||
return isNaN(budgetForm.total_fuel) ? '0.0' : parseFloat(budgetForm.total_fuel).toFixed(2);
|
||||
});
|
||||
|
||||
const totalFuelCost = computed(() => {
|
||||
budgetForm.total_fuel_cost = isNaN(budgetForm.total_fuel * budgetForm.fuel_price_per_liter) ? 0.0 : budgetForm.total_fuel * budgetForm.fuel_price_per_liter;
|
||||
return isNaN(budgetForm.total_fuel_cost) ? '0.0' : parseFloat(budgetForm.total_fuel_cost);
|
||||
});
|
||||
|
||||
const totalAdminExpenses = computed(() => {
|
||||
budgetForm.total_administrative_expenses = budgetForm.driver_salary*1 + budgetForm.accomadation_allowance*1 + budgetForm.other_administrative_expenses*1;
|
||||
budgetForm.total_administrative_expenses = isNaN(budgetForm.total_administrative_expenses) ? 0.0 : budgetForm.total_administrative_expenses;
|
||||
return isNaN(budgetForm.total_administrative_expenses) ? '0.0' : parseFloat(budgetForm.total_administrative_expenses);
|
||||
});
|
||||
|
||||
const totalBeforeTax = computed(() => {
|
||||
budgetForm.total_before_tax = budgetForm.tonnage * budgetForm.price_per_ton;
|
||||
budgetForm.total_before_tax = isNaN(budgetForm.total_before_tax) ? 0.0 : budgetForm.total_before_tax;
|
||||
return isNaN(budgetForm.total_before_tax) ? '0.0' : parseFloat(budgetForm.total_before_tax);
|
||||
});
|
||||
|
||||
const totalUtilityPerKm = computed(() => {
|
||||
budgetForm.total_utility_per_km = isNaN(budgetForm.total_before_tax / budgetForm.total_travel) ? 0.0 : (budgetForm.total_profit / budgetForm.total_travel);
|
||||
return isNaN(budgetForm.total_utility_per_km) ? '0.0' : parseFloat(budgetForm.total_utility_per_km);
|
||||
});
|
||||
|
||||
const totalProfit = computed(() => {
|
||||
budgetForm.total_profit = budgetForm.total_before_tax - budgetForm.total_fuel_cost - budgetForm.other_fuel_expenses - budgetForm.driver_salary - budgetForm.accomadation_allowance - budgetForm.other_administrative_expenses;
|
||||
return isNaN(budgetForm.total_profit) ? '0.0' : parseFloat(budgetForm.total_profit);
|
||||
});
|
||||
|
||||
const totalPercentage = computed(() => {
|
||||
budgetForm.profit_percentage = budgetForm.total_profit / budgetForm.total_before_tax * 100;
|
||||
budgetForm.profit_percentage = isNaN(budgetForm.profit_percentage) ? 0.0 : budgetForm.profit_percentage;
|
||||
return (isNaN(budgetForm.profit_percentage) ? '0.0' : parseFloat(budgetForm.profit_percentage).toFixed(2)) + "%";
|
||||
});
|
||||
|
||||
const saveBudget = async() => {
|
||||
validations();
|
||||
if(errors.value.client) return;
|
||||
let budgetData ={
|
||||
client: budgetForm.client,
|
||||
material: budgetForm.budgetproduct ? budgetForm.budgetproduct._id : null,
|
||||
origin: budgetForm.origin ? budgetForm.origin.city_name : null,
|
||||
destination: budgetForm.destination ? budgetForm.destination.city_name : null,
|
||||
truck_type: budgetForm.budgetTrucktypes ? budgetForm.budgetTrucktypes.meta_value : null,
|
||||
num_tons: budgetForm.num_tons,
|
||||
price_per_ton: budgetForm.price_per_ton,
|
||||
|
||||
tonnage: budgetForm.tonnage,
|
||||
pickup_distance: budgetForm.pickup_distance,
|
||||
delivery_distance: budgetForm.delivery_distance,
|
||||
warehouse_distance: budgetForm.warehouse_distance,
|
||||
total_km_travel: budgetForm.total_travel,
|
||||
|
||||
cost_per_liter: budgetForm.cost_per_liter,
|
||||
fuel_price_per_liter: budgetForm.fuel_price_per_liter,
|
||||
other_fuel_expenses: budgetForm.other_fuel_expenses,
|
||||
total_fuel_consumed: budgetForm.total_fuel,
|
||||
total_cost_fuel: budgetForm.total_fuel_cost,
|
||||
|
||||
driver_salary: budgetForm.driver_salary,
|
||||
accomadation_allowance: budgetForm.accomadation_allowance,
|
||||
other_administrative_expenses: budgetForm.other_administrative_expenses,
|
||||
total_administrative_expenses: budgetForm.total_administrative_expenses,
|
||||
|
||||
total_before_tax: budgetForm.total_before_tax,
|
||||
total_utility_per_km: budgetForm.total_utility_per_km,
|
||||
total_profit: budgetForm.total_profit,
|
||||
profit_percentage: (isNaN(budgetForm.profit_percentage) || isFinite(budgetForm.profit_percentage)) ? 0.0 : budgetForm.profit_percentage,
|
||||
company: authStore.user.company,
|
||||
}
|
||||
|
||||
let result = 'error';
|
||||
let action = t('calculator.msgCreate');
|
||||
loading.value = true;
|
||||
|
||||
const localData = {
|
||||
material: budgetForm.budgetproduct
|
||||
}
|
||||
|
||||
if(props.budget !== null) { // update
|
||||
result = await companyStore.updateBudgetCompany(props.budget._id, budgetData, localData);
|
||||
action = t('calculator.msgUpdate');
|
||||
} else { // create
|
||||
result = await companyStore.createBudgetCompany(budgetData);
|
||||
action = t('calculator.msgCreate');
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
if(result === 'success') {
|
||||
document.getElementById('btnClosebudgetModal').click();
|
||||
Swal.fire({
|
||||
title: action,
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: result,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const validations = () => {
|
||||
errors.value = {
|
||||
client: budgetForm.client?.length < 2 ? t('errors.customer') : null,
|
||||
};
|
||||
}
|
||||
|
||||
const downloadPdf = () => {
|
||||
const options = {
|
||||
margin: 10,
|
||||
// filename: 'mi_seccion.pdf',
|
||||
filename: `presupuesto_${props.budget.client.replaceAll(' ', '_')}.pdf`,
|
||||
image: { type: 'jpeg', quality: 0.98 },
|
||||
html2canvas: { scale: 2 },
|
||||
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
|
||||
};
|
||||
|
||||
html2pdf(pdfContent.value, options);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" id="budgetModal" tabindex="-1" role="dialog" aria-labelledby="editcompany" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div></div>
|
||||
<button
|
||||
id="btnClosebudgetModal"
|
||||
type="button"
|
||||
@click="$emit('reset-budget')"
|
||||
class="close bg-white" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body form-content">
|
||||
<div class="container-fluid">
|
||||
<form action="" @submit.prevent="saveBudget" autocomplete="off" method="post" ref="pdfContent">
|
||||
<div class="calculator-card">
|
||||
<div class="calculator-card__title">
|
||||
<h2>{{ t('calculator.titleDoc') }}</h2>
|
||||
<img src="/images/logo-eta.png" alt="Eta viaporte" width="110">
|
||||
</div>
|
||||
<div class="calculator-card__subtitle">
|
||||
<h4>{{ t('calculator.loadData') }}</h4>
|
||||
</div>
|
||||
<div class="calculator-card__body">
|
||||
<div class="calculator-card__loads_data">
|
||||
<CustomInput
|
||||
:label="t('calculator.client') + '*'"
|
||||
type="text"
|
||||
:filled="false"
|
||||
name="client"
|
||||
:error="errors.client"
|
||||
v-model:field="budgetForm.client"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('loads.product') }}</label>
|
||||
<Products
|
||||
v-model="budgetForm.budgetproduct"
|
||||
:disabled="isPrint"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('loads.origin') }}</label>
|
||||
<Cities
|
||||
v-model="budgetForm.origin"
|
||||
:disabled="isPrint"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('loads.destination') }}</label>
|
||||
<Cities
|
||||
v-model="budgetForm.destination"
|
||||
:disabled="isPrint"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('loads.truckType') }}</label>
|
||||
<TruckTypes
|
||||
v-model="budgetForm.budgetTrucktypes"
|
||||
:disabled="isPrint"
|
||||
/>
|
||||
</div>
|
||||
<CustomInput
|
||||
:label="t('labels.tons')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="num_tons"
|
||||
v-model:field="budgetForm.num_tons"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('labels.priceTons')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="price_per_ton"
|
||||
v-model:field="budgetForm.price_per_ton"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="calculator-card__subtitle">
|
||||
<h4>{{ t('calculator.ratekm') }}</h4>
|
||||
</div>
|
||||
<div class="calculator-card__body">
|
||||
<div class="calculator-card__loads_data">
|
||||
<CustomInput
|
||||
:label="t('labels.tonLoad')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="tonnage"
|
||||
v-model:field="budgetForm.tonnage"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('labels.priceTons')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="price_per_ton"
|
||||
v-model:field="budgetForm.price_per_ton"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('labels.originTruckKm1')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="pickup_distance"
|
||||
v-model:field="budgetForm.pickup_distance"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('labels.originTruckKm2')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="delivery_distance"
|
||||
v-model:field="budgetForm.delivery_distance"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('labels.destinationTruck1')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="warehouse_distance"
|
||||
v-model:field="budgetForm.warehouse_distance"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<div class="calculator-card__totals">
|
||||
<div>
|
||||
<h6>{{ t('calculator.totalkm').toUpperCase() }}</h6>
|
||||
</div>
|
||||
<div>
|
||||
{{ totalTravel }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="calculator-card__subtitle">
|
||||
<h4>{{ t('calculator.costTruck') }}</h4>
|
||||
</div>
|
||||
<div class="calculator-card__body">
|
||||
<div class="calculator-card__loads_data">
|
||||
<CustomInput
|
||||
:label="t('calculator.truckCost1')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="cost_per_liter"
|
||||
v-model:field="budgetForm.cost_per_liter"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('calculator.truckCost2')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="fuel_price_per_liter"
|
||||
v-model:field="budgetForm.fuel_price_per_liter"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('calculator.otherSpends')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="other_fuel_expenses"
|
||||
v-model:field="budgetForm.other_fuel_expenses"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<div class="calculator-card__totals">
|
||||
<div>
|
||||
<h6>{{ t('calculator.totallt').toLocaleUpperCase() }}</h6>
|
||||
</div>
|
||||
<div>
|
||||
{{ totalFuel }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="calculator-card__totals">
|
||||
<div>
|
||||
<h6>{{ t('calculator.truckCost4').toLocaleUpperCase() }}</h6>
|
||||
</div>
|
||||
<div>
|
||||
{{ formatCurrency(totalFuelCost) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="calculator-card__subtitle">
|
||||
<h4>{{ t('calculator.operatorSalary') }}</h4>
|
||||
</div>
|
||||
<div class="calculator-card__body">
|
||||
<div class="calculator-card__loads_data">
|
||||
<CustomInput
|
||||
:label="t('calculator.salaryAdmin')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="driver_salary"
|
||||
v-model:field="budgetForm.driver_salary"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('calculator.stands')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="accomadation_allowance"
|
||||
v-model:field="budgetForm.accomadation_allowance"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('calculator.otherSpends')"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="other_administrative_expenses"
|
||||
v-model:field="budgetForm.other_administrative_expenses"
|
||||
:readonly="isPrint"
|
||||
/>
|
||||
<div class="calculator-card__totals">
|
||||
<div>
|
||||
<h6>{{ t('calculator.spendsAdminTotal').toUpperCase() }}</h6>
|
||||
</div>
|
||||
<div>
|
||||
{{ formatCurrency(totalAdminExpenses) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="calculator-card__subtitle">
|
||||
<h4>{{ t('calculator.utilityLoss') }}</h4>
|
||||
</div>
|
||||
<div class="calculator-card__body">
|
||||
<div class="calculator-card__loads_data">
|
||||
<div class="calculator-card__totals">
|
||||
<div>
|
||||
<h6>{{ t('calculator.totalBeforeIva').toUpperCase() }}</h6>
|
||||
</div>
|
||||
<div>
|
||||
{{ formatCurrency(totalBeforeTax) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="calculator-card__totals">
|
||||
<div>
|
||||
<h6>{{ t('calculator.totalUtityByKm').toUpperCase() }}</h6>
|
||||
</div>
|
||||
<div>
|
||||
{{ formatCurrency(totalUtilityPerKm) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="calculator-card__totals">
|
||||
<div>
|
||||
<h6>{{ t('calculator.totalUtity').toUpperCase() }}</h6>
|
||||
</div>
|
||||
<div>
|
||||
{{ formatCurrency(totalProfit) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="calculator-card__totals">
|
||||
<div>
|
||||
<h6>{{ t('calculator.percentUtility').toUpperCase() }}</h6>
|
||||
</div>
|
||||
<div>
|
||||
{{ totalPercentage }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 text-center" v-if="!isPrint">
|
||||
<Spiner v-if="loading"/>
|
||||
<button v-else class="btn-primary-sm radius-sm" type="submit">{{ t('buttons.save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-4 text-center" v-if="isPrint">
|
||||
<button
|
||||
class="btn-primary-sm radius-sm"
|
||||
@click="downloadPdf"
|
||||
type="button">
|
||||
{{ t('buttons.download') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.form-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
border: 1px solid grey;
|
||||
}
|
||||
|
||||
.header-style {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.calculator-card {
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
width: 85%;
|
||||
border: 1px solid grey;
|
||||
}
|
||||
|
||||
.calculator-card__title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
background-color: #323030;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
color: #FFF;
|
||||
text-align: center;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.calculator-card__subtitle {
|
||||
background-color: #FBBA33;
|
||||
padding: 8px 20px;
|
||||
color: #FFF;
|
||||
text-align: center;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.calculator-card__loads_data {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
.calculator-card__totals {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.calculator-card {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.calculator-card {
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,341 +0,0 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import CustomInput from './ui/CustomInput.vue';
|
||||
import Segments from './ui/Segments.vue';
|
||||
import TruckTypes from './ui/TruckTypes.vue';
|
||||
import States from './ui/States.vue';
|
||||
import Cities from './ui/Cities.vue';
|
||||
import Spiner from './ui/Spiner.vue';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useCompanyStore } from '../stores/company';
|
||||
import Swal from 'sweetalert2';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { validateEmail } from '../helpers/validations';
|
||||
|
||||
const props = defineProps({
|
||||
location: {
|
||||
type: Object,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
|
||||
defineEmits(['reset-location']);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const companyStore = useCompanyStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(false);
|
||||
const emails = ref([]);
|
||||
const emailInput = ref('');
|
||||
|
||||
const title = computed(() => {
|
||||
return (props.location) ? t('directory.editLocation') : t('directory.createLocation');
|
||||
});
|
||||
|
||||
const initState = {
|
||||
branch_name: "",
|
||||
phone: "",
|
||||
categories: [],
|
||||
city: "",
|
||||
state: "",
|
||||
location_type: "",
|
||||
truck_type: [],
|
||||
address: "",
|
||||
description: "",
|
||||
zipcode: ""
|
||||
}
|
||||
|
||||
const errors = ref({
|
||||
branch_name: null,
|
||||
address: null,
|
||||
city: null,
|
||||
state: null,
|
||||
location_type: null,
|
||||
zipcode: null
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if(props.location) {
|
||||
locationForm.branch_name = props.location.branch_name;
|
||||
locationForm.phone = props.location.phone;
|
||||
locationForm.categories = props.location.categories;
|
||||
locationForm.city = {
|
||||
city_name: props.location.city
|
||||
};
|
||||
locationForm.state = {
|
||||
state_name: props.location.state
|
||||
};
|
||||
locationForm.truck_type = props.location.truck_type?.map((e) => {
|
||||
return {
|
||||
meta_value: e
|
||||
}
|
||||
});
|
||||
locationForm.location_type = props.location.type;
|
||||
locationForm.zipcode = props.location.zipcode;
|
||||
locationForm.address = props.location.address;
|
||||
locationForm.description = props.location.description;
|
||||
locationForm.zipcode = props.location.zipcode;
|
||||
emails.value = props.location.alert_list || [];
|
||||
} else {
|
||||
Object.assign(locationForm, initState);
|
||||
}
|
||||
})
|
||||
|
||||
const locationForm = reactive({
|
||||
...initState
|
||||
});
|
||||
|
||||
const saveLocation = async() => {
|
||||
validations();
|
||||
if(errors.value.branch_name || errors.value.address || errors.value.city || errors.value.state || errors.value.zipcode || errors.value.location_type) {
|
||||
return;
|
||||
} else {
|
||||
|
||||
const branchData ={
|
||||
branch_name: locationForm.branch_name,
|
||||
phone: locationForm.phone,
|
||||
categories: locationForm.categories?.length <= 0 ? null : locationForm.categories?.map((e) => e._id),
|
||||
city: locationForm.city.city_name,
|
||||
state: locationForm.state.state_name,
|
||||
address: locationForm.address,
|
||||
truck_type: locationForm.truck_type?.length <= 0 ? null : locationForm.truck_type?.map((e) => e.meta_value),
|
||||
description: locationForm.description,
|
||||
company: authStore.user.company,
|
||||
type: locationForm.location_type,
|
||||
zipcode: locationForm.zipcode,
|
||||
alert_list: emails.value.length > 0 ? emails.value : null
|
||||
}
|
||||
|
||||
let result = 'error';
|
||||
let action = t('directory.msgLocationCreated');
|
||||
loading.value = true;
|
||||
|
||||
const localData = {
|
||||
categories: locationForm.categories,
|
||||
}
|
||||
|
||||
if(props.location !== null) {
|
||||
// Se actualiza
|
||||
result = await companyStore.updateLocationCompany(props.location._id, branchData, localData);
|
||||
action = t('directory.msgLocationUpdate');
|
||||
} else {
|
||||
// Se crea
|
||||
result = await companyStore.createLocationCompany(branchData, localData);
|
||||
action = t('directory.msgLocationCreated');
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
if(result === 'success') {
|
||||
document.getElementById('btnCloseLocationFormModal').click();
|
||||
Swal.fire({
|
||||
title: `<strong>${action}</strong>`,
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: result,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
errors.value = {
|
||||
branch_name: locationForm.branch_name.length < 2 ? t('errors.nameRequired') : null,
|
||||
address: locationForm.address.length <= 4 ? t('errors.address') : null,
|
||||
city: locationForm.city.length <= 0 ? t('errors.city') : null,
|
||||
state: locationForm.state.length <= 0 ? t('errors.state') : null,
|
||||
location_type: locationForm.location_type.length <= 0 ? t('errors.directoryType') : null,
|
||||
zipcode: locationForm.zipcode.length < 5 ? t('errors.zipcode') : null,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" id="locationFormModal" tabindex="-1" role="dialog" aria-labelledby="locationFormModal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="title mt-2 mb-3">{{ title }}</h2>
|
||||
<button
|
||||
id="btnCloseLocationFormModal"
|
||||
type="button"
|
||||
@click="$emit('reset-location')"
|
||||
class="close bg-white" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body form-content px-5">
|
||||
<form @submit.prevent="saveLocation" autocomplete="off" method="post" ref="formRef">
|
||||
<CustomInput
|
||||
:label="t('directory.name') + '*'"
|
||||
name="name"
|
||||
v-model:field="locationForm.branch_name"
|
||||
:filled="false"
|
||||
:error="errors.branch_name"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('directory.address') + '*'"
|
||||
name="address"
|
||||
v-model:field="locationForm.address"
|
||||
:filled="false"
|
||||
:error="errors.address"
|
||||
/>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label required">{{ t('directory.state')}}*</label>
|
||||
<States
|
||||
v-model="locationForm.state"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.state">{{ errors.state }}</span>
|
||||
</div>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label required">{{ t('directory.city')}}*</label>
|
||||
<Cities
|
||||
v-model="locationForm.city"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.city">{{ errors.city }}</span>
|
||||
</div>
|
||||
<div class="d-flex flex-column mb-4">
|
||||
<label class="custom-label required" for="role">{{ t('directory.typeDirectory') + '*'}}</label>
|
||||
<select
|
||||
class="custom-input-light"
|
||||
name="type"
|
||||
id="type"
|
||||
v-model="locationForm.location_type"
|
||||
>
|
||||
<option disabled value="">-- {{ t('labels.select')}} --</option>
|
||||
<!-- <option value="owner">Dueño</option> -->
|
||||
<option value="loading">{{ t('labels.load')}}</option>
|
||||
<option value="unloading">{{ t('labels.download')}}</option>
|
||||
<option value="both">{{ t('labels.both')}}</option>
|
||||
</select>
|
||||
<span class="error-msg" v-if="errors.location_type">{{ errors.location_type }}</span>
|
||||
</div>
|
||||
<CustomInput
|
||||
:label="t('directory.zipCode') + '*'"
|
||||
name="zipcode"
|
||||
type="number"
|
||||
:step="1"
|
||||
v-model:field="locationForm.zipcode"
|
||||
:filled="false"
|
||||
:error="errors.zipcode"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('labels.phone')"
|
||||
name="phone"
|
||||
type="number"
|
||||
v-model:field="locationForm.phone"
|
||||
:filled="false"
|
||||
/>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('global.segments') }}</label>
|
||||
<Segments
|
||||
v-model="locationForm.categories"
|
||||
:multiple="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('directory.typeTruck') }}</label>
|
||||
<TruckTypes
|
||||
v-model="locationForm.truck_type"
|
||||
:multiple="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
<label class="custom-label" for="description">{{ t('directory.additionalInfoLocation') }}</label>
|
||||
<textarea
|
||||
class="custom-input-light"
|
||||
name="description"
|
||||
id="description"
|
||||
placeholder="Escribe aqui..."
|
||||
v-model="locationForm.description"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="divider mt-4"></div>
|
||||
<div class="box-observers mt-4">
|
||||
<div class="box-input">
|
||||
<div class="input-observer">
|
||||
<CustomInput
|
||||
label="Agregar correo de bodega"
|
||||
name="email"
|
||||
type="email"
|
||||
v-model:field="emailInput"
|
||||
:filled="false"
|
||||
:tooltip="t('messages.observerWarehouse')"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@click="addObserver"
|
||||
class="btn-leading-input mb-4"
|
||||
>
|
||||
Agregar
|
||||
</button>
|
||||
</div>
|
||||
<h5 v-if="emails.length > 0">Lista correos de usuarios de bodega:</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">
|
||||
<Spiner v-if="loading"/>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-dark" type="submit">{{ t('buttons.save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-dark"
|
||||
@click="$emit('reset-location')"
|
||||
data-dismiss="modal">{{ t('buttons.close') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,356 +0,0 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import CustomInput from './ui/CustomInput.vue';
|
||||
import States from './ui/States.vue';
|
||||
import Cities from './ui/Cities.vue';
|
||||
import Segments from './ui/Segments.vue';
|
||||
import TruckTypes from './ui/TruckTypes.vue';
|
||||
import Spiner from './ui/Spiner.vue';
|
||||
import { validateEmail } from '../helpers/validations';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useCompanyStore } from '../stores/company';
|
||||
import Swal from 'sweetalert2';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const props = defineProps({
|
||||
user: {
|
||||
type: Object,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const authStore = useAuthStore();
|
||||
const companyStore = useCompanyStore();
|
||||
|
||||
onMounted(async() => {
|
||||
if(props.user) {
|
||||
userForm.job_role = props.user.job_role;
|
||||
userForm.name = props.user.first_name;
|
||||
userForm.last_name = props.user.last_name;
|
||||
userForm.email = props.user.email;
|
||||
userForm.phone = props.user.phone;
|
||||
userForm.categories = props.user.categories;
|
||||
userForm.user_city = props.user.user_city?.map(m =>{
|
||||
return { city_name: m };
|
||||
});
|
||||
userForm.user_state = props.user.user_state?.map(m =>{
|
||||
return { state_name:m };
|
||||
});
|
||||
userForm.truck_type = props.user.truck_type?.map(m =>{
|
||||
return { meta_value:m };
|
||||
});
|
||||
userForm.user_description = props.user.user_description;
|
||||
if(props.user.job_role === 'warehouse') {
|
||||
await loadWarehouses();
|
||||
userForm.warehouse = warehouses.value.find(w => w._id === props.user.branch?._id);
|
||||
}
|
||||
} else {
|
||||
Object.assign(userForm, initState);
|
||||
}
|
||||
})
|
||||
|
||||
const initState = {
|
||||
name: '',
|
||||
last_name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
// phone2: '',
|
||||
job_role: '',
|
||||
categories: [],
|
||||
user_city: [],
|
||||
user_state: [],
|
||||
truck_type: [],
|
||||
user_description: '',
|
||||
warehouse: null
|
||||
};
|
||||
|
||||
const userForm = reactive({
|
||||
...initState
|
||||
})
|
||||
|
||||
const errors = ref({
|
||||
name: null,
|
||||
last_name: null,
|
||||
email: null,
|
||||
phone: null,
|
||||
job_role: null,
|
||||
warehouse: null
|
||||
})
|
||||
|
||||
const formRef = ref(null);
|
||||
const loading = ref(false);
|
||||
const loadingWarehouses = ref(false);
|
||||
const warehouses = ref([]);
|
||||
|
||||
const title = computed(() => {
|
||||
return (props.user) ? t('labels.editUser') : t('labels.createUser');
|
||||
});
|
||||
|
||||
defineEmits(['reset-user']);
|
||||
|
||||
const saveUser = async() => {
|
||||
validations()
|
||||
if(errors.value.name || errors.value.last_name || errors.value.email || errors.value.phone || errors.value.job_role || errors.value.warehouse) {
|
||||
return;
|
||||
} else {
|
||||
let userData ={
|
||||
first_name: userForm.name,
|
||||
last_name: userForm.last_name,
|
||||
email: userForm.email,
|
||||
phone: userForm.phone,
|
||||
job_role: userForm.job_role,
|
||||
permissions: authStore.user.permissions,
|
||||
company: authStore.user.company,
|
||||
categories: userForm.categories?.length <= 0 ? null : userForm.categories?.map((e) => e._id),
|
||||
user_city: userForm.user_city?.length <= 0 ? null : userForm.user_city?.map((e) => e.city_name),
|
||||
user_state: userForm.user_state?.length <= 0 ? null : userForm.user_state?.map((e) => e.state_name),
|
||||
truck_type: userForm.truck_type?.length <= 0 ? null : userForm.truck_type?.map((e) => e.meta_value),
|
||||
user_description: userForm.user_description,
|
||||
branch: userForm.warehouse?._id
|
||||
}
|
||||
|
||||
const dataUpdate = {
|
||||
categories: userForm.categories,
|
||||
name: userForm.name + ' ' + userForm.last_name,
|
||||
branch: userForm.warehouse
|
||||
}
|
||||
|
||||
let result = 'error';
|
||||
let action = t('messages.createdUser');
|
||||
loading.value = true;
|
||||
if(props.user !== null) {
|
||||
// Se actualiza
|
||||
result = await companyStore.updateUserCompany(props.user._id, userData, dataUpdate);
|
||||
action = t('messages.updatedUser');
|
||||
} else {
|
||||
// Se crea
|
||||
result = await companyStore.createUserCompany(userData, dataUpdate);
|
||||
action = t('messages.createdUser');
|
||||
}
|
||||
loading.value = false;
|
||||
if(result === 'success') {
|
||||
document.getElementById('btnCloseuserModal').click();
|
||||
Swal.fire({
|
||||
title: `<strong>${action}</strong>`,
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: result,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validations = () => {
|
||||
errors.value = {
|
||||
name: userForm.name.length < 4 ? t('errors.name') : null,
|
||||
last_name: userForm.last_name.length <= 1 ? t('errors.lastname') : null,
|
||||
email: !validateEmail(userForm.email) ? t('errors.email') : null,
|
||||
phone: userForm.phone.length < 10 ? t('errors.phone') : null,
|
||||
job_role: userForm.job_role === '' ? t('errors.required') : null,
|
||||
warehouse: (userForm.job_role == 'warehouse' && userForm.warehouse === null)
|
||||
? t('errors.required')
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
const handleRoleChange = async () => {
|
||||
if (userForm.job_role === "warehouse") {
|
||||
await loadWarehouses();
|
||||
}
|
||||
}
|
||||
|
||||
const loadWarehouses = async () => {
|
||||
loadingWarehouses.value = true;
|
||||
warehouses.value = await companyStore.getLocationsLoads('loading');
|
||||
loadingWarehouses.value = false;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" id="userModal" tabindex="-1" role="dialog" aria-labelledby="userModal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="title mt-2 mb-3">{{ title }}</h2>
|
||||
<button
|
||||
id="btnCloseuserModal"
|
||||
type="button"
|
||||
@click="$emit('reset-user')"
|
||||
class="close bg-white" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body form-content">
|
||||
<form @submit.prevent="saveUser" autocomplete="off" method="post" ref="formRef">
|
||||
<CustomInput
|
||||
:label="t('labels.names') + '*'"
|
||||
name="name"
|
||||
v-model:field="userForm.name"
|
||||
:filled="false"
|
||||
:error="errors.name"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('labels.lastnames') + '*'"
|
||||
name="lastname"
|
||||
v-model:field="userForm.last_name"
|
||||
:filled="false"
|
||||
:error="errors.last_name"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('labels.phone') + '*'"
|
||||
name="phone1"
|
||||
type="number"
|
||||
v-model:field="userForm.phone"
|
||||
:filled="false"
|
||||
:step="1"
|
||||
|
||||
:error="errors.phone"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('labels.email') + '*'"
|
||||
name="email"
|
||||
type="email"
|
||||
v-model:field="userForm.email"
|
||||
:readonly="(props.user) ? true : false"
|
||||
:filled="false"
|
||||
:error="errors.email"
|
||||
/>
|
||||
<div
|
||||
v-if="userForm.job_role !== 'owner'"
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<label class="custom-label required" for="role">{{ t('labels.userRole') }}*</label>
|
||||
<select
|
||||
class="custom-input-light"
|
||||
name="role"
|
||||
id="role"
|
||||
v-model="userForm.job_role"
|
||||
@change="handleRoleChange"
|
||||
>
|
||||
<option disabled value="">-- {{ t('labels.selectedRol') }} --</option>
|
||||
<option value="manager">{{ t('labels.manager') }}</option>
|
||||
<option value="staff">{{ t('labels.staff') }}</option>
|
||||
<!-- <option
|
||||
v-if="authStore.user?.permissions === 'role_shipper'"
|
||||
value="warehouse"
|
||||
>
|
||||
{{ t('labels.warehouse') }}
|
||||
</option> -->
|
||||
<option
|
||||
v-if="authStore.user?.permissions === 'role_carrier'"
|
||||
value="driver"
|
||||
>
|
||||
{{ t('labels.driver') }}
|
||||
</option>
|
||||
</select>
|
||||
<span class="error-msg" v-if="errors.job_role">{{ errors.job_role }}</span>
|
||||
</div>
|
||||
<!-- <div v-if="userForm.job_role === 'warehouse'">
|
||||
<Spiner v-if="loadingWarehouses"/>
|
||||
<div class="d-flex flex-column my-4" v-if="!loadingWarehouses">
|
||||
<label class="custom-label required" for="locationLoad">{{ t('labels.warehouses') }}*</label>
|
||||
<select
|
||||
class="custom-input-light"
|
||||
name="locationLoad"
|
||||
id="locationLoad"
|
||||
v-model="userForm.warehouse"
|
||||
>
|
||||
<option disabled value="null">-- {{ t('labels.warehouseSelect') }} --</option>
|
||||
<option v-for="loc in warehouses" :value="loc">{{ loc.branch_name }}</option>
|
||||
</select>
|
||||
<span class="error-msg" v-if="errors.warehouse">{{ errors.warehouse }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- <div v-else> -->
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('global.segments') }}</label>
|
||||
<Segments
|
||||
v-model="userForm.categories"
|
||||
:multiple="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('directory.typeTruck') }}</label>
|
||||
<TruckTypes
|
||||
v-model="userForm.truck_type"
|
||||
:multiple="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('labels.locationLoadState') }}</label>
|
||||
<States
|
||||
v-model="userForm.user_state"
|
||||
:multiple="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('labels.locationLoadCity') }}</label>
|
||||
<Cities
|
||||
v-model="userForm.user_city"
|
||||
:multiple="true"
|
||||
/>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
<div class="d-flex flex-column">
|
||||
<label class="custom-label" for="description">{{ t('labels.userInfo') }}</label>
|
||||
<textarea
|
||||
class="custom-input-light"
|
||||
name="description"
|
||||
id="description"
|
||||
placeholder="Escribe aqui..."
|
||||
v-model="userForm.user_description"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="box-info" v-if="!props.user">
|
||||
<div>
|
||||
<i class="fa-solid fa-circle-info info"></i>
|
||||
</div>
|
||||
<span v-html="t('messages.msgCreatedUser')"></span>
|
||||
</div>
|
||||
<div class="mt-4 text-center">
|
||||
<Spiner v-if="loading"/>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-dark" type="submit">{{t('buttons.save')}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-dark"
|
||||
@click="$emit('reset-user')"
|
||||
data-dismiss="modal">{{t('buttons.close')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form-content {
|
||||
margin: 0 auto;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.box-info {
|
||||
margin-top: 20px;
|
||||
align-items: center;
|
||||
background-color: aqua;
|
||||
display: flex;
|
||||
padding: 16px 24px;
|
||||
border-radius: 13px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 32px;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,283 +0,0 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import CustomInput from './ui/CustomInput.vue';
|
||||
import TruckTypes from './ui/TruckTypes.vue';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import Segments from './ui/Segments.vue';
|
||||
import States from './ui/States.vue';
|
||||
import Cities from './ui/Cities.vue';
|
||||
import Spiner from './ui/Spiner.vue';
|
||||
import Swal from 'sweetalert2';
|
||||
import { useVehiclesStore } from '../stores/vehicles';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const props = defineProps({
|
||||
vehicle: {
|
||||
type: Object,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
|
||||
defineEmits(['reset-vehicle']);
|
||||
|
||||
const initState = {
|
||||
truck_type: '',
|
||||
categories: '',
|
||||
vehicle_number: '',
|
||||
city: '',
|
||||
state: '',
|
||||
trailer_plate_1: '',
|
||||
trailer_plate_2: '',
|
||||
circulation_serial_number: '',
|
||||
notes: '',
|
||||
destinoCity: '',
|
||||
destinoState: '',
|
||||
}
|
||||
|
||||
const errors = ref({
|
||||
truck_type: null,
|
||||
categories: null,
|
||||
vehicle_number: null,
|
||||
state: null,
|
||||
city: null,
|
||||
destinoCity: null,
|
||||
destinoState: null,
|
||||
})
|
||||
|
||||
const vehicleForm = reactive({
|
||||
...initState
|
||||
})
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const vehicleStore = useVehiclesStore();
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
if(props.vehicle) {
|
||||
const [destinoCityName = '', destinoStateName = ''] = props.vehicle.destino?.split(';') || [];
|
||||
vehicleForm.truck_type = {
|
||||
meta_value: props.vehicle.truck_type
|
||||
};
|
||||
vehicleForm.categories = props.vehicle.categories;
|
||||
vehicleForm.vehicle_number = props.vehicle.vehicle_number,
|
||||
vehicleForm.city = {city_name: props.vehicle.city},
|
||||
vehicleForm.state = {state_name: props.vehicle.state},
|
||||
vehicleForm.trailer_plate_1 = props.vehicle.trailer_plate_1;
|
||||
vehicleForm.trailer_plate_2 = props.vehicle.trailer_plate_2;
|
||||
vehicleForm.circulation_serial_number = props.vehicle.circulation_serial_number;
|
||||
vehicleForm.notes = props.vehicle.notes;
|
||||
vehicleForm.destinoCity = destinoCityName ? {city_name: destinoCityName} : '';
|
||||
vehicleForm.destinoState = destinoStateName ? { state_name: destinoStateName} : '';
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const title = computed(() => {
|
||||
return (props.vehicle) ? t('vehicles.modalEdit') : t('vehicles.addVehicle');
|
||||
});
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const handleSaveVehicle = async() => {
|
||||
validations();
|
||||
|
||||
if(errors.value.vehicle_number || errors.value.truck_type || errors.value.categories || errors.value.city || errors.value.state || errors.value.destinoCity || errors.value.destinoState ) return;
|
||||
let vehicleData ={
|
||||
vehicle_number : vehicleForm.vehicle_number,
|
||||
city : vehicleForm.city?.city_name,
|
||||
state : vehicleForm.state?.state_name,
|
||||
truck_type : vehicleForm?.truck_type.meta_value,
|
||||
trailer_plate_1: vehicleForm.trailer_plate_1,
|
||||
trailer_plate_2: vehicleForm.trailer_plate_2,
|
||||
circulation_serial_number: vehicleForm.circulation_serial_number,
|
||||
notes: vehicleForm.notes,
|
||||
company: authStore.user.company,
|
||||
posted_by: authStore.user._id,
|
||||
categories: vehicleForm.categories.length <= 0 ? null : vehicleForm.categories?.map((e) => e._id),
|
||||
destino: vehicleForm.destinoCity.city_name + ';' + vehicleForm.destinoState.state_name,
|
||||
available_date: new Date()
|
||||
};
|
||||
let localData = {};
|
||||
let result = 'error';
|
||||
let action = t('vehicles.msgVehicleCreated');
|
||||
loading.value = true;
|
||||
if(props.vehicle) {
|
||||
localData = {
|
||||
categories: vehicleForm.categories,
|
||||
driver: props.vehicle.driver
|
||||
};
|
||||
action = t('vehicles.msgVehicleUpdate');
|
||||
result = await vehicleStore.updateVehicleCompany(props.vehicle._id, vehicleData, localData);
|
||||
} else {
|
||||
localData = {
|
||||
categories: vehicleForm.categories
|
||||
}
|
||||
action = t('vehicles.msgVehicleCreated');
|
||||
result = await vehicleStore.createVehicleCompany(vehicleData, localData);
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
if(result === 'success') {
|
||||
document.getElementById('btnClosecreateVehicleModal').click();
|
||||
Swal.fire({
|
||||
title: action,
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: result,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const validations = () => {
|
||||
errors.value = {
|
||||
truck_type: (!vehicleForm.truck_type) ? t('errors.truck') : null,
|
||||
categories: (!vehicleForm.categories) ? t('errors.segments') : null,
|
||||
vehicle_number: (!vehicleForm.vehicle_number) ? t('errors.required') : null,
|
||||
state: (!vehicleForm.state) ? t('errors.state') : null,
|
||||
city: (!vehicleForm.city) ? t('errors.city') : null,
|
||||
destinoCity: (!vehicleForm.destinoCity) ? t('errors.city') : null,
|
||||
destinoState: (!vehicleForm.destinoState) ? t('errors.state') : null,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" id="createVehicleModal" tabindex="-1" role="dialog" aria-labelledby="createVehicleModal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="title mt-2 mb-3">{{ title }}</h2>
|
||||
<button
|
||||
id="btnClosecreateVehicleModal"
|
||||
type="button"
|
||||
@click="$emit('reset-vehicle')"
|
||||
class="close bg-white" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body form-content">
|
||||
<form @submit.prevent="handleSaveVehicle" autocomplete="off" class="vehicle-form">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-12 mt-4">
|
||||
<label class="custom-label required">{{ t('directory.typeTruck') }}*</label>
|
||||
<TruckTypes
|
||||
v-model="vehicleForm.truck_type"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.truck_type">{{ errors.truck_type }}</span>
|
||||
</div>
|
||||
<div class="col-lg-6 col-12 mt-4">
|
||||
<label class="custom-label required">{{ t('vehicles.segments') }}*</label>
|
||||
<Segments
|
||||
v-model="vehicleForm.categories"
|
||||
:multiple="true"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.categories">{{ errors.categories }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4">
|
||||
<div class="col-lg-6 col-12">
|
||||
<CustomInput
|
||||
:label="t('vehicles.serialNumber') + '*'"
|
||||
name="vehicle_number"
|
||||
v-model:field="vehicleForm.vehicle_number"
|
||||
:filled="false"
|
||||
:error="errors.vehicle_number"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-lg-6 col-12">
|
||||
<CustomInput
|
||||
:label="t('vehicles.truckPlates')"
|
||||
name="circulation_serial_number"
|
||||
v-model:field="vehicleForm.circulation_serial_number"
|
||||
:filled="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-12">
|
||||
<CustomInput
|
||||
:label="t('vehicles.trailerPlates') + ' 1'"
|
||||
name="trailer_plate_1"
|
||||
v-model:field="vehicleForm.trailer_plate_1"
|
||||
:filled="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-lg-6 col-12">
|
||||
<CustomInput
|
||||
:label="t('vehicles.trailerPlates') + ' 2'"
|
||||
name="trailer_plate_2"
|
||||
v-model:field="vehicleForm.trailer_plate_2"
|
||||
:filled="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-12 mb-4">
|
||||
<label class="custom-label required">{{ t('labels.stateBase') }}*</label>
|
||||
<States
|
||||
v-model="vehicleForm.state"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.state">{{ errors.state }}</span>
|
||||
</div>
|
||||
<div class="col-lg-6 col-12 mb-4">
|
||||
<label class="custom-label required">{{ t('labels.cityBase') }}*</label>
|
||||
<Cities
|
||||
v-model="vehicleForm.city"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.city">{{ errors.city }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-12 mb-4">
|
||||
<label class="custom-label required">{{t('vehicles.destinationState')}}*</label>
|
||||
<States
|
||||
v-model="vehicleForm.destinoState"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.destinoState">{{ errors.destinoState }}</span>
|
||||
</div>
|
||||
<div class="col-lg-6 col-12 mb-4">
|
||||
<label class="custom-label required">{{t('vehicles.destinationCity')}}*</label>
|
||||
<Cities
|
||||
v-model="vehicleForm.destinoCity"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.destinoCity">{{ errors.destinoCity }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-column mt-4">
|
||||
<label class="custom-label" for="notes">{{ t('vehicles.additionalInfoVehicle') }}:</label>
|
||||
<textarea
|
||||
class="custom-input-light"
|
||||
name="notes"
|
||||
id="notes"
|
||||
:placeholder=" t('labels.writeHere') + '...'"
|
||||
v-model="vehicleForm.notes"
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="mt-4 text-center">
|
||||
<Spiner v-if="loading"/>
|
||||
<button
|
||||
v-else
|
||||
class="btn-primary-sm radius-sm" type="submit">{{ t('buttons.save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vehicle-form {
|
||||
padding: 0px 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.vehicle-form {
|
||||
padding: 0px 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,76 +0,0 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { Doughnut } from 'vue-chartjs'
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale, ArcElement } from 'chart.js'
|
||||
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, ArcElement)
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
dataModel: {
|
||||
type: Array,
|
||||
},
|
||||
targetFind: {
|
||||
type: String
|
||||
},
|
||||
targetLabel: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
const chartData = ref(null);
|
||||
const dataMap = ref([]);
|
||||
onMounted(() => {
|
||||
|
||||
props.data.forEach(item => {
|
||||
const index = dataMap.value.findIndex((e) => e.name?.toUpperCase() === item?.toUpperCase());
|
||||
if(index === -1) { // Lo agrega
|
||||
if(props.dataModel) {
|
||||
const itemModel = props.dataModel.find((e) => e[props.targetFind].toUpperCase() === item.toUpperCase());
|
||||
const itemTem = {
|
||||
label: (props.targetLabel) ? itemModel[props.targetLabel] : item,
|
||||
data: 1,
|
||||
...itemModel
|
||||
};
|
||||
dataMap.value.push(itemTem)
|
||||
} else {
|
||||
dataMap.value.push({
|
||||
label: item,
|
||||
data: 1,
|
||||
color: 'green'
|
||||
});
|
||||
}
|
||||
} else { // Lo actualiza
|
||||
dataMap.value[index].data += 1;
|
||||
}
|
||||
});
|
||||
chartData.value = {
|
||||
labels: dataMap.value.map((e) => e.label),
|
||||
datasets: [{
|
||||
data: dataMap.value.map((e) => e.data),
|
||||
backgroundColor: dataMap.value.map((e) => e.color),
|
||||
}],
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Doughnut
|
||||
id="my-chart-statictics"
|
||||
v-if="chartData"
|
||||
:options="chartOptions"
|
||||
:data="chartData"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,158 +0,0 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useCompanyStore } from '../stores/company';
|
||||
import Spiner from './ui/Spiner.vue';
|
||||
import { useVehiclesStore } from '../stores/vehicles';
|
||||
import Swal from 'sweetalert2';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const props = defineProps({
|
||||
vehicle: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
defineEmits(['reset-vehicle']);
|
||||
|
||||
const companyStore = useCompanyStore();
|
||||
const vehicleStore = useVehiclesStore();
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const driverSelected = ref(null);
|
||||
const drivers = ref([]);
|
||||
const error = ref(null)
|
||||
const loading = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
drivers.value = companyStore.drivers;
|
||||
if(props?.vehicle?.driver) {
|
||||
const index = drivers.value.findIndex((d) => d._id === props.vehicle.driver?._id);
|
||||
driverSelected.value = drivers.value[index];
|
||||
} else {
|
||||
drivers.value = companyStore.drivers.filter((e) => !e.vehicle)
|
||||
}
|
||||
});
|
||||
|
||||
const handleSetDriver = async() => {
|
||||
if(driverSelected.value === null) {
|
||||
error.value = t('errors.driver');
|
||||
return
|
||||
}
|
||||
|
||||
let vehicle_id = props.vehicle._id;
|
||||
let driver_id = driverSelected.value._id;
|
||||
|
||||
let vehicleData ={
|
||||
driver : driverSelected.value
|
||||
}
|
||||
let localData ={
|
||||
driver : driverSelected.value,
|
||||
categories: props.vehicle.categories
|
||||
}
|
||||
loading.value = true;
|
||||
const result = await vehicleStore.updateVehicleCompany(vehicle_id, vehicleData, localData);
|
||||
|
||||
if( result === 'success' ) {
|
||||
//Actualizamos el vehiculo
|
||||
let userData = {
|
||||
vehicle : vehicle_id
|
||||
}
|
||||
|
||||
let localUser = {
|
||||
categories: driverSelected.value.categories,
|
||||
}
|
||||
|
||||
const result2 = await companyStore.updateUserCompany(driver_id, userData, localUser);
|
||||
if(result2 === 'success' ){
|
||||
document.getElementById('btnCloseeditDriverVehicle').click();
|
||||
Swal.fire({
|
||||
title: `<strong>${t('vehicles.msgAssigedDriver')}</strong>`,
|
||||
icon: 'success'
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: result2,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: result,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" id="editDriverVehicle" tabindex="-1" role="dialog" aria-labelledby="editDriverVehicle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="title mt-2 mb-3">{{ t('vehicles.changeDriver') }}</h2>
|
||||
<button
|
||||
id="btnCloseeditDriverVehicle"
|
||||
type="button"
|
||||
class="close bg-white"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
@click="$emit('reset-vehicle')"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body view-proposals">
|
||||
<div class="custom-selected-field">
|
||||
<label class="custom-label my-2" for="driver">{{ t('vehicles.assignedDriver') }}:</label>
|
||||
<select
|
||||
class="custom-input-light"
|
||||
name="driver"
|
||||
id="driver"
|
||||
v-model="driverSelected"
|
||||
>
|
||||
<option disabled value="">-- {{ t('vehicles.selectDriver') }} --</option>
|
||||
<option v-for="driver in drivers" :value="driver">{{driver.first_name}} {{ driver.last_name }}</option>
|
||||
</select>
|
||||
<span class="error-msg mt-2" v-if="error">{{ error }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<Spiner v-if="loading"/>
|
||||
<div v-else class="btns-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-dark radius-sm"
|
||||
data-dismiss="modal"
|
||||
@click="$emit('reset-vehicle')"
|
||||
>{{ t('buttons.cancel') }}</button>
|
||||
<button
|
||||
class="btn-primary-sm radius-sm"
|
||||
@click="handleSetDriver"
|
||||
>
|
||||
<span class="clear-xsm">{{ t('buttons.save') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-selected-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.btns-footer {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,194 +0,0 @@
|
||||
<script setup>
|
||||
import {reactive, ref} from 'vue';
|
||||
import CustomInput from './ui/CustomInput.vue';
|
||||
import Spiner from './ui/Spiner.vue';
|
||||
import NotificationBadge from './ui/NotificationBadge.vue';
|
||||
import { validateEmail } from '../helpers/validations';
|
||||
import Swal from 'sweetalert2';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useNotificationsStore } from '../stores/notifications';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const emailForm = reactive({
|
||||
email: '',
|
||||
email2: '',
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const msgError = ref('');
|
||||
const msgSuccess = ref('');
|
||||
const auth = useAuthStore();
|
||||
const notifications = useNotificationsStore();
|
||||
const router = useRouter();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const hangleSave = () => {
|
||||
msgError.value = '';
|
||||
msgSuccess.value = '';
|
||||
let resp = validations();
|
||||
if(resp !== '') {
|
||||
msgError.value = resp;
|
||||
clearMessages();
|
||||
return;
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: t('profile.questionChangeEmail'),
|
||||
// text: '',
|
||||
html: t('profile.msgInfoEmail'),
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: t('buttons.confirm'),
|
||||
cancelButtonText: t('buttons.cancel'),
|
||||
}).then( async(result) => {
|
||||
if(result.isConfirmed) {
|
||||
Swal.fire({
|
||||
title: t('messages.loading'),
|
||||
html: t('messages.savingChanes') + '...',
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading()
|
||||
},
|
||||
});
|
||||
|
||||
const userData = {
|
||||
"email" : emailForm.email,
|
||||
};
|
||||
// console.log(userData);
|
||||
loading.value = true;
|
||||
const response = await auth.updateProfile(userData)
|
||||
loading.value = false;
|
||||
if (response.msg !== 'success') {
|
||||
msgError.value = response.msg;
|
||||
clearMessages();
|
||||
} else {
|
||||
clearMessages();
|
||||
notifications.$patch({
|
||||
text : t('profile.msgChangeEmail'),
|
||||
show : true,
|
||||
error: false
|
||||
});
|
||||
auth.logout();
|
||||
router.push({name: 'login'});
|
||||
}
|
||||
Swal.close()
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const validations = () => {
|
||||
if(emailForm.email.trim() == '') {
|
||||
return t('errors.requireds');
|
||||
} else if (!validateEmail(emailForm.email)) {
|
||||
return t('errors.email')
|
||||
} else if (emailForm.email !== emailForm.email2) {
|
||||
return t('errors.notMatchEmails')
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const clearMessages = () => {
|
||||
setTimeout(() => {
|
||||
msgError.value = '';
|
||||
msgSuccess.value = '';
|
||||
}, 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form
|
||||
@submit.prevent="hangleSave"
|
||||
autocomplete="off"
|
||||
method="post"
|
||||
ref="formRef1"
|
||||
>
|
||||
<br/>
|
||||
<h3 class="title">{{ t('profile.titleFormEmail') }}</h3>
|
||||
<br/>
|
||||
<NotificationBadge :msg="msgError" v-if="msgError != ''"/>
|
||||
<NotificationBadge :msg="msgSuccess" v-if="msgSuccess != ''" :is-error="false"/>
|
||||
<CustomInput
|
||||
:label="t('profile.newEmail') + '*:'"
|
||||
type="email"
|
||||
name="email"
|
||||
:filled="false"
|
||||
v-model:field="emailForm.email"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('profile.confirmEmail') + '*:'"
|
||||
type="email"
|
||||
name="email2"
|
||||
:step="1"
|
||||
:filled="false"
|
||||
v-model:field="emailForm.email2"
|
||||
/>
|
||||
<div class="warning-info">
|
||||
<div>
|
||||
<i class="fa-solid fa-triangle-exclamation warning"></i>
|
||||
</div>
|
||||
<span v-html="t('profile.msgInfoEmail')"></span>
|
||||
</div>
|
||||
<div class="mt-5 text-center">
|
||||
<Spiner v-if="loading"/>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-dark btn-block" type="submit">{{ t('buttons.save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.warning-info {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
background: rgb(223, 223, 161);
|
||||
font-size: 0.9rem;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
color: rgb(86, 57, 57);
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
font-size: 33px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.form-content {
|
||||
margin: 24px 16px;
|
||||
background-color: #FFF;
|
||||
padding: 32px 32px;
|
||||
border-radius: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 568px) {
|
||||
.form-content {
|
||||
margin: 24px 8px;
|
||||
background-color: #FFF;
|
||||
padding: 20px 20px;
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
.phone {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-phone{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,261 +0,0 @@
|
||||
<script setup>
|
||||
import {reactive, ref} from 'vue';
|
||||
import CustomInput from './ui/CustomInput.vue';
|
||||
import Spiner from './ui/Spiner.vue';
|
||||
import NotificationBadge from './ui/NotificationBadge.vue';
|
||||
import { recoveryPassword, recoveryPasswordConfirm, regiter } from '../services/auth';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useNotificationsStore } from '../stores/notifications';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const pwdForm = reactive({
|
||||
pwd: '',
|
||||
pwd2: '',
|
||||
code: '',
|
||||
checksum: '',
|
||||
})
|
||||
|
||||
const step = ref(1);
|
||||
|
||||
const auth = useAuthStore();
|
||||
const router = useRouter();
|
||||
const notifications = useNotificationsStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const msgError = ref('');
|
||||
const msgSuccess = ref('');
|
||||
const { t } = useI18n()
|
||||
|
||||
const hangleSave = async() => {
|
||||
msgError.value = '';
|
||||
msgSuccess.value = '';
|
||||
let resp = validations();
|
||||
if(resp !== '') {
|
||||
msgError.value = resp;
|
||||
clearMessages();
|
||||
return;
|
||||
} else {
|
||||
loading.value = true;
|
||||
msgError.value = '';
|
||||
const data = {
|
||||
email: auth.user.email,
|
||||
password: pwdForm.pwd
|
||||
}
|
||||
const result = await recoveryPassword(data);
|
||||
if(result.msg === 'success' && result.data !== null) {
|
||||
msgSuccess.value = t('messages.sendCode')
|
||||
pwdForm.checksum = result.data.checksum;
|
||||
step.value = 2;
|
||||
clearMessages();
|
||||
} else {
|
||||
msgError.value = result.msg;
|
||||
clearMessages();
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const handleConfirmChange = async() => {
|
||||
msgError.value = '';
|
||||
msgSuccess.value = '';
|
||||
if(pwdForm.code.length < 6) {
|
||||
msgError.value = t('errors.code');
|
||||
clearMessages();
|
||||
return;
|
||||
} else {
|
||||
loading.value = true;
|
||||
msgError.value = '';
|
||||
const data = {
|
||||
email: auth.user.email,
|
||||
password: pwdForm.pwd,
|
||||
otp: pwdForm.code,
|
||||
checksum: pwdForm.checksum
|
||||
}
|
||||
const result = await recoveryPasswordConfirm(data);
|
||||
// console.log(result);
|
||||
if(result.msg === 'success' && result.data !== null){
|
||||
Object.assign(pwdForm, {
|
||||
pwd: '',
|
||||
retryPwd: '',
|
||||
code: '',
|
||||
checksum: '',
|
||||
});
|
||||
clearMessages();
|
||||
notifications.$patch({
|
||||
text : t('messages.changePassword'),
|
||||
show : true,
|
||||
error: false
|
||||
});
|
||||
auth.logout();
|
||||
router.push({name: 'login'});
|
||||
} else {
|
||||
msgError.value = result.msg;
|
||||
clearMessages()
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const resendCode = async() => {
|
||||
// loading.value = true;
|
||||
// const data = {
|
||||
// email: pwdForm.email,
|
||||
// password: pwdForm.pwd
|
||||
// }
|
||||
// const result = await regiter(data);
|
||||
// if(result.msg === 'success' && result.data !== null) {
|
||||
// msgSuccess.value = 'Te enviamos un código al correo, ingresado!';
|
||||
// checksum.value = result.data.checksum;
|
||||
// clearMessages();
|
||||
// } else {
|
||||
// msgError.value = result.msg;
|
||||
// clearMessages();
|
||||
// }
|
||||
// loading.value = false;
|
||||
}
|
||||
|
||||
const handleBack = (val) => {
|
||||
step.value = val;
|
||||
}
|
||||
|
||||
const validations = () => {
|
||||
const pass = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/;
|
||||
|
||||
if(!pass.test(pwdForm.pwd)) {
|
||||
return t('errors.weakPassword')
|
||||
} else if (pwdForm.pwd !== pwdForm.pwd2) {
|
||||
return t('errors.matchPassword');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const clearMessages = () => {
|
||||
setTimeout(() => {
|
||||
msgError.value = '';
|
||||
msgSuccess.value = '';
|
||||
}, 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form
|
||||
@submit.prevent="hangleSave"
|
||||
autocomplete="off"
|
||||
method="post"
|
||||
ref="formRef1"
|
||||
v-if="step === 1"
|
||||
>
|
||||
<br/>
|
||||
<h3 class="title">{{ t('profile.changePassword') }}</h3>
|
||||
<br/>
|
||||
<NotificationBadge :msg="msgError" v-if="msgError != ''"/>
|
||||
<NotificationBadge :msg="msgSuccess" v-if="msgSuccess != ''" :is-error="false"/>
|
||||
<CustomInput
|
||||
:label="t('labels.password2') + '*:'"
|
||||
type="password"
|
||||
name="pwd"
|
||||
:filled="false"
|
||||
v-model:field="pwdForm.pwd"
|
||||
:help-text="t('login.helptext')"
|
||||
/>
|
||||
<CustomInput
|
||||
:label="t('labels.password3') + '*:'"
|
||||
type="password"
|
||||
name="pwd2"
|
||||
:step="1"
|
||||
:filled="false"
|
||||
v-model:field="pwdForm.pwd2"
|
||||
/>
|
||||
<div class="warning-info">
|
||||
<div><i class="fa-solid fa-triangle-exclamation warning"></i></div>
|
||||
{{ t('profile.helpTextResetPass') }}
|
||||
</div>
|
||||
<div class="mt-5 text-center">
|
||||
<Spiner v-if="loading"/>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-dark btn-block" type="submit">{{ t('buttons.continue') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
<form v-if="step === 2" @submit.prevent="handleConfirmChange" class="mx-5">
|
||||
<div class="d-flex justify-content-center align-items-center mb-4 mt-4">
|
||||
<a
|
||||
@click="handleBack(1)"
|
||||
class="btn-text ms-2"><i class="fa-solid fa-arrow-left"></i> {{ t('buttons.back') }}</a>
|
||||
</div>
|
||||
<NotificationBadge :msg="msgError" v-if="msgError != ''"/>
|
||||
<NotificationBadge :msg="msgSuccess" :is-error="false" v-if="msgSuccess != ''"/>
|
||||
<p class="help-info">{{ t('login.helptextCode') }}</p>
|
||||
<CustomInput
|
||||
:label="t('labels.code') + '*:'"
|
||||
name="code"
|
||||
:filled="false"
|
||||
type="text"
|
||||
v-model:field="pwdForm.code"
|
||||
/>
|
||||
<!-- <div v-if="!loading" class="d-flex justify-content-center align-items-center mt-4">
|
||||
<a
|
||||
@click="resendCode()"
|
||||
class="btn-text ms-2"><i class="fa-solid fa-rotate-right"></i> Reenviar código</a>
|
||||
</div> -->
|
||||
<div class="mt-5 text-center">
|
||||
<Spiner v-if="loading"/>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-dark btn-block" type="submit">{{ t('buttons.confirm') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.warning-info {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
background: rgb(223, 223, 161);
|
||||
font-size: 0.9rem;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
color: rgb(86, 57, 57);
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
font-size: 33px;
|
||||
margin-right: 12px;
|
||||
/* color: white; */
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.form-content {
|
||||
margin: 24px 16px;
|
||||
background-color: #FFF;
|
||||
padding: 32px 32px;
|
||||
border-radius: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 568px) {
|
||||
.form-content {
|
||||
margin: 24px 8px;
|
||||
background-color: #FFF;
|
||||
padding: 20px 20px;
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
.phone {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-phone{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,694 +0,0 @@
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, watch } from 'vue';
|
||||
import { useLoadsStore } from '../stores/loads';
|
||||
import Custominput from './ui/CustomInput.vue';
|
||||
import Segments from './ui/Segments.vue';
|
||||
import TruckTypes from './ui/TruckTypes.vue';
|
||||
import Spiner from './ui/Spiner.vue';
|
||||
import Products from './ui/Products.vue';
|
||||
import { GoogleMap, Marker, Polyline } from "vue3-google-map";
|
||||
import useDirectionsRender from '../composables/useDirectionRender';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import Swal from 'sweetalert2';
|
||||
import { useNotificationsStore } from '../stores/notifications';
|
||||
import { useCompanyStore } from '../stores/company';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed } from 'vue';
|
||||
import {getDateTime } from '../helpers/date_formats';
|
||||
import { validateEmail } from '../helpers/validations';
|
||||
import AddressPreview from './AddressPreview.vue';
|
||||
|
||||
|
||||
const loadStore = useLoadsStore();
|
||||
const notyStore = useNotificationsStore();
|
||||
const auth = useAuthStore();
|
||||
const companyStore = useCompanyStore()
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
const zoom = ref(6);
|
||||
const heightMap = ref(768);
|
||||
const originCoords = ref(null);
|
||||
const destinationCoords = ref(null);
|
||||
const polylines = ref([]);
|
||||
const startLocation = ref(null);
|
||||
const endLocation = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const loadingLocations = ref(false);
|
||||
const submited = ref(false);
|
||||
const { geocodeAddress, getDirections } = useDirectionsRender();
|
||||
const formRef = ref(null);
|
||||
const locationLoadSelected = ref(null)
|
||||
const locationDownloadSelected = ref(null)
|
||||
const originRef = ref('')
|
||||
const destinationRef = ref('')
|
||||
const emails = ref([]);
|
||||
const emailInput = ref('');
|
||||
|
||||
const errors = ref({
|
||||
segment: null,
|
||||
truckType: null,
|
||||
locationOrigin: null,
|
||||
locationDestination: null,
|
||||
});
|
||||
|
||||
const mapKey = import.meta.env.VITE_MAP_KEY;
|
||||
const { t } = useI18n()
|
||||
|
||||
|
||||
const clearLoad = () => {
|
||||
loadStore.currentLoad = null;
|
||||
loadStore.openModalEdit = false;
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
windowWidth.value = window.innerWidth
|
||||
if(windowWidth.value <= 1024){
|
||||
zoom.value = 4
|
||||
heightMap.value = 420;
|
||||
} else {
|
||||
zoom.value = 6;
|
||||
heightMap.value = 768;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
if(window.innerWidth <= 1024) {
|
||||
zoom.value = 4;
|
||||
heightMap.value = 420;
|
||||
}
|
||||
getLocations('unloading');
|
||||
getLocations('loading');
|
||||
formLoad.owner = auth.user?.first_name + ' ' + auth.user?.last_name;
|
||||
if(loadStore.currentLoad){
|
||||
const dateStart = getDateTime(loadStore.currentLoad.est_loading_date, 0);
|
||||
const dateEnd = getDateTime(loadStore.currentLoad.est_unloading_date, 0);
|
||||
formLoad.price = loadStore.currentLoad.actual_cost;
|
||||
formLoad.segment = loadStore.currentLoad.categories?.length <= 0 ? [] : loadStore.currentLoad.categories.map(m =>{
|
||||
return m;
|
||||
});
|
||||
startLocation.value = loadStore.currentLoad.origin_formatted_address;
|
||||
endLocation.value = loadStore.currentLoad.destination_formatted_address;
|
||||
formLoad.terms = loadStore.currentLoad.product;
|
||||
formLoad.owner = loadStore.currentLoad.posted_by_name;
|
||||
formLoad.notes = loadStore.currentLoad.notes;
|
||||
formLoad.weight = loadStore.currentLoad.weight;
|
||||
formLoad.dateLoad = dateStart.substring(0, 10);
|
||||
formLoad.dateDownload = dateEnd.substring(0, 10);
|
||||
formLoad.truckType = loadStore.currentLoad.truck_type ? {meta_value: loadStore.currentLoad.truck_type} : null;
|
||||
emails.value = loadStore.currentLoad?.alert_list || [];
|
||||
getCoordsMap();
|
||||
}
|
||||
|
||||
watch(origin, async() => {
|
||||
if(origin.city && origin.state) {
|
||||
startLocation.value = origin.address + ', ' + origin.city.city_name + ', ' + origin.state.state_name + ', ' + origin.country + ', ' +origin.postalCode;
|
||||
originCoords.value = await geocodeAddress(startLocation.value);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
watch(destination, async() => {
|
||||
if(destination.city && destination.state) {
|
||||
endLocation.value = destination.address + ', ' + destination.city.city_name + ', ' + destination.state.state_name + ', ' + destination.country + ', ' +destination.postalCode;
|
||||
destinationCoords.value = await geocodeAddress(endLocation.value);
|
||||
}
|
||||
})
|
||||
|
||||
watch([originCoords, destinationCoords], async() => {
|
||||
if(originCoords.value && destinationCoords.value) {
|
||||
polylines.value = await getDirections(originCoords.value, destinationCoords.value);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
watch(locationLoadSelected, () => {
|
||||
origin.locationName = locationLoadSelected.value?.branch_name;
|
||||
origin.address = locationLoadSelected.value?.address;
|
||||
origin.state = { state_name: locationLoadSelected.value?.state };
|
||||
origin.city = { city_name: locationLoadSelected.value?.city };
|
||||
originRef.value = locationLoadSelected.value?.description;
|
||||
});
|
||||
|
||||
watch(locationDownloadSelected, () => {
|
||||
destination.locationName = locationDownloadSelected.value?.branch_name;
|
||||
destination.address = locationDownloadSelected.value?.address;
|
||||
destination.state = { state_name: locationDownloadSelected.value?.state };
|
||||
destination.city = { city_name: locationDownloadSelected.value?.city };
|
||||
destinationRef.value = locationDownloadSelected.value?.description;
|
||||
});
|
||||
|
||||
const getLocations = async(type) => {
|
||||
loadingLocations.value = true;
|
||||
await companyStore.getLocationsLoads(type)
|
||||
setLocations(type);
|
||||
loadingLocations.value = false;
|
||||
}
|
||||
|
||||
const setLocations = (type) => {
|
||||
if(loadStore.currentLoad) {
|
||||
if(type === 'loading') {
|
||||
locationLoadSelected.value = companyStore.locationsLoad.find( (l) => l._id === loadStore.currentLoad?.origin_warehouse?._id);
|
||||
origin.locationName = loadStore.currentLoad.origin.company_name;
|
||||
origin.address = loadStore.currentLoad.origin.street_address1;
|
||||
origin.state = loadStore.currentLoad.origin?.state ? { state_name: loadStore.currentLoad.origin.state } : null;
|
||||
origin.city = loadStore.currentLoad.origin?.city ? { city_name: loadStore.currentLoad.origin.city } : null;
|
||||
origin.country = loadStore.currentLoad.origin.country;
|
||||
origin.postalCode = loadStore.currentLoad.origin.zipcode;
|
||||
originRef.value = loadStore.currentLoad.origin.landmark;
|
||||
} else {
|
||||
locationDownloadSelected.value = companyStore.locationsDowload.find( (l) => l._id === loadStore.currentLoad?.destination_warehouse?._id)
|
||||
destination.locationName = loadStore.currentLoad.destination.company_name;
|
||||
destination.address = loadStore.currentLoad.destination.street_address1;
|
||||
destination.state = loadStore.currentLoad.destination?.state ? { state_name: loadStore.currentLoad.destination.state } : null;
|
||||
destination.city = loadStore.currentLoad.destination?.city ? { city_name: loadStore.currentLoad.destination.city } : null;
|
||||
destination.country = loadStore.currentLoad.destination.country;
|
||||
destination.postalCode = loadStore.currentLoad.destination.zipcode;
|
||||
destinationRef.value = loadStore.currentLoad.destination.landmark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getCoordsMap = async() => {
|
||||
const destinationLat = loadStore.currentLoad.destination?.lat;
|
||||
const destinationLng = loadStore.currentLoad.destination?.lng;
|
||||
const originLat = loadStore.currentLoad.origin?.lat;
|
||||
const originLng = loadStore.currentLoad.origin?.lng;
|
||||
if(destinationLat && destinationLng) {
|
||||
destinationCoords.value = {lat: Number.parseFloat(destinationLat), lng: Number.parseFloat(destinationLng)}
|
||||
}
|
||||
if(originLat && originLng) {
|
||||
originCoords.value = {lat: Number.parseFloat(originLat), lng: Number.parseFloat(originLng)}
|
||||
}
|
||||
|
||||
if(originCoords.value && destinationCoords.value) {
|
||||
polylines.value = await getDirections(originCoords.value, destinationCoords.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const formLoad = reactive({
|
||||
dateLoad: '',
|
||||
dateDownload: '',
|
||||
segment: [],
|
||||
truckType: '',
|
||||
terms: '',
|
||||
price: 0,
|
||||
weight: 0,
|
||||
notes: '',
|
||||
owner: '',
|
||||
});
|
||||
|
||||
const origin = reactive({
|
||||
id: null,
|
||||
locationName: '',
|
||||
address: '',
|
||||
state: '',
|
||||
city: '',
|
||||
country: '',
|
||||
postalCode: '',
|
||||
});
|
||||
|
||||
const destination = reactive({
|
||||
locationName: '',
|
||||
address: '',
|
||||
state: '',
|
||||
city: '',
|
||||
country: '',
|
||||
postalCode: '',
|
||||
});
|
||||
|
||||
const setLoadData = () => {
|
||||
const currentDate = new Date();
|
||||
const hours = currentDate.getHours().toString().padStart(2, '0');
|
||||
const minutes = currentDate.getMinutes().toString().padStart(2, '0');
|
||||
const seconds = currentDate.getSeconds().toString().padStart(2, '0');
|
||||
const startDate = formLoad.dateLoad === "" ? "" : new Date(`${formLoad.dateLoad}T${hours}:${minutes}:${seconds}`).toISOString();
|
||||
const endDate = formLoad.dateDownload === "" ? "" : new Date(`${formLoad.dateDownload}T${hours}:${minutes}:${seconds}`).toISOString();
|
||||
let loadData = {
|
||||
actual_cost: formLoad.price,
|
||||
truck_type: formLoad.truckType?.meta_value || null,
|
||||
est_loading_date : startDate,
|
||||
est_unloading_date : endDate,
|
||||
notes : formLoad.notes,
|
||||
weight : formLoad.weight,
|
||||
product: formLoad.terms?.length <= 0 ? null : formLoad.terms,
|
||||
categories: formLoad.segment || null,
|
||||
origin:{
|
||||
company_name : origin?.locationName,
|
||||
street_address1 : origin?.address,
|
||||
state : origin.state?.state_name,
|
||||
city : origin.city?.city_name,
|
||||
country : origin?.country,
|
||||
landmark : originRef.value,
|
||||
zipcode : origin?.postalCode,
|
||||
lat : originCoords.value?.lat,
|
||||
lng : originCoords.value?.lng
|
||||
},
|
||||
destination:{
|
||||
company_name : destination?.locationName,
|
||||
street_address1 : destination?.address,
|
||||
state : destination.state?.state_name,
|
||||
city : destination.city?.city_name,
|
||||
country : destination?.country,
|
||||
landmark : destinationRef.value,
|
||||
zipcode : destination?.postalCode,
|
||||
lat : destinationCoords.value?.lat,
|
||||
lng : destinationCoords.value?.lng
|
||||
},
|
||||
company: auth.user.company,
|
||||
posted_by: auth.user._id,
|
||||
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
|
||||
};
|
||||
return loadData;
|
||||
}
|
||||
|
||||
const updateLoad = async(loadData) => {
|
||||
isLoading.value = true;
|
||||
if(loadStore.currentLoad){
|
||||
const resp = await loadStore.updateLoad(loadStore.currentLoad._id, loadData);
|
||||
isLoading.value = false;
|
||||
const dataLocal = {
|
||||
company: auth.user.company,
|
||||
categories: formLoad.segment || null,
|
||||
product: formLoad.terms?.length <= 0 ? null : formLoad.terms,
|
||||
origin_warehouse: locationLoadSelected.value,
|
||||
destination_warehouse: locationDownloadSelected.value,
|
||||
};
|
||||
if(resp) {
|
||||
const index = loadStore.loads.findIndex((load) => load._id === resp._id);
|
||||
loadStore.loads[index] = {
|
||||
...loadStore.loads[index],
|
||||
...resp,
|
||||
...dataLocal,
|
||||
};
|
||||
return 'success';
|
||||
} else {
|
||||
return 'error';
|
||||
}
|
||||
} else{
|
||||
const resp = await loadStore.saveLoad(loadData);
|
||||
isLoading.value = false;
|
||||
if(resp) {
|
||||
const load = {
|
||||
...resp,
|
||||
...loadData,
|
||||
categories: [loadData.categories],
|
||||
origin_warehouse: locationLoadSelected.value,
|
||||
destination_warehouse: locationDownloadSelected.value,
|
||||
}
|
||||
|
||||
loadStore.loads.unshift(load);
|
||||
return 'success';
|
||||
} else {
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async() => {
|
||||
submited.value = false;
|
||||
const loadData = setLoadData();
|
||||
const resp = await updateLoad(loadData);
|
||||
if(resp === 'success') {
|
||||
notyStore.show = 'true';
|
||||
notyStore.text = t('loads.msgSave');
|
||||
document.getElementById('btnCloseFormLoadModal').click();
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: "Error!",
|
||||
text: t('loads.msgNotSave'),
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const validations = () => {
|
||||
errors.value = {
|
||||
segment: (!formLoad.segment || formLoad.segment?.length <= 0) ? t('errors.segment') : null,
|
||||
truckType: formLoad.truckType ? null : t('errors.truck'),
|
||||
locationOrigin: locationLoadSelected.value ? null : 'Seleccione una ubicación de origen',
|
||||
locationDestination: locationDownloadSelected.value ? null : 'Seleccione una ubicación de destino',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const handlePostLoad = async() => {
|
||||
submited.value = true;
|
||||
setTimeout(async() => {
|
||||
const formValid = formRef.value.checkValidity();
|
||||
validations()
|
||||
if(formValid){
|
||||
const hasError = Object.values(errors.value).some(val => val != null);
|
||||
if(!hasError) {
|
||||
let loadData = setLoadData();
|
||||
|
||||
loadData = {
|
||||
...loadData,
|
||||
status: "Published",
|
||||
load_status: "Published"
|
||||
}
|
||||
const resp = await updateLoad(loadData);
|
||||
if(resp === 'success') {
|
||||
notyStore.show = 'true';
|
||||
notyStore.text = t('loads.msgPost')
|
||||
document.getElementById('btnCloseFormLoadModal').click();
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: "Error!",
|
||||
text: t('loads.msgNotPost'),
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 200)
|
||||
|
||||
}
|
||||
|
||||
const title = computed(() => loadStore.currentLoad == null
|
||||
? t('loads.create')
|
||||
: 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>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" id="formLoadModal" tabindex="-1" role="dialog" aria-labelledby="formLoadModal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-fullscreen modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="title mt-2 mb-3">{{ title }}</h2>
|
||||
<button
|
||||
id="btnCloseFormLoadModal"
|
||||
type="button"
|
||||
class="close bg-white"
|
||||
data-dismiss="modal"
|
||||
@click="clearLoad"
|
||||
aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-content" ref="formRef">
|
||||
<div class="form-box">
|
||||
<div class="form-section">
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label required">{{ t('global.segment') }}*</label>
|
||||
<Segments
|
||||
v-model="formLoad.segment"
|
||||
/>
|
||||
<span class="error-msg" v-if="submited && errors.segment">{{ errors.segment }}</span>
|
||||
</div>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label required">{{ t('directory.typeTruck') }}*</label>
|
||||
<TruckTypes
|
||||
v-model="formLoad.truckType"
|
||||
/>
|
||||
<span class="error-msg" v-if="submited && errors.truckType">{{ errors.truckType }}</span>
|
||||
</div>
|
||||
<Custominput
|
||||
:label="t('loads.dateLoad') + '*'"
|
||||
type="date"
|
||||
:filled="false"
|
||||
name="date-load"
|
||||
:required="submited ? true : false"
|
||||
:error="(submited && !formLoad.dateLoad) ? t('errors.date') : null"
|
||||
v-model:field="formLoad.dateLoad"
|
||||
/>
|
||||
<Custominput
|
||||
:label="t('loads.dateDownload') + '*'"
|
||||
type="date"
|
||||
:filled="false"
|
||||
name="date-download"
|
||||
:required="submited ? true : false"
|
||||
:error="(submited && !formLoad.dateDownload) ? t('errors.date') : null"
|
||||
v-model:field="formLoad.dateDownload"
|
||||
/>
|
||||
<Custominput
|
||||
:label="t('loads.labelWeight') + '*'"
|
||||
type="number"
|
||||
:filled="false"
|
||||
name="weight"
|
||||
:required="submited ? true : false"
|
||||
:error="(submited && !formLoad.weight) ? t('errors.weight') : null"
|
||||
v-model:field="formLoad.weight"
|
||||
/>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('loads.product') }}</label>
|
||||
<Products
|
||||
v-model="formLoad.terms"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<Custominput
|
||||
:label="t('loads.labelPrice')"
|
||||
type="Number"
|
||||
:filled="false"
|
||||
name="price"
|
||||
v-model:field="formLoad.price"
|
||||
/>
|
||||
<Custominput
|
||||
:label="t('loads.notes')"
|
||||
type="text"
|
||||
:filled="false"
|
||||
name="notes"
|
||||
v-model:field="formLoad.notes"
|
||||
/>
|
||||
<Custominput
|
||||
:label="t('loads.postBy')"
|
||||
type="text"
|
||||
:filled="false"
|
||||
:readonly="true"
|
||||
name="owner"
|
||||
v-model:field="formLoad.owner"
|
||||
/>
|
||||
<div class="box-observers mt-4">
|
||||
<div class="box-input">
|
||||
<div class="input-observer">
|
||||
<Custominput
|
||||
label="Agregar correo del cliente"
|
||||
name="email"
|
||||
type="email"
|
||||
v-model:field="emailInput"
|
||||
:filled="false"
|
||||
:tooltip="t('messages.observerClient')"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@click="addObserver"
|
||||
class="btn-leading-input mb-4"
|
||||
>
|
||||
Agregar
|
||||
</button>
|
||||
</div>
|
||||
<h5 v-if="emails.length > 0">Lista de correos de clientes:</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 class="form-box">
|
||||
<div class="form-section">
|
||||
<Spiner v-if="loadingLocations"/>
|
||||
<div class="d-flex flex-column mb-4" v-if="!loadingLocations">
|
||||
<label class="custom-label mb-2" for="locationLoad">{{ t('loads.addressOrigin') }}:</label>
|
||||
<select
|
||||
class="custom-input-light"
|
||||
name="locationLoad"
|
||||
id="locationLoad"
|
||||
v-model="locationLoadSelected"
|
||||
>
|
||||
<option disabled value="">-- {{ t('loads.selectedLocation') }} --</option>
|
||||
<option v-for="loc in companyStore.locationsLoad" :value="loc">{{ loc.branch_name }}</option>
|
||||
</select>
|
||||
<span class="error-msg" v-if="submited && errors.locationOrigin">{{ errors.locationOrigin }}</span>
|
||||
</div>
|
||||
<AddressPreview
|
||||
v-if="origin.locationName"
|
||||
:address="origin"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<Spiner v-if="loadingLocations"/>
|
||||
<div class="d-flex flex-column mb-4" v-if="!loadingLocations">
|
||||
<label class="custom-label mb-2" for="locationDownload">{{ t('loads.addressDestination') }}:</label>
|
||||
<select
|
||||
class="custom-input-light"
|
||||
name="locationDownload"
|
||||
id="locationDownload"
|
||||
v-model="locationDownloadSelected"
|
||||
>
|
||||
<option disabled value="">-- {{ t('loads.selectedLocation') }} --</option>
|
||||
<option v-for="loc in companyStore.locationsDowload" :value="loc">{{ loc.branch_name }}</option>
|
||||
</select>
|
||||
<span class="error-msg" v-if="submited && errors.locationDestination">{{ errors.locationDestination }}</span>
|
||||
</div>
|
||||
<AddressPreview
|
||||
v-if="destination.locationName"
|
||||
:address="destination"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<GoogleMap
|
||||
:api-key="mapKey"
|
||||
:center="{lat:19.432600, lng:-99.133209}"
|
||||
:zoom="zoom"
|
||||
:min-zoom="2"
|
||||
:max-zoom="12"
|
||||
:style="{width: 100 + '%', height: heightMap + 'px'}"
|
||||
:options="{
|
||||
zoomControl: true,
|
||||
mapTypeControl: true,
|
||||
scaleControl: true,
|
||||
streetViewControl: true,
|
||||
rotateControl: true,
|
||||
fullscreenControl: true
|
||||
}"
|
||||
>
|
||||
<Marker v-if="originCoords" :options="{position: originCoords, label: 'O', title: 'Destino'}" />
|
||||
<Marker v-if="destinationCoords" :options="{position: destinationCoords, label: 'D', title: 'Origen'}" />
|
||||
<Polyline
|
||||
v-if="polylines"
|
||||
:options="{
|
||||
path: polylines,
|
||||
// geodesic: true,
|
||||
strokeColor: '#2D90BB',
|
||||
strokeOpacity: 0.7,
|
||||
strokeWeight: 5,
|
||||
clickable: true,
|
||||
fillColor: '#75ad3e',
|
||||
}"
|
||||
/>
|
||||
</GoogleMap>
|
||||
</div>
|
||||
<div class="modal-footer custom-footer">
|
||||
<Spiner v-if="isLoading"/>
|
||||
<div v-else class="btns-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-dark"
|
||||
@click="clearLoad"
|
||||
data-dismiss="modal">{{ t('buttons.close') }}</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-dark"
|
||||
@click="handleSave"
|
||||
>{{ t('buttons.save') }}</button>
|
||||
<button
|
||||
type="submit"
|
||||
@click.prevent="handlePostLoad"
|
||||
class="btn-primary-sm radius-sm"
|
||||
>{{ t('buttons.post') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-box {
|
||||
width: 80%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: 4rem;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.custom-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.btns-footer {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.chekmark {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.radius-sm{
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.form-box {
|
||||
width: 95%;
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 568px) {
|
||||
.form-box {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,223 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import CardEmpty from './CardEmpty.vue';
|
||||
import Spiner from './ui/Spiner.vue';
|
||||
import { GoogleMap, Marker, CustomMarker, Polyline } from 'vue3-google-map';
|
||||
import useDirectionsRender from '../composables/useDirectionRender';
|
||||
import Cardload from './CardLoad.vue';
|
||||
import { useLoadsStore } from '../stores/loads';
|
||||
import { useNotificationsStore } from '../stores/notifications';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const mapKey = import.meta.env.VITE_MAP_KEY;
|
||||
|
||||
const zoom = ref(6);
|
||||
const heightMap = ref(768);
|
||||
const originCoords = ref(null);
|
||||
const polylines = ref([]);
|
||||
const destinationCoords = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
const load = ref(null);
|
||||
const vehicleLastLocation = ref(null);
|
||||
const isLoadActive = ref(false);
|
||||
|
||||
const { getDirections, geocodeAddress } = useDirectionsRender()
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
proposal: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
})
|
||||
|
||||
defineEmits(['reset-proposal'])
|
||||
const loadStore = useLoadsStore();
|
||||
const notyStore = useNotificationsStore();
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
if(window.innerWidth <= 1024) {
|
||||
zoom.value = 4;
|
||||
heightMap.value = 420;
|
||||
}
|
||||
|
||||
initData()
|
||||
});
|
||||
|
||||
const initData = async() => {
|
||||
isLoading.value = true;
|
||||
const code = props.proposal.load.shipment_code;
|
||||
const filter = "?shipment_code[$in]=" + code;
|
||||
const resp = await loadStore.getLoad(filter);
|
||||
if(resp.total > 0) {
|
||||
load.value = resp.data[0];
|
||||
try {
|
||||
const addressOrigin = load.value?.origin;
|
||||
const addressDestination = load.value?.destination;
|
||||
if(addressOrigin && addressDestination) {
|
||||
if(addressOrigin.lat && addressOrigin.lng) {
|
||||
originCoords.value = {lat: Number.parseFloat(addressOrigin.lat), lng: Number.parseFloat(addressOrigin.lng)};
|
||||
} else {
|
||||
let startLocation = addressOrigin.street_address1 + ', ' + addressOrigin.city + ', ' + addressOrigin.state + ', ' + addressOrigin.country + ', ' +addressOrigin.zipcode;
|
||||
originCoords.value = await geocodeAddress(startLocation);
|
||||
}
|
||||
|
||||
if(addressDestination.lat && addressDestination.lng) {
|
||||
destinationCoords.value = {lat: Number.parseFloat(addressDestination.lat), lng: Number.parseFloat(addressDestination.lng)};
|
||||
} else {
|
||||
let endLocation = addressDestination.street_address1 + ', ' + addressDestination.city + ', ' + addressDestination.state + ', ' + addressDestination.country + ', ' +addressDestination.zipcode;
|
||||
destinationCoords.value = await geocodeAddress(endLocation);
|
||||
}
|
||||
|
||||
polylines.value = await getDirections(originCoords.value, destinationCoords.value);
|
||||
|
||||
}
|
||||
|
||||
if(load.value.vehicle) {
|
||||
vehicleLastLocation.value = {
|
||||
lat: parseFloat(load.value.vehicle.last_location_lat),
|
||||
lng: parseFloat(load.value.vehicle.last_location_lng)
|
||||
}
|
||||
}
|
||||
|
||||
switch (load.value.load_status) {
|
||||
case 'Loading':
|
||||
isLoadActive.value = true;
|
||||
break;
|
||||
case 'Transit':
|
||||
isLoadActive.value = true;
|
||||
break;
|
||||
case 'Downloading':
|
||||
isLoadActive.value = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
isLoadActive.value = false;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
notyStore.$patch({
|
||||
text : t('errors.map'),
|
||||
show : true,
|
||||
error: true
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
windowWidth.value = window.innerWidth
|
||||
if(windowWidth.value <= 1024){
|
||||
zoom.value = 4
|
||||
heightMap.value = 420;
|
||||
} else {
|
||||
zoom.value = 6;
|
||||
heightMap.value = 768;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" id="loadDetailModal" tabindex="-1" role="dialog" aria-labelledby="editcompany" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="title mt-2 mb-3">{{ t('loads.loadDetails') }}</h2>
|
||||
<button
|
||||
id="btnCloseloadDetailModal"
|
||||
type="button"
|
||||
class="close bg-white"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
@click="$emit('reset-proposal')"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body view-proposals">
|
||||
<Spiner v-if="isLoading"/>
|
||||
<div v-else>
|
||||
<div v-if="load">
|
||||
<Cardload :load="load" :read-only="true"/>
|
||||
<GoogleMap
|
||||
:api-key="mapKey"
|
||||
:center="{lat:19.432600, lng:-99.133209}"
|
||||
:zoom="zoom"
|
||||
:min-zoom="2"
|
||||
:max-zoom="12"
|
||||
:style="{width: 100 + '%', height: heightMap + 'px'}"
|
||||
:options="{
|
||||
zoomControl: true,
|
||||
mapTypeControl: true,
|
||||
scaleControl: true,
|
||||
streetViewControl: true,
|
||||
rotateControl: true,
|
||||
fullscreenControl: true
|
||||
}"
|
||||
>
|
||||
<Marker v-if="originCoords" :options="{position: originCoords, label: 'O', title: 'Destino'}" />
|
||||
<Marker v-if="destinationCoords" :options="{position: destinationCoords, label: 'D', title: 'Origen'}" />
|
||||
<CustomMarker
|
||||
v-if="vehicleLastLocation && load.vehicle.background_tracking && isLoadActive"
|
||||
:options="{position: vehicleLastLocation}"
|
||||
:clickable="false"
|
||||
:draggable="false"
|
||||
>
|
||||
<div style="text-align: center">
|
||||
<i class="fa-solid fa-truck" :style="{fontSize: 25 + 'px', color: 'green'}"></i>
|
||||
</div>
|
||||
</CustomMarker>
|
||||
<Polyline
|
||||
v-if="polylines"
|
||||
:options="{
|
||||
path: polylines,
|
||||
strokeColor: '#2D90BB',
|
||||
strokeOpacity: 0.7,
|
||||
strokeWeight: 5,
|
||||
clickable: true,
|
||||
fillColor: '#75ad3e',
|
||||
}"
|
||||
/>
|
||||
</GoogleMap>
|
||||
</div>
|
||||
<CardEmpty v-else :text="t('loads.noInfo')"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-dark"
|
||||
data-dismiss="modal"
|
||||
@click="$emit('reset-proposal')"
|
||||
>{{ t('buttons.close') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.box-form {
|
||||
width: 90%;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.custom-selected-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.box-btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
@@ -1,142 +0,0 @@
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useNotificationsStore } from '../stores/notifications';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
import { onMounted } from 'vue';
|
||||
import CustomRadioInput from './ui/CustomRadioInput.vue';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const lang = ref(null);
|
||||
|
||||
const noty = useNotificationsStore();
|
||||
const auth = useAuthStore();
|
||||
const router = useRouter();
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
onMounted(() => {
|
||||
lang.value = localStorage.getItem('lang') ?? 'es';
|
||||
locale.value = lang.value;
|
||||
});
|
||||
|
||||
watch(lang, () => {
|
||||
locale.value = lang.value
|
||||
localStorage.setItem('lang', lang.value)
|
||||
})
|
||||
|
||||
const closePopup = () => {
|
||||
noty.toggleProfile()
|
||||
}
|
||||
|
||||
const handleEditProfile = () => {
|
||||
closePopup();
|
||||
router.push({name: 'profile'})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="noty.openProfile"
|
||||
>
|
||||
<div
|
||||
class="content-profile"
|
||||
@click="closePopup()"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="profile-card">
|
||||
<i class="fa-solid fa-xmark close-icon" @click="closePopup()"></i>
|
||||
<h3>{{auth.user?.first_name}} {{ auth.user?.last_name }}</h3>
|
||||
<p>{{ auth.user?.email }}</p>
|
||||
<p>{{ auth.user?.phone }}</p>
|
||||
<p class="section-prefs">{{ t('global.prefs') }}</p>
|
||||
<p class="label-item">{{ t('global.lang') }}:</p>
|
||||
<CustomRadioInput
|
||||
value="es"
|
||||
:label="t('global.es')"
|
||||
name="lang"
|
||||
v-model:typeselected="lang"
|
||||
/>
|
||||
<CustomRadioInput
|
||||
value="en"
|
||||
:label="t('global.en')"
|
||||
name="lang"
|
||||
v-model:typeselected="lang"
|
||||
/>
|
||||
<br>
|
||||
<button
|
||||
class="btn btn-dark"
|
||||
data-toggle="modal"
|
||||
data-target="#editProfileModal"
|
||||
@click="handleEditProfile()"
|
||||
>{{ t('buttons.editProfile') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.content-profile {
|
||||
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;
|
||||
right: 20px;
|
||||
top: 70px;
|
||||
z-index: 2000;
|
||||
width: 320px;
|
||||
background-color: #FFF;
|
||||
opacity: 1;
|
||||
border-radius: 13px;
|
||||
padding: 20px 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.10));
|
||||
}
|
||||
|
||||
.section-prefs {
|
||||
margin-top: 20px !important;
|
||||
font-size: 1rem !important;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.label-item {
|
||||
font-weight: 900 !important;
|
||||
font-size: 1.2rem !important;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.profile-card h3 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.profile-card p {
|
||||
margin-top: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 16px;
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,284 +0,0 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useLoadsStore } from '../stores/loads';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import Spiner from './ui/Spiner.vue';
|
||||
import { getDateMonthDay } from '../helpers/date_formats';
|
||||
import VehicleInfo from './VehicleInfo.vue';
|
||||
import Swal from 'sweetalert2'
|
||||
import CardEmpty from './CardEmpty.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const loadsStore = useLoadsStore();
|
||||
const authStore = useAuthStore();
|
||||
const isLoading = ref(false);
|
||||
const isLoadingActions = ref(false);
|
||||
const { t } = useI18n();
|
||||
|
||||
onMounted(() => {
|
||||
getProposalsData()
|
||||
});
|
||||
|
||||
const getProposalsData = async() => {
|
||||
isLoading.value = true;
|
||||
await loadsStore.getProposalsOfLoads(loadsStore.currentLoad._id);
|
||||
console.log(loadsStore.proposalsOfLoads)
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
const clearMoal = () => {
|
||||
loadsStore.openProposalsModal = false;
|
||||
loadsStore.currentLoad = false;
|
||||
}
|
||||
|
||||
const handleAceptedProposal = async(proposal) => {
|
||||
|
||||
const load_id = proposal.load._id;
|
||||
|
||||
let loadData = {
|
||||
status : "Completed",
|
||||
contract_start_date: new Date(),
|
||||
carrier: proposal.carrier._id,
|
||||
vehicle: proposal.vehicle._id,
|
||||
}
|
||||
|
||||
console.log(proposal)
|
||||
|
||||
isLoadingActions.value = true;
|
||||
let load = await loadsStore.updateLoad(load_id, loadData);
|
||||
if(load != null) {
|
||||
const dataLocal = {
|
||||
company: authStore.user.company,
|
||||
categories: loadsStore?.currentLoad?.categories,
|
||||
product: loadsStore?.currentLoad?.product
|
||||
};
|
||||
const index = loadsStore.loads.findIndex((load) => load._id === load_id);
|
||||
loadsStore.loads[index] = {
|
||||
...loadsStore.loads[index],
|
||||
...load,
|
||||
...dataLocal,
|
||||
carrier: proposal.carrier,
|
||||
vehicle: proposal.vehicle,
|
||||
};
|
||||
const proposal_id = proposal._id;
|
||||
|
||||
let formData = {
|
||||
accepted_by : authStore.user,
|
||||
accepted_date : new Date(),
|
||||
is_accepted : true,
|
||||
}
|
||||
const resp = await loadsStore.updateProposal(proposal_id, formData);
|
||||
if(resp){
|
||||
const index = loadsStore.proposalsOfLoads.findIndex((p) => p._id === proposal_id);
|
||||
loadsStore.proposalsOfLoads[index] = {
|
||||
...proposal,
|
||||
...formData
|
||||
};
|
||||
Swal.fire({
|
||||
title: t('proposals.titleOfferAccept'),
|
||||
text: t('proposals.msgOfferAccept'),
|
||||
icon: "success"
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: "Error!",
|
||||
text: t('proposals.msgErrorAcceptOffer'),
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: "Error!",
|
||||
text: t('proposals.msgNotAcceptOffer'),
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
isLoadingActions.value = false;
|
||||
|
||||
}
|
||||
|
||||
const handleCancelProposal = async(proposal) => {
|
||||
const proposal_id = proposal._id;
|
||||
const load_id = proposal.load._id;
|
||||
const {isConfirmed} = await Swal.fire({
|
||||
title: t('proposals.titleCanceModal'),
|
||||
text: t('proposals.textCancelModal'),
|
||||
icon: 'warning',
|
||||
cancelButtonColor: "#d33",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t('proposals.confirmCancel'),
|
||||
cancelButtonText: 'No'
|
||||
})
|
||||
if( isConfirmed ) {
|
||||
let loadData = {
|
||||
status : "Published",
|
||||
contract_start_date: null,
|
||||
carrier: null,
|
||||
vehicle: null,
|
||||
}
|
||||
|
||||
isLoadingActions.value = true;
|
||||
let load = await loadsStore.updateLoad(load_id, loadData);
|
||||
if(load) {
|
||||
const dataLocal = {
|
||||
company: authStore.user.company,
|
||||
categories: loadsStore?.currentLoad?.categories,
|
||||
product: loadsStore?.currentLoad?.product
|
||||
};
|
||||
const index = loadsStore.loads.findIndex((load) => load._id === load_id);
|
||||
loadsStore.loads[index] = {
|
||||
...loadsStore.loads[index],
|
||||
...load,
|
||||
...dataLocal
|
||||
};
|
||||
let formData = {
|
||||
accepted_by : null,
|
||||
accepted_date : null,
|
||||
is_accepted : false,
|
||||
}
|
||||
const resp = await loadsStore.updateProposal(proposal_id, formData);
|
||||
if(resp) {
|
||||
const index = loadsStore.proposalsOfLoads.findIndex((p) => p._id === proposal._id);
|
||||
loadsStore.proposalsOfLoads[index] = {
|
||||
...proposal,
|
||||
...formData
|
||||
};
|
||||
Swal.fire({
|
||||
title: t('proposals.msgTitleCancel'),
|
||||
text: t('proposals.msgCancel'),
|
||||
icon: "success"
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: "Error!",
|
||||
text: t('proposals.msgNotCancel'),
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: "Error!",
|
||||
text: t('errors.generic'),
|
||||
icon: "error"
|
||||
});
|
||||
}
|
||||
isLoadingActions.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" id="proposalsModal" tabindex="-1" role="dialog" aria-labelledby="editcompany" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="title mt-2 mb-3">{{ t('loads.offers') }}</h2>
|
||||
<button
|
||||
id="btnCloseProposalsModal"
|
||||
type="button"
|
||||
@click="clearMoal"
|
||||
class="close bg-white" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body view-proposals">
|
||||
<Spiner v-if="isLoading"/>
|
||||
<div v-else>
|
||||
<div v-if="loadsStore.proposalsOfLoads.length > 0" v-for="proposal in loadsStore.proposalsOfLoads" class="card-fixed card-proposal">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<p>{{ t('global.company') }}: <span>{{ proposal.carrier.company_name }}</span></p>
|
||||
<p>{{ t('proposals.bidder') }}: <span>{{ proposal.bidder.first_name }} {{ proposal.bidder.last_name }}</span></p>
|
||||
<p>{{ t('proposals.numCarrier') }}: <span>{{proposal.carrier.company_code}}</span></p>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<p>{{ t('labels.date') }}: <span>{{ getDateMonthDay(proposal.carrier.createdAt) }}</span></p>
|
||||
<p>RFC: <span>{{proposal.carrier.rfc}}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="proposal.comment" class="box-note">
|
||||
{{ proposal.comment }}
|
||||
</div>
|
||||
<VehicleInfo
|
||||
v-if="proposal.vehicle"
|
||||
:vehicle="proposal.vehicle"
|
||||
:driver="proposal.driver"
|
||||
/>
|
||||
<Spiner v-if="isLoadingActions"/>
|
||||
<div class="d-flex justify-content-end gap-3" v-else>
|
||||
<div v-if="proposal.is_accepted" class="indicator-check">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
{{ t('buttons.accepted') }}
|
||||
</div>
|
||||
<button v-if="!proposal.is_accepted"
|
||||
type="button"
|
||||
class="btn-primary-sm"
|
||||
@click="handleAceptedProposal(proposal)"
|
||||
>
|
||||
<i class="fa-solid fa-check"></i>
|
||||
{{ t('buttons.accept') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="proposal.load.load_status !== 'Delivered' && proposal.is_accepted"
|
||||
class="btn-primary-sm"
|
||||
@click="handleCancelProposal(proposal)"
|
||||
>
|
||||
<i class="fa-solid fa-ban clear-sm"></i>
|
||||
{{ t('buttons.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<CardEmpty v-else :text="t('proposals.empty')"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-dark"
|
||||
@click="clearMoal"
|
||||
data-dismiss="modal">{{ t('buttons.close') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.view-proposals {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-proposal {
|
||||
flex-direction: column;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: #323032;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p span {
|
||||
color: #323032;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.indicator-check {
|
||||
width: 120px;
|
||||
padding: 10px 12px;
|
||||
background: #FFF;
|
||||
border: 1px solid green;
|
||||
border-radius: 50px;
|
||||
color: green;
|
||||
}
|
||||
|
||||
.box-note {
|
||||
padding: 12px 16px;
|
||||
background-color: aqua;
|
||||
border-radius: 13px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,206 +0,0 @@
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import Spiner from './ui/Spiner.vue';
|
||||
import CustomRadioInput from './ui/CustomRadioInput.vue';
|
||||
import { useVehiclesStore } from '../stores/vehicles';
|
||||
import CustomInput from './ui/CustomInput.vue';
|
||||
import States from './ui/States.vue';
|
||||
import Cities from './ui/Cities.vue';
|
||||
import Swal from 'sweetalert2';
|
||||
import { getDateTime } from '../helpers/date_formats';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
vehicle: {
|
||||
type: Object
|
||||
}
|
||||
});
|
||||
|
||||
const statusSelected = ref(null);
|
||||
const loading = ref(false);
|
||||
const vehicleStore = useVehiclesStore();
|
||||
|
||||
onMounted(() => {
|
||||
statusSelected.value = props.vehicle.is_available === true ? 'Availiable' : 'Booked'
|
||||
|
||||
const [destinoCityName = '', destinoStateName = ''] = props.vehicle.destino?.split(';') || [];
|
||||
const [availableCityName = '', availableStateName = ''] = props.vehicle.available_in?.split(';') || [];
|
||||
const date = getDateTime( props.vehicle?.available_date || new Date(), 0);
|
||||
formAvailiable.destinoCity = destinoCityName ? { city_name: destinoCityName } : '';
|
||||
formAvailiable.destinoState = destinoStateName ? { state_name: destinoStateName } : '';
|
||||
formAvailiable.availableCity = availableCityName ? { city_name: availableCityName } : '';
|
||||
formAvailiable.availableState = availableStateName ? { state_name: availableStateName } : '';
|
||||
formAvailiable.available_date = date.substring(0, 10);
|
||||
});
|
||||
|
||||
defineEmits(['reset-vehicle']);
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const formAvailiable = reactive({
|
||||
available_date: new Date(),
|
||||
availableCity: '',
|
||||
availableState: '',
|
||||
destinoCity: '',
|
||||
destinoState: '',
|
||||
});
|
||||
|
||||
const errors = ref({
|
||||
destinoState: null,
|
||||
destinoCity: null,
|
||||
availableState : null,
|
||||
availableCity : null,
|
||||
})
|
||||
|
||||
const handleSetStatusVehicle = async() => {
|
||||
let vehicleData;
|
||||
const currentDate = new Date();
|
||||
const hours = currentDate.getHours().toString().padStart(2, '0');
|
||||
const minutes = currentDate.getMinutes().toString().padStart(2, '0');
|
||||
const seconds = currentDate.getSeconds().toString().padStart(2, '0');
|
||||
if(statusSelected.value === 'Availiable') {
|
||||
validations();
|
||||
if(errors.value.availableCity || errors.value.availableState || errors.value.destinoCity || errors.value.destinoState ) return;
|
||||
vehicleData = {
|
||||
available_date : new Date(`${formAvailiable.available_date}T${hours}:${minutes}:${seconds}`).toISOString(),
|
||||
destino: formAvailiable.destinoCity.city_name + ';' + formAvailiable.destinoState.state_name,
|
||||
available_in: formAvailiable.availableCity.city_name + ';' + formAvailiable.availableState.state_name,
|
||||
is_available : true
|
||||
}
|
||||
} else {
|
||||
vehicleData = {
|
||||
available_date : null,
|
||||
is_available : false
|
||||
}
|
||||
}
|
||||
let localData = {
|
||||
driver: props.vehicle.driver,
|
||||
categories: props.vehicle.categories,
|
||||
active_load: props.vehicle.active_load,
|
||||
}
|
||||
loading.value = true;
|
||||
const result = await vehicleStore.updateVehicleCompany(props.vehicle._id, vehicleData, localData);
|
||||
loading.value = false;
|
||||
if(result === 'success') {
|
||||
document.getElementById('btnCloseeditStatusVehicle').click();
|
||||
Swal.fire({
|
||||
title: `<strong>${t('vehicles.msgStatusUpdate')}</strong>`,
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
Swal.fire({
|
||||
title: result,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const validations = () => {
|
||||
errors.value = {
|
||||
destinoState: (formAvailiable.destinoState === null || formAvailiable.destinoState === '') ? t('errors.state') : null,
|
||||
destinoCity: (formAvailiable.destinoCity === null || formAvailiable.destinoCity === '') ? t('errors.city') : null,
|
||||
availableState: (!formAvailiable.availableState) ? t('errors.state') : null,
|
||||
availableCity: (!formAvailiable.availableCity) ? t('errors.city') : null,
|
||||
};
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" id="editStatusVehicle" tabindex="-1" role="dialog" aria-labelledby="editStatusVehicle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="title mt-2 mb-3">{{ t('vehicles.changeStatus') }}</h2>
|
||||
<button
|
||||
id="btnCloseeditStatusVehicle"
|
||||
type="button"
|
||||
class="close bg-white"
|
||||
data-dismiss="modal"
|
||||
@click="$emit('reset-vehicle')"
|
||||
aria-label="Close"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body view-proposals">
|
||||
<form @submit.prevent="handleSetStatusVehicle">
|
||||
<div class="custom-selected-field">
|
||||
<h4 class="custom-label my-3">{{ t('vehicles.statusVehicle') }}</h4>
|
||||
<div class="d-flex">
|
||||
<CustomRadioInput
|
||||
value="Booked"
|
||||
:label="t('vehicles.reserved')"
|
||||
:name="'status-vehicle' + vehicle._id"
|
||||
v-model:typeselected="statusSelected"
|
||||
/>
|
||||
<CustomRadioInput
|
||||
value="Availiable"
|
||||
:label="t('vehicles.available')"
|
||||
:name="'status-vehicle' + vehicle._id"
|
||||
v-model:typeselected="statusSelected"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="statusSelected === 'Availiable'">
|
||||
<CustomInput
|
||||
:label="t('vehicles.availableDate') + '*'"
|
||||
type="date"
|
||||
:filled="false"
|
||||
name="date-load"
|
||||
v-model:field="formAvailiable.available_date"
|
||||
/>
|
||||
<p>Disponible en:</p>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('global.state') }}</label>
|
||||
<States
|
||||
v-model="formAvailiable.availableState"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.availableState">{{ errors.availableState }}</span>
|
||||
</div>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('global.city') }}</label>
|
||||
<Cities
|
||||
v-model="formAvailiable.availableCity"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.availableCity">{{ errors.availableCity }}</span>
|
||||
</div>
|
||||
<p>Destino:</p>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('global.state') }}</label>
|
||||
<States
|
||||
key="destinoState"
|
||||
v-model="formAvailiable.destinoState"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.destinoState">{{ errors.destinoState }}</span>
|
||||
</div>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('global.city') }}</label>
|
||||
<Cities
|
||||
key="destinoCity"
|
||||
v-model="formAvailiable.destinoCity"
|
||||
/>
|
||||
<span class="error-msg" v-if="errors.destinoCity">{{ errors.destinoCity }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 text-center">
|
||||
<Spiner v-if="loading === true"/>
|
||||
<button
|
||||
v-else
|
||||
class="btn-primary-sm radius-sm"
|
||||
type="submit"
|
||||
>
|
||||
<span class="clear-xsm">{{ t('buttons.save') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,174 +0,0 @@
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import Spiner from '../ui/Spiner.vue';
|
||||
import NotificationBadge from './NotificationBadge.vue';
|
||||
import Segments from './Segments.vue';
|
||||
import TruckTypes from './TruckTypes.vue';
|
||||
import Cities from './Cities.vue';
|
||||
import States from './States.vue';
|
||||
import Custominput from './CustomInput.vue';
|
||||
import { useCompanyStore } from '../../stores/company';
|
||||
import { useNotificationsStore } from '../../stores/notifications';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const companyStore = useCompanyStore()
|
||||
const notifications = useNotificationsStore()
|
||||
const loading = ref(false);
|
||||
const msgError = ref('');
|
||||
const companyCategories = ref([]);
|
||||
const companyStates = ref([]);
|
||||
const companyCity = ref([]);
|
||||
const companyTruckType = ref([]);
|
||||
const { t } = useI18n()
|
||||
|
||||
onMounted(() => {
|
||||
if(companyStore.company){
|
||||
companyCategories.value = companyStore.company.categories;
|
||||
}
|
||||
if(companyStore.company){
|
||||
companyStates.value = companyStore.company.company_state.map(m =>{
|
||||
return { state_name: m };
|
||||
});
|
||||
}
|
||||
if(companyStore.company){
|
||||
companyCity.value = companyStore.company.company_city.map(m =>{
|
||||
return { city_name: m };
|
||||
});
|
||||
}
|
||||
if(companyStore.company){
|
||||
companyTruckType.value = companyStore.company.truck_type.map(m =>{
|
||||
return { meta_value: m };
|
||||
});
|
||||
}
|
||||
})
|
||||
const company = reactive({
|
||||
segments: companyCategories,
|
||||
states: companyStates,
|
||||
cities: companyCity,
|
||||
truckTypes: companyTruckType,
|
||||
description: companyStore.company?.company_description ?? '',
|
||||
});
|
||||
|
||||
const handleSave = async() => {
|
||||
const resp = validations();
|
||||
if(resp !== '') {
|
||||
msgError.value = resp;
|
||||
clearMessages();
|
||||
} else {
|
||||
loading.value = true;
|
||||
let companyData = {
|
||||
is_company: companyStore.company._id,
|
||||
company_type: companyStore.company.company_type,
|
||||
meta_data: companyStore.company.meta_data,
|
||||
categories: company.segments.map((e) => e),
|
||||
company_city: company.cities.map((e) => e.city_name),
|
||||
company_state: company.states.map((e) => e.state_name),
|
||||
truck_type: company.truckTypes.map((e) => e.meta_value),
|
||||
company_description: company.description
|
||||
};
|
||||
// console.log( companyData )
|
||||
const result = await companyStore.editCompany(companyData);
|
||||
loading.value = false;
|
||||
if(result === 'success') {
|
||||
document.getElementById('btnCloseEditCompany').click();
|
||||
notifications.show = true;
|
||||
notifications.text = t('messages.updateCompany');
|
||||
} else {
|
||||
msgError.value === result;
|
||||
clearMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const clearMessages = () => {
|
||||
setTimeout(() => {
|
||||
msgError.value = '';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
const validations = () => {
|
||||
if(company.segments.length === 0) {
|
||||
return t('errors.segments');
|
||||
}else if(company.states.length === 0) {
|
||||
return t('errors.states');
|
||||
} else if( company.cities.length === 0) {
|
||||
msgError.value = t('errors.cities');
|
||||
} else if(company.truckTypes.length === 0){
|
||||
msgError.value = t('errors.trucks');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="modal fade" id="editcompanymodal" tabindex="-1" role="dialog" aria-labelledby="editcompany" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="title mt-2 mb-3">{{ t('company.edit') }}</h2>
|
||||
<button id="btnCloseEditCompany" type="button" class="close bg-white" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form @submit.prevent="handleSave" class="view-form">
|
||||
<NotificationBadge :msg="msgError" v-if="msgError != ''"/>
|
||||
<div class="mb-4 mt-3">
|
||||
<label class="custom-label">{{ t('labels.segmentsCompany') }}</label>
|
||||
<Segments
|
||||
v-model="company.segments"
|
||||
:multiple="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="custom-label">{{ t('labels.locationLoadState') }}</label>
|
||||
<States
|
||||
v-model="company.states"
|
||||
:multiple="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="custom-label">{{ t('labels.locationLoadCity') }}</label>
|
||||
<Cities
|
||||
v-model="company.cities"
|
||||
:multiple="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="custom-label">{{ t('labels.truckUsed') }}</label>
|
||||
<TruckTypes
|
||||
v-model="company.truckTypes"
|
||||
:multiple="true"
|
||||
/>
|
||||
</div>
|
||||
<Custominput
|
||||
:label=" t('labels.infoCompany')"
|
||||
type="text"
|
||||
name="description"
|
||||
:filled="false"
|
||||
v-model:field="company.description"
|
||||
/>
|
||||
<Spiner v-if="loading"/>
|
||||
<input
|
||||
v-else
|
||||
type="submit"
|
||||
:value="t('buttons.save')" class="btn-primary-lg btn-lg-block my-4">
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-dark" data-dismiss="modal">{{t('buttons.close')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.view-form {
|
||||
padding: 1rem 4rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user