0
0
Fork 0

Merge pull request 'feature/ms-68-WorkersDepartments' (#45) from feature/ms-68-WorkersDepartments into dev

Reviewed-on: hyervoni/salix-front-mindshore#45
This commit is contained in:
William Buezas 2023-12-28 15:07:33 +00:00
commit 59e2e20827
15 changed files with 790 additions and 19 deletions

View File

@ -26,10 +26,6 @@ const closeButton = ref(null);
const countriesOptions = ref([]);
const loading = ref(false);
const setCountriesOptions = (data) => {
countriesOptions.value = data;
};
const onDataSaved = (data) => {
emit('onDataSaved', data);
closeForm();
@ -43,7 +39,7 @@ const closeForm = () => {
<template>
<FetchData
url="Countries"
@on-fetch="(data) => setCountriesOptions(data)"
@on-fetch="(data) => (countriesOptions = data)"
:filter="countriesFilter"
auto-load
/>

View File

@ -0,0 +1,100 @@
<script setup>
import { onMounted, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FormModel from 'components/FormModel.vue';
const emit = defineEmits(['onDataSaved']);
const $props = defineProps({
parentId: {
type: Number,
default: null,
},
});
const { t } = useI18n();
const departmentChildData = reactive({
name: null,
});
const closeButton = ref(null);
const isLoading = ref(false);
const onDataSaved = () => {
emit('onDataSaved');
closeForm();
};
const closeForm = () => {
if (closeButton.value) closeButton.value.click();
};
onMounted(() => {
if ($props.parentId) departmentChildData.parentId = $props.parentId;
});
</script>
<template>
<FormModel
:form-initial-data="departmentChildData"
:observe-form-changes="false"
:default-actions="false"
url-create="departments/createChild"
@on-data-saved="onDataSaved()"
>
<template #form="{ data }">
<span ref="closeButton" class="close-icon" v-close-popup>
<QIcon name="close" size="sm" />
</span>
<h1 class="title">{{ t('New department') }}</h1>
<VnRow class="row q-gutter-md q-mb-md" style="min-width: 250px">
<div class="col">
<VnInput :label="t('Name')" v-model="data.name" />
</div>
</VnRow>
<div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.cancel')"
type="reset"
color="primary"
flat
class="q-ml-sm"
:disabled="isLoading"
:loading="isLoading"
v-close-popup
/>
<QBtn
:label="t('globals.save')"
type="submit"
color="primary"
:disabled="isLoading"
:loading="isLoading"
/>
</div>
</template>
</FormModel>
</template>
<style lang="scss" scoped>
.close-icon {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
}
.title {
font-size: 17px;
font-weight: bold;
line-height: 20px;
}
</style>
<i18n>
es:
Name: Nombre
New department: Nuevo departamento
</i18n>

View File

@ -58,7 +58,7 @@ const $props = defineProps({
mapper: {
type: Function,
default: null,
}
},
});
const emit = defineEmits(['onFetch', 'onDataSaved']);
@ -129,7 +129,7 @@ async function save() {
isLoading.value = true;
try {
const body = $props.mapper ? $props.mapper(formData.value) : formData.value
const body = $props.mapper ? $props.mapper(formData.value) : formData.value;
if ($props.urlCreate) {
await axios.post($props.urlCreate, body);
notify('globals.dataCreated', 'positive');
@ -179,11 +179,14 @@ watch(formUrl, async () => {
});
</script>
<template>
<QBanner v-if="$props.observeFormChanges && hasChanges" class="text-white bg-warning">
<QBanner
v-if="$props.observeFormChanges && hasChanges"
class="text-white bg-warning full-width"
>
<QIcon name="warning" size="md" class="q-mr-md" />
<span>{{ t('globals.changesToSave') }}</span>
</QBanner>
<div class="column items-center">
<div class="column items-center full-width">
<QForm
v-if="formData"
@submit="save"

View File

@ -0,0 +1,158 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar';
import CreateDepartmentChild from '../CreateDepartmentChild.vue';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
const quasar = useQuasar();
const { t } = useI18n();
const router = useRouter();
const { notify } = useNotify();
const treeRef = ref(null);
const showCreateNodeFormVal = ref(false);
const creationNodeSelectedId = ref(null);
const expanded = ref([]);
const nodes = ref([{ id: null, name: t('Departments'), sons: true, children: [{}] }]);
const fetchedChildrensSet = ref(new Set());
const onNodeExpanded = (nodeKeysArray) => {
// Verificar si el nodo ya fue expandido
if (!fetchedChildrensSet.value.has(nodeKeysArray.at(-1))) {
fetchedChildrensSet.value.add(nodeKeysArray.at(-1));
fetchNodeLeaves(nodeKeysArray.at(-1)); // Llamar a la función para obtener los nodos hijos
}
};
const fetchNodeLeaves = async (nodeKey) => {
try {
const node = treeRef.value.getNodeByKey(nodeKey);
if (!node || node.sons === 0) return;
const params = { parentId: node.id };
const response = await axios.get('/departments/getLeaves', { params });
// Si hay datos en la respuesta y tiene hijos, agregarlos al nodo actual
if (response.data) {
node.children = response.data;
node.children.forEach((node) => {
if (node.sons) node.children = [{}];
});
}
} catch (err) {
console.error('Error fetching department leaves');
throw new Error();
}
};
const removeNode = (node) => {
quasar
.dialog({
title: 'Are you sure you want to delete it?',
message: 'Delete department',
ok: {
push: true,
color: 'primary',
},
cancel: true,
})
.onOk(async () => {
try {
await axios.post(`/Departments/${node.id}/removeChild`, node.id);
notify('department.departmentRemoved', 'positive');
await fetchNodeLeaves(node.parentFk);
} catch (err) {
console.log('Error removing department');
}
});
};
const showCreateNodeForm = (nodeId) => {
showCreateNodeFormVal.value = true;
creationNodeSelectedId.value = nodeId;
};
const onNodeCreated = async () => {
await fetchNodeLeaves(creationNodeSelectedId.value);
};
const redirectToDepartmentSummary = (id) => {
if (!id) return;
router.push({ name: 'DepartmentSummary', params: { id: id } });
};
</script>
<template>
<QCard class="full-width" style="max-width: 800px">
<QTree
ref="treeRef"
:nodes="nodes"
node-key="id"
label-key="name"
v-model:expanded="expanded"
@update:expanded="onNodeExpanded($event)"
>
<template #default-header="prop">
<div
class="row justify-between full-width q-pr-md cursor-pointer"
@click.stop="redirectToDepartmentSummary(prop.node.id)"
>
<span class="text-uppercase">
{{ prop.node.name }}
</span>
<div class="row justify-between" style="max-width: max-content">
<QIcon
v-if="prop.node.id"
name="delete"
color="primary"
size="sm"
class="q-pr-xs cursor-pointer"
@click.stop="removeNode(prop.node)"
>
<QTooltip>
{{ t('Remove') }}
</QTooltip>
</QIcon>
<QIcon
name="add"
color="primary"
size="sm"
class="cursor-pointer"
@click.stop="showCreateNodeForm(prop.node.id)"
>
<QTooltip>
{{ t('Create') }}
</QTooltip>
</QIcon>
</div>
</div>
</template>
</QTree>
<QDialog
v-model="showCreateNodeFormVal"
transition-show="scale"
transition-hide="scale"
>
<CreateDepartmentChild
:parent-id="creationNodeSelectedId"
@on-data-saved="onNodeCreated()"
/>
</QDialog>
</QCard>
</template>
<i18n>
es:
Departments: Departamentos
Remove: Quitar
Create: Crear
Are you sure you want to delete it?: ¿Seguro que quieres eliminarlo?
Delete department: Eliminar departamento
</i18n>

View File

@ -568,7 +568,7 @@ export default {
landed: 'Landed',
hour: 'Hour',
agency: 'Agency',
total: 'Total'
total: 'Total',
},
form: {
clientFk: 'Client',
@ -577,7 +577,7 @@ export default {
agencyModeFk: 'Agency',
},
list: {
newOrder: 'New Order'
newOrder: 'New Order',
},
summary: {
basket: 'Basket',
@ -603,8 +603,27 @@ export default {
description: 'Description',
quantity: 'Quantity',
price: 'Price',
amount: 'Amount'
}
amount: 'Amount',
},
},
department: {
pageTitles: {
basicData: 'Basic data',
department: 'Department',
summary: 'Summary',
},
name: 'Name',
code: 'Code',
chat: 'Chat',
bossDepartment: 'Boss Department',
email: 'Email',
selfConsumptionCustomer: 'Self-consumption customer',
telework: 'Telework',
notifyOnErrors: 'Notify on errors',
worksInProduction: 'Works in production',
hasToRefill: 'Fill in days without physical check-ins',
hasToSendMail: 'Send check-ins by email',
departmentRemoved: 'Department removed',
},
worker: {
pageTitles: {
@ -614,6 +633,7 @@ export default {
summary: 'Summary',
notifications: 'Notifications',
workerCreate: 'New worker',
department: 'Department',
},
list: {
name: 'Name',

View File

@ -477,7 +477,7 @@ export default {
landed: 'F. entrega',
hour: 'Hora',
agency: 'Agencia',
total: 'Total'
total: 'Total',
},
form: {
clientFk: 'Cliente',
@ -486,7 +486,7 @@ export default {
agencyModeFk: 'Agencia',
},
list: {
newOrder: 'Nuevo Pedido'
newOrder: 'Nuevo Pedido',
},
summary: {
basket: 'Cesta',
@ -512,8 +512,8 @@ export default {
description: 'Descripción',
quantity: 'Cantidad',
price: 'Precio',
amount: 'Monto'
}
amount: 'Monto',
},
},
shelving: {
pageTitles: {
@ -606,6 +606,25 @@ export default {
country: 'País',
},
},
department: {
pageTitles: {
basicData: 'Basic data',
department: 'Departamentos',
summary: 'Resumen',
},
name: 'Nombre',
code: 'Código',
chat: 'Chat',
bossDepartment: 'Jefe de departamento',
email: 'Email',
selfConsumptionCustomer: 'Cliente autoconsumo',
telework: 'Teletrabaja',
notifyOnErrors: 'Notificar errores',
worksInProduction: 'Pertenece a producción',
hasToRefill: 'Completar días sin registros físicos',
hasToSendMail: 'Enviar fichadas por mail',
departmentRemoved: 'Departamento eliminado',
},
worker: {
pageTitles: {
workers: 'Trabajadores',
@ -614,6 +633,7 @@ export default {
summary: 'Resumen',
notifications: 'Notificaciones',
workerCreate: 'Nuevo trabajador',
department: 'Departamentos',
},
list: {
name: 'Nombre',

View File

@ -0,0 +1,135 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
const route = useRoute();
const { t } = useI18n();
const workersOptions = ref([]);
const clientsOptions = ref([]);
</script>
<template>
<fetch-data
url="Workers/search"
@on-fetch="(data) => (workersOptions = data)"
auto-load
/>
<fetch-data url="Clients" @on-fetch="(data) => (clientsOptions = data)" auto-load />
<FormModel
:url="`Departments/${route.params.id}`"
model="department"
auto-load
class="full-width"
>
<template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
:label="t('department.name')"
v-model="data.name"
:rules="validate('department.name')"
clearable
autofocus
/>
</div>
<div class="col">
<VnInput
v-model="data.code"
:label="t('department.code')"
:rules="validate('department.code')"
clearable
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
:label="t('department.chat')"
v-model="data.chatName"
:rules="validate('department.chat')"
clearable
/>
</div>
<div class="col">
<VnInput
v-model="data.notificationEmail"
:label="t('department.email')"
:rules="validate('department.email')"
clearable
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('department.bossDepartment')"
v-model="data.workerFk"
:options="workersOptions"
option-value="id"
option-label="name"
hide-selected
map-options
:rules="validate('department.workerFk')"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('department.selfConsumptionCustomer')"
v-model="data.clientFk"
:options="clientsOptions"
option-value="id"
option-label="name"
hide-selected
map-options
:rules="validate('department.clientFk')"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QCheckbox
:label="t('department.telework')"
v-model="data.isTeleworking"
/>
</div>
<div class="col">
<QCheckbox
:label="t('department.notifyOnErrors')"
v-model="data.hasToMistake"
:false-value="0"
:true-value="1"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QCheckbox
:label="t('department.worksInProduction')"
v-model="data.isProduction"
/>
</div>
<div class="col">
<QCheckbox
:label="t('department.hasToRefill')"
v-model="data.hasToRefill"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QCheckbox
:label="t('department.hasToSendMail')"
v-model="data.hasToSendMail"
/>
</div>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -0,0 +1,28 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import DepartmentDescriptor from 'pages/Department/Card/DepartmentDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<DepartmentDescriptor />
<QSeparator />
<LeftMenu source="card" />
</QScrollArea>
</QDrawer>
<QPageContainer>
<QPage>
<QToolbar class="bg-vn-dark justify-end">
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<div class="q-pa-md column items-center">
<RouterView></RouterView>
</div>
</QPage>
</QPageContainer>
</template>

View File

@ -0,0 +1,129 @@
<script setup>
import { computed, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import VnLv from 'src/components/ui/VnLv.vue';
import CardDescriptor from 'src/components/ui/CardDescriptor.vue';
import useCardDescription from 'src/composables/useCardDescription';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
summary: {
type: Object,
default: null,
},
});
const quasar = useQuasar();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const { notify } = useNotify();
const entityId = computed(() => {
return $props.id || route.params.id;
});
const department = ref();
const data = ref(useCardDescription());
const setData = (entity) => {
if (!entity) return;
data.value = useCardDescription(entity.name, entity.id);
};
const removeDepartment = () => {
console.log('entityId: ', entityId.value);
quasar
.dialog({
title: 'Are you sure you want to delete it?',
message: 'Delete department',
ok: {
push: true,
color: 'primary',
},
cancel: true,
})
.onOk(async () => {
try {
await axios.post(
`/Departments/${entityId.value}/removeChild`,
entityId.value
);
router.push({ name: 'WorkerDepartment' });
notify('department.departmentRemoved', 'positive');
} catch (err) {
console.log('Error removing department');
}
});
};
</script>
<template>
<CardDescriptor
module="Department"
data-key="departmentData"
:url="`Departments/${entityId}`"
:title="data.title"
:subtitle="data.subtitle"
:summary="$props.summary"
@on-fetch="
(data) => {
department = data;
setData(data);
}
"
>
<template #menu="{}">
<QItem v-ripple clickable @click="removeDepartment()">
<QItemSection>{{ t('Delete') }}</QItemSection>
</QItem>
</template>
<template #body="{ entity }">
<VnLv :label="t('department.chat')" :value="entity.chatName" dash />
<VnLv :label="t('department.email')" :value="entity.notificationEmail" dash />
<VnLv
:label="t('department.selfConsumptionCustomer')"
:value="entity.client?.name"
dash
/>
<VnLv
:label="t('department.bossDepartment')"
:value="entity.worker?.user?.name"
dash
/>
</template>
<template #actions>
<QCardActions>
<QBtn
size="md"
icon="vn:worker"
color="primary"
:to="{
name: 'WorkerList',
query: {
params: JSON.stringify({ departmentFk: entityId }),
},
}"
>
<QTooltip>{{ t('Department workers') }}</QTooltip>
</QBtn>
</QCardActions>
</template>
</CardDescriptor>
</template>
<i18n>
es:
Department workers: Trabajadores del departamento
</i18n>

View File

@ -0,0 +1,107 @@
<script setup>
import { ref, onMounted, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardSummary from 'components/ui/CardSummary.vue';
import { getUrl } from 'src/composables/getUrl';
import VnLv from 'src/components/ui/VnLv.vue';
const route = useRoute();
const { t } = useI18n();
const $props = defineProps({
id: {
type: Number,
default: 0,
},
});
const entityId = computed(() => $props.id || route.params.id);
const departmentUrl = ref();
onMounted(async () => {
departmentUrl.value = (await getUrl('')) + `departments/${entityId.value}/`;
});
</script>
<template>
<CardSummary
ref="summary"
:url="`Departments/${entityId}`"
class="full-width"
style="max-width: 900px"
>
<template #header="{ entity }">
<div>{{ entity.name }}</div>
</template>
<template #body="{ entity: department }">
<QCard class="column">
<a class="header" :href="department + `basic-data`">
{{ t('Basic data') }}
<QIcon name="open_in_new" color="primary" />
</a>
<div class="full-width row wrap justify-between content-between">
<div class="column" style="min-width: 50%">
<VnLv
:label="t('department.name')"
:value="department.name"
dash
/>
<VnLv
:label="t('department.code')"
:value="department.code"
dash
/>
<VnLv
:label="t('department.chat')"
:value="department.chatName"
dash
/>
<VnLv
:label="t('department.bossDepartment')"
:value="department.worker?.user?.name"
dash
/>
<VnLv
:label="t('department.email')"
:value="department.notificationEmail"
dash
/>
<VnLv
:label="t('department.selfConsumptionCustomer')"
:value="department.client?.name"
dash
/>
</div>
<div class="column" style="min-width: 50%">
<VnLv
:label="t('department.telework')"
:value="department.isTeleworking"
dash
/>
<VnLv
:label="t('department.notifyOnErrors')"
:value="Boolean(department.hasToMistake)"
dash
/>
<VnLv
:label="t('department.worksInProduction')"
:value="department.isProduction"
dash
/>
<VnLv
:label="t('department.hasToRefill')"
:value="department.hasToRefill"
dash
/>
<VnLv
:label="t('department.hasToSendMail')"
:value="department.hasToSendMail"
dash
/>
</div>
</div>
</QCard>
</template>
</CardSummary>
</template>

View File

@ -0,0 +1,15 @@
<script setup>
import VnTree from 'components/ui/VnTree.vue';
</script>
<template>
<QPage class="column items-center q-pa-md">
<VnTree />
</QPage>
</template>
<i18n>
es:
Search worker: Buscar trabajador
You can search by worker id or name: Puedes buscar por id o nombre del trabajador
</i18n>

View File

@ -0,0 +1,46 @@
import { RouterView } from 'vue-router';
export default {
path: '/department',
name: 'Department',
meta: {
title: 'department',
icon: 'vn:greuge',
},
component: RouterView,
redirect: { name: 'DepartmentCard' },
menus: {
main: [],
card: ['DepartmentBasicData'],
},
children: [
{
name: 'DepartmentCard',
path: 'department/:id',
component: () => import('src/pages/Department/Card/DepartmentCard.vue'),
redirect: { name: 'DepartmentSummary' },
children: [
{
name: 'DepartmentSummary',
path: 'summary',
meta: {
title: 'summary',
icon: 'launch',
},
component: () =>
import('src/pages/Department/Card/DepartmentSummary.vue'),
},
{
name: 'DepartmentBasicData',
path: 'basic-data',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () =>
import('src/pages/Department/Card/DepartmentBasicData.vue'),
},
],
},
],
};

View File

@ -10,6 +10,7 @@ import Route from './route';
import Supplier from './Supplier';
import Travel from './travel';
import Order from './order';
import Department from './department';
export default [
Customer,
@ -24,4 +25,5 @@ export default [
Travel,
Order,
invoiceIn,
Department,
];

View File

@ -10,8 +10,9 @@ export default {
component: RouterView,
redirect: { name: 'WorkerMain' },
menus: {
main: ['WorkerList'],
main: ['WorkerList', 'WorkerDepartment'],
card: ['WorkerNotificationsManager'],
departmentCard: ['BasicData'],
},
children: [
{
@ -29,6 +30,15 @@ export default {
},
component: () => import('src/pages/Worker/WorkerList.vue'),
},
{
path: 'department',
name: 'WorkerDepartment',
meta: {
title: 'department',
icon: 'vn:greuge',
},
component: () => import('src/pages/Worker/WorkerDepartment.vue'),
},
{
path: 'create',
name: 'WorkerCreate',

View File

@ -8,8 +8,9 @@ import wagon from './modules/wagon';
import supplier from './modules/Supplier';
import route from './modules/route';
import travel from './modules/travel';
import department from './modules/department';
import shelving from 'src/router/modules/shelving';
import order from "src/router/modules/order";
import order from 'src/router/modules/order';
const routes = [
{
@ -61,6 +62,7 @@ const routes = [
route,
supplier,
travel,
department,
{
path: '/:catchAll(.*)*',
name: 'NotFound',