0
0
Fork 0
This commit is contained in:
William Buezas 2024-05-21 09:50:36 -03:00
parent a8fef3fa84
commit 84845b7958
8 changed files with 449 additions and 23 deletions

View File

@ -20,6 +20,8 @@ const props = defineProps({
searchUrl: { type: String, default: undefined }, searchUrl: { type: String, default: undefined },
searchbarLabel: { type: String, default: '' }, searchbarLabel: { type: String, default: '' },
searchbarInfo: { type: String, default: '' }, searchbarInfo: { type: String, default: '' },
searchCustomRouteRedirect: { type: String, default: undefined },
searchRedirect: { type: Boolean, default: false },
}); });
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -62,6 +64,8 @@ watchEffect(() => {
:url="props.searchUrl" :url="props.searchUrl"
:label="props.searchbarLabel" :label="props.searchbarLabel"
:info="props.searchbarInfo" :info="props.searchbarInfo"
:custom-route-redirect-name="searchCustomRouteRedirect"
:redirect="searchRedirect"
/> />
</slot> </slot>
</Teleport> </Teleport>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref, watch } from 'vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
@ -67,11 +67,19 @@ const props = defineProps({
}, },
}); });
const arrayData = useArrayData(props.dataKey, { ...props }); let arrayData = useArrayData(props.dataKey, { ...props });
const { store } = arrayData; let store = arrayData.store;
const searchText = ref(''); const searchText = ref('');
const { navigate } = useRedirect(); const { navigate } = useRedirect();
watch(
() => props.dataKey,
(val) => {
arrayData = useArrayData(val, { ...props });
store = arrayData.store;
}
);
onMounted(() => { onMounted(() => {
const params = store.userParams; const params = store.userParams;
if (params && params.search) { if (params && params.search) {

View File

@ -0,0 +1,256 @@
<script setup>
import { computed, onMounted, ref, watch, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n';
import ZoneDeliveryCalendar from '../ZoneDeliveryCalendar.vue';
import { useStateStore } from 'stores/useStateStore';
import { useWeekdayStore } from 'src/stores/useWeekdayStore';
import { useArrayData } from 'src/composables/useArrayData';
const { t } = useI18n();
const stateStore = useStateStore();
const weekdayStore = useWeekdayStore();
const nMonths = ref(4);
const _date = ref(Date.vnNew());
const _data = ref(null);
const firstDay = ref(null);
const lastDay = ref(null);
const months = ref([]);
const days = ref({});
const exclusions = ref({});
const geoExclusions = ref({});
const events = ref([]);
const arrayData = useArrayData('ZoneDeliveryDays', {
url: 'Zones/getEvents',
});
const { store } = arrayData;
const refreshEvents = () => {
days.value = {};
if (!data.value) return;
let day = new Date(firstDay.value.getTime());
while (day <= lastDay.value) {
let stamp = day.getTime();
let wday = day.getDay();
let dayEvents = [];
let _exclusions = exclusions.value[stamp] || [];
if (events.value) {
for (let event of events.value) {
let match;
switch (event.type) {
case 'day':
match = event.dated == stamp;
break;
default:
match =
event.wdays[wday] &&
(!event.started || stamp >= event.started) &&
(!event.ended || stamp <= event.ended);
break;
}
if (match && !_exclusions.find((e) => e.zoneFk == event.zoneFk)) {
dayEvents.push(event);
}
}
}
if (dayEvents.length) days.value[stamp] = dayEvents;
day.setDate(day.getDate() + 1);
}
};
const date = computed({
get: () => _date.value,
set: (value) => {
_date.value = value;
let stamp = value.getTime();
firstDay.value = new Date(stamp);
firstDay.value.setDate(1);
lastDay.value = new Date(stamp);
lastDay.value.setMonth(lastDay.value.getMonth() + nMonths.value);
lastDay.value.setDate(0);
months.value = [];
for (let i = 0; i < nMonths.value; i++) {
let monthDate = new Date(stamp);
monthDate.setMonth(value.getMonth() + i);
months.value.push(monthDate);
}
refreshEvents();
},
});
const data = computed({
get: () => {
return _data.value;
},
set: (value) => {
_data.value = value;
value = value || {};
events.value = value.events;
function toStamp(date) {
return date && new Date(date).setHours(0, 0, 0, 0);
}
exclusions.value = {};
let _exclusions = value.exclusions;
if (_exclusions) {
for (let exclusion of _exclusions) {
let stamp = toStamp(exclusion.dated);
if (!exclusions[stamp]) exclusions.value[stamp] = [];
exclusions.value[stamp].push(exclusion);
}
}
geoExclusions.value = {};
let _geoExclusions = value.geoExclusions;
if (_geoExclusions) {
for (let geoExclusion of _geoExclusions) {
let stamp = toStamp(geoExclusion.dated);
if (!geoExclusions[stamp]) geoExclusions.value[stamp] = [];
geoExclusions.value[stamp].push(geoExclusion);
}
}
let _events = value.events;
if (_events) {
for (let event of _events) {
event.dated = toStamp(event.dated);
event.ended = toStamp(event.ended);
event.started = toStamp(event.started);
event.wdays = weekdayStore.fromSet(event.weekDays || '');
}
}
refreshEvents();
},
});
watch(
() => store.data,
(value) => {
data.value = value;
},
{ immediate: true }
);
const getMonthNameAndYear = (date) => {
const monthName = weekdayStore.getLocaleMonths[date.getMonth()].locale;
const year = date.getFullYear();
return `${monthName} ${year}`;
};
const headerTitle = computed(() => {
if (!months.value?.length) return;
const firstMonth = getMonthNameAndYear(months.value[0]);
const lastMonth = getMonthNameAndYear(months.value[months.value.length - 1]);
return `${firstMonth} - ${lastMonth}`;
});
const step = (direction) => {
const _date = new Date(date.value);
_date.setMonth(_date.getMonth() + nMonths.value * direction);
date.value = _date;
};
onMounted(async () => {
stateStore.rightDrawer = true;
let initialDate = Date.vnNew();
initialDate.setDate(1);
initialDate.setHours(0, 0, 0, 0);
date.value = initialDate;
weekdayStore.initStore();
});
onUnmounted(() => arrayData.destroy());
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<!-- <ZoneCalendarPanel /> -->
</QScrollArea>
</QDrawer>
<QPage class="q-pa-md flex justify-center">
<QCard style="height: max-content">
<div class="calendars-header">
<QBtn
icon="arrow_left"
size="sm"
flat
class="full-height"
@click="step(-1)"
/>
<span>{{ headerTitle }}</span>
<QBtn
icon="arrow_right"
size="sm"
flat
class="full-height"
@click="step(1)"
/>
</div>
<div class="calendars-container">
<ZoneDeliveryCalendar
v-for="(month, index) in months"
:key="index"
:month="month.getMonth() + 1"
:year="month.getFullYear()"
:month-date="month"
:geo-exclusions="geoExclusions"
:exclusions="exclusions"
:days-map="days"
/>
</div>
</QCard>
</QPage>
</template>
<style lang="scss" scoped>
.calendars-header {
height: 45px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: $primary;
font-weight: bold;
font-size: 16px;
}
.calendars-container {
max-width: 800px;
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
}
</style>

View File

@ -0,0 +1,149 @@
<script setup>
import { onMounted, ref, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import { useArrayData } from 'src/composables/useArrayData';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
import { watch } from 'vue';
const { t } = useI18n();
const { notify } = useNotify();
const deliveryMethodFk = ref(null);
const deliveryMethods = ref([]);
const formData = reactive({});
const arrayData = useArrayData('ZoneDeliveryDays', {
url: 'Zones/getEvents',
});
const fetchDeliveryMethods = async (filter) => {
try {
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get('DeliveryMethods', { params });
return data.map((deliveryMethod) => deliveryMethod.id);
} catch (err) {
console.error('Error fetching delivery methods: ', err);
}
};
watch(
() => deliveryMethodFk.value,
async (val) => {
let filter;
if (val === 'pickUp') filter = { where: { code: 'PICKUP' } };
else filter = { where: { code: { inq: ['DELIVERY', 'AGENCY'] } } };
deliveryMethods.value = await fetchDeliveryMethods(filter);
},
{ immediate: true }
);
const fetchData = async (params) => {
try {
const { data } = params
? await arrayData.applyFilter({
params,
})
: await arrayData.fetch({ append: false });
if (!data.events || !data.events.length)
notify(t('deliveryPanel.noEventsWarning'), 'warning');
} catch (err) {
console.error('Error fetching events: ', err);
}
};
const onSubmit = async () => {
const params = { deliveryMethodFk: deliveryMethodFk.value, ...formData };
await fetchData(params);
};
onMounted(async () => {
deliveryMethodFk.value = 'delivery';
formData.geoFk = arrayData.store?.userParams?.geoFk;
formData.agencyModeFk = arrayData.store?.userParams?.agencyModeFk;
if (formData.geoFk || formData.agencyModeFk) await fetchData();
});
</script>
<template>
<QForm @submit="onSubmit()" class="q-pa-md">
<div class="column q-gutter-y-sm">
<QRadio
v-model="deliveryMethodFk"
dense
val="pickUp"
:label="t('deliveryPanel.pickup')"
/>
<QRadio
v-model="deliveryMethodFk"
dense
val="delivery"
:label="t('deliveryPanel.delivery')"
class="q-mb-sm"
/>
<VnSelect
v-if="deliveryMethodFk === 'delivery'"
:label="t('deliveryPanel.postcode')"
v-model="formData.geoFk"
url="Postcodes/location"
:fields="['geoFk', 'code', 'townFk']"
sort-by="code, townFk"
option-value="geoFk"
option-label="code"
option-filter="code"
hide-selected
dense
outlined
rounded
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection v-if="opt.code">
<QItemLabel>{{ opt.code }}</QItemLabel>
<QItemLabel caption
>{{ opt.town?.province?.name }},
{{ opt.town?.province?.country?.country }}</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnSelect
:label="
t(
deliveryMethodFk === 'delivery'
? 'deliveryPanel.agency'
: 'deliveryPanel.warehouse'
)
"
v-model="formData.agencyModeFk"
url="AgencyModes/isActive"
:fields="['id', 'name']"
:where="{
deliveryMethodFk: { inq: deliveryMethods },
}"
sort-by="name ASC"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
</div>
<QBtn
:label="t('deliveryPanel.query')"
type="submit"
color="primary"
class="q-mt-md full-width"
unelevated
rounded
dense
/>
</QForm>
</template>

View File

@ -5,38 +5,30 @@ import { computed } from 'vue';
import VnCard from 'components/common/VnCard.vue'; import VnCard from 'components/common/VnCard.vue';
import ZoneDescriptor from './ZoneDescriptor.vue'; import ZoneDescriptor from './ZoneDescriptor.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
const routeName = computed(() => route.name); const routeName = computed(() => route.name);
const customRouteRedirectName = computed(() => {
if (routeName.value === 'ZoneLocations') return null;
return routeName.value;
});
const searchBarDataKeys = { const searchBarDataKeys = {
ZoneWarehouses: 'ZoneWarehouses', ZoneWarehouses: 'ZoneWarehouses',
ZoneSummary: 'ZoneSummary', ZoneSummary: 'ZoneSummary',
ZoneLocations: 'ZoneLocations',
ZoneCalendar: 'ZoneCalendar',
}; };
</script> </script>
<template> <template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
:data-key="searchBarDataKeys[routeName]"
:custom-route-redirect-name="routeName"
:label="t('list.searchZone')"
:info="t('list.searchInfo')"
/>
</Teleport>
</template>
<VnCard <VnCard
data-key="Zone" data-key="Zone"
base-url="Zones"
:descriptor="ZoneDescriptor" :descriptor="ZoneDescriptor"
searchbar-data-key="ZoneList" :search-data-key="searchBarDataKeys[routeName]"
searchbar-url="Zones/filter" :search-custom-route-redirect="customRouteRedirectName"
searchbar-label="Search zones" :search-redirect="!!customRouteRedirectName"
searchbar-info="You can search by zone reference" :searchbar-label="t('list.searchZone')"
:searchbar-info="t('list.searchInfo')"
/> />
</template> </template>

View File

@ -7,6 +7,7 @@ zone:
deliveryDays: Delivery days deliveryDays: Delivery days
upcomingList: Upcoming deliveries upcomingList: Upcoming deliveries
warehouses: Warehouses warehouses: Warehouses
calendar: Calendar
list: list:
clone: Clone clone: Clone
id: Id id: Id

View File

@ -7,6 +7,7 @@ zone:
deliveryDays: Días de entrega deliveryDays: Días de entrega
upcomingList: Próximos repartos upcomingList: Próximos repartos
warehouses: Almacenes warehouses: Almacenes
calendar: Calendario
list: list:
clone: Clonar clone: Clonar
id: Id id: Id

View File

@ -12,7 +12,13 @@ export default {
redirect: { name: 'ZoneMain' }, redirect: { name: 'ZoneMain' },
menus: { menus: {
main: ['ZoneList', 'ZoneDeliveryDays', 'ZoneUpcomingList'], main: ['ZoneList', 'ZoneDeliveryDays', 'ZoneUpcomingList'],
card: ['ZoneBasicData', 'ZoneWarehouses', 'ZoneHistory', 'ZoneLocations'], card: [
'ZoneBasicData',
'ZoneWarehouses',
'ZoneHistory',
'ZoneLocations',
'ZoneCalendar',
],
}, },
children: [ children: [
{ {
@ -110,6 +116,15 @@ export default {
}, },
component: () => import('src/pages/Zone/Card/ZoneLog.vue'), component: () => import('src/pages/Zone/Card/ZoneLog.vue'),
}, },
{
name: 'ZoneCalendar',
path: 'events',
meta: {
title: 'calendar',
icon: 'vn:calendar',
},
component: () => import('src/pages/Zone/Card/ZoneCalendar.vue'),
},
], ],
}, },
], ],