DONE: layout letter with letterhead and footer, notification for newly...

This commit is contained in:
ISMAIL MASSERAN
2026-06-28 02:16:49 +00:00
parent 94ecbe5887
commit 034ecf947f
400 changed files with 29802 additions and 5446 deletions
+1
View File
@@ -6,5 +6,6 @@ export interface Menu {
route_name?: string
params?: Record<string, unknown>
badge?: number
permission?: string
sub_menu?: Menu[]
}
+38
View File
@@ -0,0 +1,38 @@
import type { AuthPermission, AuthRole, AuthUser } from '@/modules/auth/types/auth.types'
const DEVELOPER_ROLE = 'DEVELOPER'
export function getActiveRolePermissions(
user: AuthUser | null | undefined,
activeRole: AuthRole | null | undefined,
): AuthPermission[] {
if (!user?.roles?.length || !activeRole) {
return []
}
return user.roles.find((role) => role.id === activeRole.id)?.permissions ?? []
}
export function hasActiveRolePermission(
user: AuthUser | null | undefined,
activeRole: AuthRole | null | undefined,
permission: string,
): boolean {
if (activeRole?.name === DEVELOPER_ROLE) {
return true
}
return getActiveRolePermissions(user, activeRole).some(
(item) => item.name === permission,
)
}
export function hasAnyActiveRolePermission(
user: AuthUser | null | undefined,
activeRole: AuthRole | null | undefined,
permissions: string[],
): boolean {
return permissions.some((permission) =>
hasActiveRolePermission(user, activeRole, permission),
)
}
@@ -0,0 +1,75 @@
import type { Menu } from '@/core/types/menu'
export type MenuList = (string | Menu)[]
function isMenuVisible(menu: Menu, hasPermission: (permission: string) => boolean): boolean {
if (menu.sub_menu?.length) {
return menu.sub_menu.some((item) => isMenuVisible(item, hasPermission))
}
if (!menu.permission) {
return true
}
return hasPermission(menu.permission)
}
function filterMenuItem(menu: Menu, hasPermission: (permission: string) => boolean): Menu | null {
if (menu.sub_menu?.length) {
const subMenu = menu.sub_menu
.map((item) => filterMenuItem(item, hasPermission))
.filter((item): item is Menu => item !== null)
if (!subMenu.length) {
return null
}
return { ...menu, sub_menu: subMenu }
}
return isMenuVisible(menu, hasPermission) ? menu : null
}
function sectionHasVisibleItems(
menu: MenuList,
startIndex: number,
hasPermission: (permission: string) => boolean,
): boolean {
for (let index = startIndex + 1; index < menu.length; index += 1) {
const entry = menu[index]
if (typeof entry === 'string') {
break
}
if (entry && isMenuVisible(entry, hasPermission)) {
return true
}
}
return false
}
export function filterMenuByPermission(
menu: MenuList,
hasPermission: (permission: string) => boolean,
): MenuList {
const filtered: MenuList = []
menu.forEach((entry, index) => {
if (typeof entry === 'string') {
if (sectionHasVisibleItems(menu, index, hasPermission)) {
filtered.push(entry)
}
return
}
const visibleItem = filterMenuItem(entry, hasPermission)
if (visibleItem) {
filtered.push(visibleItem)
}
})
return filtered
}
+12
View File
@@ -5,6 +5,18 @@ type ApiErrorBody = {
errors?: Record<string, string[]>
}
export function getApiValidationErrors(error: unknown): Record<string, string[]> | null {
if (axios.isAxiosError(error)) {
const data = error.response?.data as ApiErrorBody | undefined
if (data?.errors && Object.keys(data.errors).length > 0) {
return data.errors
}
}
return null
}
export function getApiErrorMessage(
error: unknown,
fallback = 'Something went wrong. Please try again.',
+17
View File
@@ -0,0 +1,17 @@
import type { RouteLocationNormalized } from 'vue-router'
export function getRouteRequiredPermissions(
route: RouteLocationNormalized,
): string[] {
for (const record of [...route.matched].reverse()) {
if (typeof record.meta.permission === 'string') {
return [record.meta.permission]
}
if (Array.isArray(record.meta.permissions) && record.meta.permissions.length) {
return record.meta.permissions
}
}
return []
}