@agendize/vue-agendize
Advanced tools
Comparing version 1.17.0 to 1.18.0
# Changelog | ||
## [1.18.0] - 11-12-2023 | ||
### Ajout | ||
- Ajout de la gestion des status custom | ||
- Ajout de messages d'erreurs, spécifiques aux borne min et max, sur la durée de création et la capacité d'un événement, ainsi que sur la durée d'un service et son délai d'annulation | ||
- Revision system de droit et possibilité de se loggué sans droit scheduling | ||
### Modification | ||
- Ajout des traductions explicite LastName pour la langue EN | ||
## [1.17.0] - 28-11-2023 | ||
@@ -4,0 +12,0 @@ ### Ajout |
{ | ||
"name": "@agendize/vue-agendize", | ||
"private": false, | ||
"version": "1.17.0", | ||
"version": "1.18.0", | ||
"description": "Vue agendize application", | ||
@@ -6,0 +6,0 @@ "keywords": [ |
@@ -5,14 +5,16 @@ import {appInitialState, AppState, LoadedState, LoadingState, OrganisationSelectionState} from "./AppState"; | ||
AccountEntity, | ||
AclEntity, | ||
ApiErrors, | ||
BrowserStorage, | ||
CalendarApi, | ||
CompanyEntity, | ||
UserEntity, | ||
BrowserStorage, | ||
StorageKeys, AclEntity | ||
StorageKeys, | ||
UserEntity | ||
} from "@agendize/js-calendar-api"; | ||
import {Locale,initTranslationTool} from "@agendize/az-i18n"; | ||
import {initTranslationTool, Locale} from "@agendize/az-i18n"; | ||
import {identifyUser as identifyUserForMatomo} from "./services/matomo"; | ||
import {getUserLocale} from "./utils/accountUtils"; | ||
import {goToAuthorizedRoute} from "./router"; | ||
import {RouteLocationNormalizedLoaded} from "vue-router"; | ||
import {getAuthorizedRouteName, ROUTE_NAMES} from "./router"; | ||
import {Router} from "vue-router"; | ||
import {Acl, useAcl} from "@agendize/vue-acl"; | ||
@@ -23,3 +25,2 @@ const MATOMO_HOST = 'https://analytics.rdv.az/js' | ||
private api: CalendarApi | ||
private acl: any | ||
private fontAwesomeKitId: string | ||
@@ -29,10 +30,11 @@ private weglotApiKey: string | ||
private stonlyWid: string | ||
private account?: AccountEntity | ||
private route: RouteLocationNormalizedLoaded | ||
private router: Router | ||
constructor(api: CalendarApi, acl: any, fontAwesomeKitId: string, weglotApiKey: string, matomoCode: string, stonlyWid: string, route: RouteLocationNormalizedLoaded) { | ||
private acl: Acl = useAcl() | ||
private accountStore = useAccountStore() | ||
constructor(api: CalendarApi, fontAwesomeKitId: string, weglotApiKey: string, matomoCode: string, stonlyWid: string, router: Router) { | ||
super(appInitialState); | ||
this.api = api | ||
this.acl = acl | ||
this.fontAwesomeKitId = fontAwesomeKitId | ||
@@ -42,3 +44,3 @@ this.weglotApiKey = weglotApiKey | ||
this.stonlyWid = stonlyWid | ||
this.route = route | ||
this.router = router | ||
} | ||
@@ -74,8 +76,7 @@ | ||
load() { | ||
const accountStore = useAccountStore() | ||
accountStore.fetchAccount(true, (account: AccountEntity) => { | ||
this.account = account | ||
this.accountStore.fetchAccount(true, (account: AccountEntity) => { | ||
this.initStorage(account) | ||
const organisationSession = sessionStorage.getItem("organisation") | ||
const browserStorage = new BrowserStorage() | ||
const organisationSession = browserStorage.getItem(StorageKeys.ORGANISATION) | ||
if (organisationSession) { | ||
@@ -102,4 +103,3 @@ this.organisationSelected(JSON.parse(organisationSession)) | ||
switchOrganisation(backToPreviousAccount: boolean) { | ||
const accountStore = useAccountStore() | ||
this.changeState(new OrganisationSelectionState(accountStore.account.acls?.filter(acl => acl.active) ?? [], backToPreviousAccount)) | ||
this.changeState(new OrganisationSelectionState(this.accountStore.account.acls?.filter((acl: AclEntity) => acl.active) ?? [], backToPreviousAccount)) | ||
} | ||
@@ -112,3 +112,26 @@ | ||
organisationSelected(organisation: AccountEntity) { | ||
this._fetchAllUsers(organisation) | ||
const acl = useAcl() | ||
const organisationAcl: AclEntity | undefined = this.accountStore.account?.acls?.find((acl: AclEntity) => acl.organisationId === organisation.id) as AclEntity | ||
this.api.refreshOrganisationStorage(organisation) | ||
if (organisationAcl) { | ||
acl.init(organisationAcl).then(() => { | ||
const currentRoute = this.router.currentRoute.value | ||
const landingPage = getAuthorizedRouteName(currentRoute) | ||
if (landingPage) { | ||
if (landingPage === ROUTE_NAMES.NOT_FOUND) { | ||
this.changeState(new LoadedState(organisation, undefined)) | ||
} else if (landingPage === ROUTE_NAMES.CALENDAR || landingPage === 'calendar') { | ||
this._initScheduling(organisation) | ||
} else { | ||
this.router.push({name: landingPage}).then(() => { | ||
this.changeState(new LoadedState(organisation, undefined)) | ||
}) | ||
} | ||
} else { | ||
this.loginError(ApiErrors.INSUFFICIENT_RIGHT) | ||
} | ||
}) | ||
} else { | ||
this.loginError(ApiErrors.INSUFFICIENT_RIGHT) | ||
} | ||
} | ||
@@ -130,4 +153,2 @@ | ||
private selectOrganisation(aclAccounts?: AclEntity[]) { | ||
if (!aclAccounts || aclAccounts.length === 0) { | ||
@@ -137,5 +158,5 @@ // Ne devrais pas arriver car l'authentification est en succés uniquement si au moins un compte est actif | ||
} else if (aclAccounts.length === 1) { | ||
this.api.getAccount(aclAccounts[0].account).then(organisationAccount => { | ||
if (organisationAccount && organisationAccount.result) { | ||
this._fetchAllUsers(organisationAccount.result) | ||
this.api.getAccount(aclAccounts[0].organisation).then(response => { | ||
if (response && response.result) { | ||
this.organisationSelected(response.result) | ||
} | ||
@@ -211,17 +232,20 @@ }) | ||
private _fetchAllUsers(organisation: AccountEntity) { | ||
private _initScheduling(organisation: AccountEntity) { | ||
//TODO voir si c'est encore pertinent de faire un fetch all pour n'avoir que l'utilisateur connecté | ||
//Le volume est réduit avec le getDefault plutôt que getAll | ||
const accountStore = useAccountStore() | ||
this.api.getUserById(organisation.email!, accountStore.account.id).then((response: { result: UserEntity | undefined, local: boolean }) => { | ||
this.api.getUserById(organisation.email!, this.accountStore.account.id).then((response: { result: UserEntity | undefined, local: boolean }) => { | ||
if (response) { | ||
let meInOrganisation = response.result | ||
if (meInOrganisation) { | ||
//Pour le moment ça marche plus trop pour remplir StorageKeys.AGENDA_USERS par ce biais | ||
this.api.refreshUserSession(organisation, undefined, meInOrganisation) | ||
this.api.refreshUserStorage(undefined, meInOrganisation) | ||
identifyUserForMatomo(meInOrganisation.login!) | ||
identifyUserForMatomo(meInOrganisation) | ||
this._fetchAllCompanies(organisation, meInOrganisation) | ||
this.api.getAllCompanies(organisation.email).then((companiesResponse: { results: CompanyEntity[] | undefined }) => { | ||
this.router.push({name: ROUTE_NAMES.CALENDAR}).then(() => { | ||
this.changeState(new LoadedState(organisation, meInOrganisation)) | ||
}) | ||
}).catch(() => { | ||
this.loginError(ApiErrors.INTERNAL_ERROR) | ||
}) | ||
} else { | ||
@@ -237,29 +261,2 @@ this.loginError(ApiErrors.INTERNAL_ERROR) | ||
} | ||
private _fetchAllCompanies(organisation: AccountEntity, meInOrganisation: UserEntity) { | ||
this.api.getAllCompanies(organisation.email).then((companiesResponse: { results: CompanyEntity[] | undefined }) => { | ||
this._initAcl(organisation, meInOrganisation, companiesResponse.results) | ||
}).catch(() => { | ||
this.loginError(ApiErrors.INTERNAL_ERROR) | ||
}) | ||
} | ||
private _initAcl(organisation: AccountEntity, meInOrganisation: UserEntity, companies?: CompanyEntity[]) { | ||
const initialAbilityAccount = new Map<{ objectType: any, objectId: string }, string>(); | ||
if (companies) { | ||
companies.forEach(company => { | ||
initialAbilityAccount.set({ | ||
objectType: 'company', | ||
objectId: company.id! | ||
}, company.owner?.userName) | ||
}) | ||
} | ||
const accountStore = useAccountStore() | ||
this.acl.init(this.api, accountStore.account?.acls?.find((acl: any) => acl.account === organisation.email), meInOrganisation).then(() => { | ||
goToAuthorizedRoute(accountStore, this.route).then(() => { | ||
this.changeState(new LoadedState(organisation, meInOrganisation)) | ||
}) | ||
}) | ||
} | ||
} |
@@ -63,6 +63,6 @@ import {ConsoleLogger, LoggerLevel} from '@agendize/js-calendar-api' | ||
const acl = createAcl({logger: logger, router: appRouter, onDeniedRoute: '/404'}); | ||
const acl = createAcl({api, logger}) | ||
app.use(acl) | ||
const store = createStore(api, useAcl()) | ||
const store = createStore(api) | ||
app.use(store) | ||
@@ -69,0 +69,0 @@ |
import { | ||
createRouter, | ||
createWebHistory, NavigationFailure, RouteLocationNamedRaw, RouteLocationNormalized, | ||
createWebHistory, NavigationFailure, RouteLocationMatched, RouteLocationNamedRaw, RouteLocationNormalized, | ||
RouteLocationNormalizedLoaded, RouteLocationPathRaw, RouteLocationRaw, Router, RouteRecordName, | ||
@@ -17,2 +17,4 @@ RouteRecordRaw, | ||
import {getEnvConfig} from "../utils/config"; | ||
import {useAccountStore} from "@agendize/vue-tools"; | ||
import {AclAbility, AclRole} from "@agendize/vue-acl"; | ||
@@ -23,2 +25,3 @@ const i18n = locales('fr') | ||
LOGIN = 'login', | ||
DASHBOARD = 'agendize-dashboard', | ||
CALENDAR = 'agendize-calendar', | ||
@@ -38,2 +41,3 @@ FORMS = 'agendize-forms', | ||
export const enum ROUTE_PATH { | ||
DASHBOARD = '/dashboard', | ||
CALENDAR = '/calendar', | ||
@@ -63,2 +67,3 @@ FORMS = '/forms', | ||
title: i18n.global.t('agendize.router-meta.calendar.title'), | ||
canAny: ['readAppointment', 'readStaffAppointment'], | ||
icon: { | ||
@@ -80,2 +85,25 @@ name: 'far fa-calendar' | ||
{ | ||
path: ROUTE_PATH.DASHBOARD, | ||
name: ROUTE_NAMES.DASHBOARD, | ||
component: () => import('../presentation/view/dashboard/View.vue'), | ||
meta: { | ||
title: i18n.global.t('agendize.router-meta.dashboard.title'), | ||
canNone: ['readAppointment', 'readStaffAppointment'], | ||
canCount: { abilities: ['readForm', 'readQueue', 'readClient', 'readReport'], count: 2 }, | ||
icon: { | ||
name: 'far fa-objects-column' | ||
}, | ||
description: i18n.global.t('agendize.router-meta.dashboard.description'), | ||
requiresAuth: true, | ||
leftSidebar: true, | ||
topBar: true, | ||
alert: false, | ||
primary: true, | ||
displayMobile: true, | ||
burgerAriaLabel: i18n.global.t('agendize.router-meta.dashboard.menuDescription'), | ||
burgerPopoverTitle: i18n.global.t('agendize.router-meta.dashboard.menu'), | ||
burgerPopoverDescription: i18n.global.t('agendize.router-meta.dashboard.menuDescription'), | ||
} | ||
}, | ||
{ | ||
path: ROUTE_PATH.WORKING_PLANNING, | ||
@@ -323,3 +351,4 @@ name: ROUTE_NAMES.WORKING_PLANNING, | ||
await api.getAccessToken(to.query.code.toString()) | ||
next({name: ROUTE_NAMES.CALENDAR}) | ||
const routeName = getLandingPageRouteName() | ||
next({name: routeName}) | ||
} catch (e: any) { | ||
@@ -343,4 +372,14 @@ let errorCode | ||
} | ||
setTitle(to.meta?.title as (string | undefined)) | ||
next() | ||
//@ts-ignore | ||
const isItABackButton = window.popStateDetected | ||
//@ts-ignore | ||
window.popStateDetected = false | ||
if (isItABackButton && !isRouteAuthorized(to)) { | ||
next({ name: ROUTE_NAMES.NOT_FOUND}) | ||
} else { | ||
setTitle(to.meta?.title as (string | undefined)) | ||
next() | ||
} | ||
}) | ||
@@ -456,4 +495,5 @@ | ||
export async function getIconifiedAuthorizedRoutes(accountStore: any, mobile: boolean) { | ||
const abilityFunc = async (route: RouteRecordRaw) => { | ||
export function getIconifiedAuthorizedRoutes(accountStore: any, mobile: boolean) { | ||
const routeIsAccessible = (route: RouteRecordRaw) => { | ||
// Check can ability | ||
let abilityToCheck = route.meta?.can | ||
@@ -463,11 +503,34 @@ if (abilityToCheck === undefined) { | ||
} | ||
if (abilityToCheck !== undefined && !accountStore.hasAbility(abilityToCheck)) { | ||
return false | ||
} | ||
let can = (abilityToCheck === undefined || await accountStore.hasAbility(abilityToCheck) === true) | ||
if (can) { | ||
let roleToCheck = route.meta?.role ?? route.meta?.roleAny | ||
if (roleToCheck !== undefined) { | ||
can = await accountStore.hasAnyRoles(roleToCheck) | ||
// Check canAny ability | ||
abilityToCheck = route.meta?.canAny | ||
if (abilityToCheck !== undefined && !accountStore.hasAnyAbility(abilityToCheck)) { | ||
return false | ||
} | ||
// Check canNone ability | ||
abilityToCheck = route.meta?.canNone | ||
if (abilityToCheck !== undefined && accountStore.hasAnyAbility(abilityToCheck)) { | ||
return false | ||
} | ||
// Check canCount ability | ||
const routeAbilities: { abilities: AclAbility[], count: number } | undefined = route.meta?.canCount as { abilities: AclAbility[], count: number } | ||
const abilitiesToCheck: AclAbility[] = routeAbilities?.abilities | ||
const count = routeAbilities?.count | ||
if (abilityToCheck !== undefined && count != undefined && abilitiesToCheck.filter(ability => accountStore.hasAbility(ability)).length < count) { | ||
return false | ||
} | ||
// Check role | ||
let roleToCheck = route.meta?.role ?? route.meta?.roleAny | ||
if (roleToCheck !== undefined) { | ||
if (!accountStore.hasAnyRoles(roleToCheck)) { | ||
return false | ||
} | ||
} | ||
return can | ||
return true | ||
} | ||
@@ -480,4 +543,3 @@ | ||
const filterMap = await Promise.all(allAccessibleRoutes?.map(abilityFunc)??[]); | ||
return allAccessibleRoutes?.filter((route, index) => filterMap[index])??[] | ||
return allAccessibleRoutes?.filter(route => routeIsAccessible(route)) ?? [] | ||
} | ||
@@ -499,34 +561,84 @@ | ||
// TODO lorsque le temps le permettra faire des router.push plutot que des redirects (useRouter() ne fonctionne pas pour le moment) | ||
export async function goToAuthorizedRoute(accountStore: any, currentRoute: RouteLocationNormalizedLoaded) { | ||
if (currentRoute.meta?.can || currentRoute.meta?.roleAny) { | ||
if (currentRoute.name === undefined) { | ||
window.location.assign('/') | ||
return Promise.reject() | ||
function isRouteAuthorized(route: RouteLocationNormalized) { | ||
const accountStore = useAccountStore() | ||
let meta = route.meta | ||
let name = route.name | ||
if (meta?.can || meta?.roleAny || meta?.canAny) { | ||
if (name === undefined) { | ||
return false | ||
} else if (meta?.can instanceof Function) { | ||
return meta.can(route) | ||
} else { | ||
const hasAbility = currentRoute.meta?.can ? await accountStore.hasAbility(currentRoute.meta?.can) : true | ||
const hasRole = currentRoute.meta?.roleAny ? await accountStore.hasAnyRoles(currentRoute.meta?.roleAny) : true | ||
if (!hasAbility || !hasRole) { | ||
const to = notAuthorizedRouteRedirections.filter(r => r.from === currentRoute.name)[0] | ||
if (to) { | ||
window.location.assign(to.to) | ||
return Promise.reject() | ||
} else { | ||
window.location.assign('/') | ||
return Promise.reject() | ||
} | ||
const hasAbility = meta?.can ? accountStore.hasAbility(meta?.can as AclAbility) : true | ||
const hasRole = meta?.roleAny ? accountStore.hasAnyRoles(meta?.roleAny as AclRole[]) : true | ||
const hasAnyAbility = meta?.canAny ? accountStore.hasAnyAbility(meta?.canAny as AclAbility[]) : true | ||
if (!hasAbility || !hasRole || !hasAnyAbility) { | ||
return false | ||
} else { | ||
const to = baseRedirection.filter(r => r.from === currentRoute.name)[0] | ||
if (to) { | ||
window.location.assign(to.to) | ||
return Promise.reject() | ||
} else { | ||
return Promise.resolve() | ||
} | ||
return true | ||
} | ||
} | ||
} else { | ||
if (name === undefined) { | ||
return false | ||
} else { | ||
return true | ||
} | ||
} | ||
return Promise.resolve() | ||
} | ||
export function getAuthorizedRouteName(currentRoute: RouteLocationNormalizedLoaded) { | ||
const landingPage = getLandingPageRouteName() | ||
if ((currentRoute.name === ROUTE_NAMES.DASHBOARD || currentRoute.matched[0]?.name === ROUTE_NAMES.DASHBOARD) && landingPage !== ROUTE_NAMES.DASHBOARD) { | ||
return landingPage | ||
} | ||
if (isRouteAuthorized(currentRoute)) { | ||
return currentRoute.name | ||
} else { | ||
return landingPage | ||
} | ||
} | ||
export function getLandingPageRouteName() { | ||
const accountStore = useAccountStore() | ||
const hasScheduling = accountStore.hasAnyAbility(['readAppointment', 'readStaffAppointment']) | ||
if (hasScheduling) { | ||
return ROUTE_NAMES.CALENDAR | ||
} | ||
const hasForm = accountStore.hasAbility('readForm') | ||
const hasQueue = accountStore.hasAbility('readQueue') | ||
const hasCrm = accountStore.hasAbility('readClient') | ||
const hasReport = accountStore.hasAbility('readReport') | ||
const abilityWithoutSchedulingCount = [hasForm, hasQueue, hasCrm, hasReport].filter(ability => ability === true).length | ||
if (abilityWithoutSchedulingCount === 0) { | ||
return undefined | ||
} else if (abilityWithoutSchedulingCount > 1) { | ||
return ROUTE_NAMES.DASHBOARD | ||
} else { | ||
if (hasForm) { | ||
return ROUTE_NAMES.FORMS | ||
} else if (hasQueue) { | ||
return ROUTE_NAMES.QUEUES | ||
} else if (hasReport) { | ||
return ROUTE_NAMES.REPORT | ||
} else if (hasCrm) { | ||
return ROUTE_NAMES.CRM | ||
} | ||
} | ||
return ROUTE_NAMES.DASHBOARD | ||
} | ||
//@ts-ignore | ||
window.popStateDetected = false | ||
window.addEventListener('popstate', () => { | ||
//@ts-ignore | ||
window.popStateDetected = true | ||
}) | ||
export default router |
@@ -38,6 +38,6 @@ import {UserEntity} from "@agendize/js-calendar-api"; | ||
function identifyUser(user: UserEntity) { | ||
window.Piwik?.getTracker().setUserId(user.login) | ||
function identifyUser(userEmail: string) { | ||
window.Piwik?.getTracker().setUserId(userEmail) | ||
} | ||
export {buildMatomoConfig, identifyUser} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
226461
75
2046