diff --git a/src/pages/Account/AccountAliasList.vue b/src/pages/Account/AccountAliasList.vue new file mode 100644 index 000000000..29e8d4d78 --- /dev/null +++ b/src/pages/Account/AccountAliasList.vue @@ -0,0 +1,105 @@ +<script setup> +import { useRouter } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import { ref } from 'vue'; + +import VnPaginate from 'components/ui/VnPaginate.vue'; +import VnSearchbar from 'components/ui/VnSearchbar.vue'; +import CardList from 'src/components/ui/CardList.vue'; +import VnLv from 'src/components/ui/VnLv.vue'; +import AliasSummary from './Alias/Card/AliasSummary.vue'; +import AliasCreateForm from './Alias/AliasCreateForm.vue'; + +import { useSummaryDialog } from 'src/composables/useSummaryDialog'; +import { useStateStore } from 'stores/useStateStore'; + +defineProps({ + id: { + type: Number, + default: 0, + }, +}); + +const { t } = useI18n(); +const { viewSummary } = useSummaryDialog(); +const router = useRouter(); +const stateStore = useStateStore(); +const aliasCreateDialogRef = ref(null); + +const exprBuilder = (param, value) => { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? { id: value } + : { alias: { like: `%${value}%` } }; + } +}; + +const navigate = (id) => router.push({ name: 'AliasSummary', params: { id } }); + +const openCreateModal = () => aliasCreateDialogRef.value.show(); +</script> + +<template> + <template v-if="stateStore.isHeaderMounted()"> + <Teleport to="#searchbar"> + <VnSearchbar + data-key="AccountAliasList" + url="MailAliases" + :expr-builder="exprBuilder" + :label="t('mailAlias.search')" + :info="t('mailAlias.searchInfo')" + /> + </Teleport> + </template> + <QPage class="flex justify-center q-pa-md"> + <div class="vn-card-list"> + <VnPaginate + ref="paginateRef" + data-key="AccountAliasList" + url="MailAliases" + :expr-builder="exprBuilder" + > + <template #body="{ rows }"> + <CardList + v-for="row of rows" + :id="row.id" + :key="row.id" + :title="row.alias" + @click="navigate(row.id)" + > + <template #list-items> + <VnLv :label="t('mailAlias.alias')" :value="row.alias"> + </VnLv> + <VnLv + :label="t('mailAlias.description')" + :value="row.description" + > + </VnLv> + </template> + <template #actions> + <QBtn + :label="t('components.smartCard.openSummary')" + @click.stop="viewSummary(row.id, AliasSummary)" + color="primary" + style="margin-top: 15px" + /> + </template> + </CardList> + </template> + </VnPaginate> + </div> + <QDialog + ref="aliasCreateDialogRef" + transition-show="scale" + transition-hide="scale" + > + <AliasCreateForm /> + </QDialog> + <QPageSticky position="bottom-right" :offset="[18, 18]"> + <QBtn fab icon="add" color="primary" @click="openCreateModal()"> + <QTooltip class="text-no-wrap">{{ t('mailAlias.newAlias') }}</QTooltip> + </QBtn> + </QPageSticky> + </QPage> +</template> diff --git a/src/pages/Account/Alias/AliasCreateForm.vue b/src/pages/Account/Alias/AliasCreateForm.vue new file mode 100644 index 000000000..d4d61a804 --- /dev/null +++ b/src/pages/Account/Alias/AliasCreateForm.vue @@ -0,0 +1,57 @@ +<script setup> +import { useRouter } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import FormModelPopup from 'components/FormModelPopup.vue'; +import VnRow from 'components/ui/VnRow.vue'; +import VnInput from 'src/components/common/VnInput.vue'; + +import { useArrayData } from 'src/composables/useArrayData'; + +const router = useRouter(); +const { t } = useI18n(); +const arrayData = useArrayData('AliasCreate'); +const { store } = arrayData; + +const defaultInitialData = { + alias: null, + description: null, +}; + +const onDataSaved = ({ id }) => { + router.push({ name: 'AliasBasicData', params: { id } }); + store.data = null; +}; +</script> + +<template> + <FormModelPopup + :title="t('Create alias')" + ref="formModelPopupRef" + url-create="MailAliases" + model="AliasCreate" + :form-initial-data="defaultInitialData" + @on-data-saved="onDataSaved" + > + <template #form-inputs="{ data }"> + <VnRow class="row q-gutter-md q-mb-md"> + <div class="col"> + <VnInput v-model="data.alias" :label="t('mailAlias.name')" /> + </div> + </VnRow> + <VnRow class="row q-gutter-md q-mb-md"> + <div class="col"> + <VnInput + v-model="data.description" + :label="t('mailAlias.description')" + /> + </div> + </VnRow> + </template> + </FormModelPopup> +</template> + +<i18n> + es: + Create alias: Crear alias +</i18n> diff --git a/src/pages/Account/Alias/Card/AliasBasicData.vue b/src/pages/Account/Alias/Card/AliasBasicData.vue new file mode 100644 index 000000000..035ba0e8b --- /dev/null +++ b/src/pages/Account/Alias/Card/AliasBasicData.vue @@ -0,0 +1,22 @@ +<script setup> +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import FormModel from 'components/FormModel.vue'; +import VnInput from 'src/components/common/VnInput.vue'; + +const route = useRoute(); +const { t } = useI18n(); +</script> + +<template> + <FormModel model="Alias"> + <template #form="{ data }"> + <div class="column q-gutter-y-md"> + <VnInput v-model="data.alias" :label="t('mailAlias.name')" /> + <VnInput v-model="data.description" :label="t('mailAlias.description')" /> + <QCheckbox :label="t('mailAlias.isPublic')" v-model="data.isPublic" /> + </div> + </template> + </FormModel> +</template> diff --git a/src/pages/Account/Alias/Card/AliasCard.vue b/src/pages/Account/Alias/Card/AliasCard.vue new file mode 100644 index 000000000..f5103cf03 --- /dev/null +++ b/src/pages/Account/Alias/Card/AliasCard.vue @@ -0,0 +1,33 @@ +<script setup> +import { useI18n } from 'vue-i18n'; +import { useRoute } from 'vue-router'; +import { computed } from 'vue'; + +import VnCard from 'components/common/VnCard.vue'; +import AliasDescriptor from './AliasDescriptor.vue'; + +const { t } = useI18n(); +const route = useRoute(); + +const routeName = computed(() => route.name); +const customRouteRedirectName = computed(() => { + return routeName.value; +}); +const searchBarDataKeys = { + AliasBasicData: 'AliasBasicData', + AliasUsers: 'AliasUsers', +}; +</script> + +<template> + <VnCard + data-key="Alias" + base-url="MailAliases" + :descriptor="AliasDescriptor" + :search-data-key="searchBarDataKeys[routeName]" + :search-custom-route-redirect="customRouteRedirectName" + :search-redirect="!!customRouteRedirectName" + :searchbar-label="t('mailAlias.search')" + :searchbar-info="t('mailAlias.searchInfo')" + /> +</template> diff --git a/src/pages/Account/Alias/Card/AliasDescriptor.vue b/src/pages/Account/Alias/Card/AliasDescriptor.vue new file mode 100644 index 000000000..963f84547 --- /dev/null +++ b/src/pages/Account/Alias/Card/AliasDescriptor.vue @@ -0,0 +1,88 @@ +<script setup> +import { ref, computed } from 'vue'; +import { useRoute, useRouter } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import { useQuasar } from 'quasar'; + +import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import VnLv from 'src/components/ui/VnLv.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, + }, +}); + +const { t } = useI18n(); +const route = useRoute(); +const quasar = useQuasar(); +const router = useRouter(); +const { notify } = useNotify(); + +const entityId = computed(() => { + return $props.id || route.params.id; +}); + +const data = ref(useCardDescription()); +const setData = (entity) => (data.value = useCardDescription(entity.alias, entity.id)); + +const removeAlias = () => { + quasar + .dialog({ + title: t('Alias will be removed'), + message: t('Are you sure you want to continue?'), + ok: { + push: true, + color: 'primary', + }, + cancel: true, + }) + .onOk(async () => { + try { + await axios.delete(`MailAliases/${entityId.value}`); + notify(t('Alias removed'), 'positive'); + router.push({ name: 'AccountAlias' }); + } catch (err) { + console.error('Error removing alias'); + } + }); +}; +</script> + +<template> + <CardDescriptor + ref="descriptor" + :url="`MailAliases/${entityId}`" + module="Alias" + @on-fetch="setData" + data-key="aliasData" + :title="data.title" + :subtitle="data.subtitle" + > + <template #menu> + <QItem v-ripple clickable @click="removeAlias()"> + <QItemSection>{{ t('Delete') }}</QItemSection> + </QItem> + </template> + <template #body="{ entity }"> + <VnLv :label="t('mailAlias.description')" :value="entity.description" /> + </template> + </CardDescriptor> +</template> + +<i18n> + en: + accountRate: Claming rate + es: + accountRate: Ratio de reclamación + Delete: Eliminar + Alias will be removed: El alias será eliminado + Are you sure you want to continue?: ¿Seguro que quieres continuar? + Alias removed: Alias eliminado +</i18n> diff --git a/src/pages/Account/Alias/Card/AliasSummary.vue b/src/pages/Account/Alias/Card/AliasSummary.vue new file mode 100644 index 000000000..cedae28b7 --- /dev/null +++ b/src/pages/Account/Alias/Card/AliasSummary.vue @@ -0,0 +1,49 @@ +<script setup> +import { ref, computed } from 'vue'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import CardSummary from 'components/ui/CardSummary.vue'; +import VnLv from 'src/components/ui/VnLv.vue'; + +import { useArrayData } from 'src/composables/useArrayData'; + +const route = useRoute(); +const { t } = useI18n(); + +const $props = defineProps({ + id: { + type: Number, + default: 0, + }, +}); + +const { store } = useArrayData('Alias'); +const alias = ref(store.data); +const entityId = computed(() => $props.id || route.params.id); +</script> + +<template> + <CardSummary + ref="summary" + :url="`MailAliases/${entityId}`" + @on-fetch="(data) => (alias = data)" + > + <template #header> {{ alias.id }} - {{ alias.alias }} </template> + <template #body> + <QCard class="vn-one"> + <QCardSection class="q-pa-none"> + <router-link + :to="{ name: 'AliasBasicData', params: { id: entityId } }" + class="header header-link" + > + {{ t('globals.summary.basicData') }} + <QIcon name="open_in_new" /> + </router-link> + </QCardSection> + <VnLv :label="t('mailAlias.id')" :value="alias.id" /> + <VnLv :label="t('mailAlias.description')" :value="alias.description" /> + </QCard> + </template> + </CardSummary> +</template> diff --git a/src/pages/Account/Alias/Card/AliasUsers.vue b/src/pages/Account/Alias/Card/AliasUsers.vue new file mode 100644 index 000000000..4a9c449e4 --- /dev/null +++ b/src/pages/Account/Alias/Card/AliasUsers.vue @@ -0,0 +1,122 @@ +<script setup> +import { useRoute } from 'vue-router'; +import { computed, ref, watch } from 'vue'; +import { useI18n } from 'vue-i18n'; + +import VnPaginate from 'components/ui/VnPaginate.vue'; + +import { useVnConfirm } from 'composables/useVnConfirm'; +import { useArrayData } from 'composables/useArrayData'; +import useNotify from 'src/composables/useNotify.js'; +import axios from 'axios'; + +const { t } = useI18n(); +const { notify } = useNotify(); +const route = useRoute(); +const { openConfirmationModal } = useVnConfirm(); + +const paginateRef = ref(null); + +const arrayData = useArrayData('AliasUsers'); +const { store } = arrayData; + +const data = computed(() => { + const dataCopy = JSON.parse(JSON.stringify(store.data)); + return dataCopy.sort((a, b) => a.user?.name.localeCompare(b.user?.name)); +}); + +const filter = { + include: { + relation: 'user', + scope: { + fields: ['id', 'name'], + }, + }, +}; + +const urlPath = computed(() => `MailAliases/${route.params.id}/accounts`); + +const columns = computed(() => [ + { + name: 'alias', + }, + { + name: 'action', + }, +]); + +const deleteAlias = async (row) => { + try { + await axios.delete(`${urlPath.value}/${row.id}`); + notify(t('User removed'), 'positive'); + fetchAliases(); + } catch (error) { + console.error(error); + } +}; + +watch( + () => route.params.id, + () => { + store.url = urlPath.value; + store.filter = filter; + fetchAliases(); + } +); + +const fetchAliases = () => paginateRef.value.fetch(); +</script> + +<template> + <QPage class="column items-center q-pa-md"> + <div class="full-width" style="max-width: 400px"> + <VnPaginate + ref="paginateRef" + data-key="AliasUsers" + :filter="filter" + :url="urlPath" + :limit="0" + auto-load + > + <template #body> + <QTable :rows="data" :columns="columns" hide-header> + <template #body="{ row }"> + <QTr> + <QTd> + <span>{{ row.user?.name }}</span> + </QTd> + <QTd style="width: 50px !important"> + <QIcon + name="delete" + size="sm" + class="cursor-pointer" + color="primary" + @click=" + openConfirmationModal( + t('User will be removed from alias'), + t('Are you sure you want to continue?'), + () => deleteAlias(row) + ) + " + > + <QTooltip> + {{ t('Delete') }} + </QTooltip> + </QIcon> + </QTd> + </QTr> + </template> + </QTable> + </template> + </VnPaginate> + </div> + </QPage> +</template> + +<i18n> +es: + User will be removed from alias: El usuario será borrado del alias + Are you sure you want to continue?: ¿Seguro que quieres continuar? + User removed: Usuario borrado + Delete: Eliminar +</i18n> diff --git a/src/pages/Account/Role/Card/RoleBasicData.vue b/src/pages/Account/Role/Card/RoleBasicData.vue index bf9b3307d..1f3b3b6da 100644 --- a/src/pages/Account/Role/Card/RoleBasicData.vue +++ b/src/pages/Account/Role/Card/RoleBasicData.vue @@ -23,6 +23,11 @@ const { t } = useI18n(); /> </div> </VnRow> + <VnRow class="row q-gutter-md q-mb-md"> + <div class="col"> + <QCheckbox :label="t('mailAlias.isPublic')" v-model="data.isPublic" /> + </div> + </VnRow> </template> </FormModel> </template> diff --git a/src/pages/Account/Role/Card/RoleDescriptor.vue b/src/pages/Account/Role/Card/RoleDescriptor.vue new file mode 100644 index 000000000..89712b0b9 --- /dev/null +++ b/src/pages/Account/Role/Card/RoleDescriptor.vue @@ -0,0 +1,92 @@ +<script setup> +import { ref, computed } from 'vue'; +import { useRoute, useRouter } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import VnLv from 'src/components/ui/VnLv.vue'; +import useCardDescription from 'src/composables/useCardDescription'; +import { useQuasar } from 'quasar'; + +import axios from 'axios'; +import useNotify from 'src/composables/useNotify.js'; +const $props = defineProps({ + id: { + type: Number, + required: false, + default: null, + }, +}); + +const route = useRoute(); + +const quasar = useQuasar(); +const router = useRouter(); + +const { notify } = useNotify(); +const { t } = useI18n(); +const entityId = computed(() => { + return $props.id || route.params.id; +}); +const data = ref(useCardDescription()); +const setData = (entity) => (data.value = useCardDescription(entity.name, entity.id)); +const filter = { + where: { id: entityId }, +}; +const removeRole = () => { + 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.error('Error removing department'); + } + }); +}; +</script> + +<template> + <CardDescriptor + ref="descriptor" + :url="`VnRoles`" + :filter="filter" + module="Role" + @on-fetch="setData" + data-key="accountData" + :title="data.title" + :subtitle="data.subtitle" + > + <template #menu> + <QItem v-ripple clickable @click="removeRole()"> + <QItemSection>{{ t('Delete') }}</QItemSection> + </QItem> + </template> + <template #body="{ entity }"> + <VnLv :label="t('role.card.description')" :value="entity.description" /> + </template> + </CardDescriptor> +</template> +<style scoped> +.q-item__label { + margin-top: 0; +} +</style> +<i18n> + en: + accountRate: Claming rate + es: + accountRate: Ratio de reclamación +</i18n> diff --git a/src/pages/Account/locale/en.yml b/src/pages/Account/locale/en.yml index c330b706e..babedae70 100644 --- a/src/pages/Account/locale/en.yml +++ b/src/pages/Account/locale/en.yml @@ -46,6 +46,18 @@ role: searchInfo: Search role by id or name name: Name description: Description + id: Id +mailAlias: + pageTitles: + aliasUsers: Users + search: Search mail alias + searchInfo: Search alias by id or name + alias: Alias + description: Description + id: Id + newAlias: New alias + name: Name + isPublic: Public ldap: enableSync: Enable synchronization server: Server diff --git a/src/pages/Account/locale/es.yml b/src/pages/Account/locale/es.yml index 7ba81b583..36125f361 100644 --- a/src/pages/Account/locale/es.yml +++ b/src/pages/Account/locale/es.yml @@ -57,6 +57,18 @@ role: searchInfo: Buscar rol por id o nombre name: Nombre description: Descripción + id: Id +mailAlias: + pageTitles: + aliasUsers: Usuarios + search: Buscar alias de correo + searchInfo: Buscar alias por id o nombre + alias: Alias + description: Descripción + id: Id + newAlias: Nuevo alias + name: Nombre + isPublic: Público ldap: enableSync: Habilitar sincronización server: Servidor diff --git a/src/pages/Zone/Card/ZoneCalendar.vue b/src/pages/Zone/Card/ZoneCalendar.vue new file mode 100644 index 000000000..e69de29bb diff --git a/src/router/modules/account.js b/src/router/modules/account.js index d8846ede1..bc95719ef 100644 --- a/src/router/modules/account.js +++ b/src/router/modules/account.js @@ -11,7 +11,7 @@ export default { component: RouterView, redirect: { name: 'AccountMain' }, menus: { - main: ['AccountRoles', 'AccountAcls'], + main: ['AccountList', 'AccountAliasList', 'AccountRoles', 'AccountAcls'], card: [], }, children: [ @@ -30,6 +30,15 @@ export default { }, component: () => import('src/pages/Account/AccountList.vue'), }, + { + path: 'alias-list', + name: 'AccountAliasList', + meta: { + title: 'alias', + icon: 'email', + }, + component: () => import('src/pages/Account/AccountAliasList.vue'), + }, { path: 'acls', name: 'AccountAcls', diff --git a/src/router/modules/index.js b/src/router/modules/index.js index 78d4d94cc..bf7e46b00 100644 --- a/src/router/modules/index.js +++ b/src/router/modules/index.js @@ -20,6 +20,8 @@ import ItemType from './itemType'; import Zone from './zone'; import Account from './account'; import Monitor from './monitor'; +import MailAlias from './mailAlias'; +import Role from './role'; export default [ Item, @@ -43,5 +45,7 @@ export default [ ItemType, Zone, Account, + MailAlias, Monitor, + Role, ]; diff --git a/src/router/modules/mailAlias.js b/src/router/modules/mailAlias.js new file mode 100644 index 000000000..8e0f8abdc --- /dev/null +++ b/src/router/modules/mailAlias.js @@ -0,0 +1,57 @@ +import { RouterView } from 'vue-router'; + +export default { + path: 'account/alias', + name: 'Alias', + meta: { + title: 'alias', + icon: 'email', + moduleName: 'Alias', + }, + component: RouterView, + redirect: { name: 'AccountAliasList' }, + menus: { + main: [], + card: ['AliasBasicData', 'AliasUsers'], + }, + children: [ + { + name: 'AliasCard', + path: ':id', + component: () => import('src/pages/Account/Alias/Card/AliasCard.vue'), + redirect: { name: 'AliasSummary' }, + children: [ + { + name: 'AliasSummary', + path: 'summary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => + import('src/pages/Account/Alias/Card/AliasSummary.vue'), + }, + { + name: 'AliasBasicData', + path: 'basic-data', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => + import('src/pages/Account/Alias/Card/AliasBasicData.vue'), + }, + { + name: 'AliasUsers', + path: 'users', + meta: { + title: 'aliasUsers', + icon: 'group', + }, + component: () => + import('src/pages/Account/Alias/Card/AliasUsers.vue'), + }, + ], + }, + ], +}; diff --git a/src/router/routes.js b/src/router/routes.js index 8ae50402b..359ce5317 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -20,6 +20,8 @@ import agency from 'src/router/modules/agency'; import zone from 'src/router/modules/zone'; import account from './modules/account'; import monitor from 'src/router/modules/monitor'; +import mailAlias from './modules/mailAlias'; +import role from './modules/role'; const routes = [ { @@ -81,6 +83,8 @@ const routes = [ ItemType, zone, account, + role, + mailAlias, { path: '/:catchAll(.*)*', name: 'NotFound',