diff --git a/.prettierrc.json b/.prettierrc.json index e0b26493..28b1a8b3 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,7 +2,7 @@ "useTabs": false, "tabWidth": 2, "singleQuote": true, - "trailingComma": "es5", + "trailingComma": "all", "printWidth": 120, "semi": false, "plugins": ["prettier-plugin-tailwindcss"] diff --git a/README.md b/README.md index 6f61806b..4b3f181a 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ RSSA - Front End ## Directory Structure for `app/` - `app.vue`: Main layout - `components` : Contains all reusable UI components. -- `components/flows` : Entry point for business logic and workflows. Pages or routes call these flow components to handle API requests and process application logic -- `components/app` : View-layer components that manage and present data. These are used within `flows/` to render or handle specific parts of the UI, and return results back to the flow +- `components/flow` : Entry point for business logic and workflows. Pages or routes call these flow components to handle API requests and process application logic +- `components/app` : View-layer components that manage and present data. These are used within `flow/` to render or handle specific parts of the UI, and return results back to the flow - `components/pub` : Public/shared components used across different parts of the app. - `composables` : Contains reusable logic and utility functions (e.g. composables, hooks).. - `layouts` : Reusable UI layout patterns used across pages. ## Directory Structure for `app/pages` - `pages/auth` : Authentication related pages. -- `pages/(features)` : Grouped feature modules that reflect specific business flows or domains. +- `pages/(features)` : Grouped feature modules that reflect specific business flow or domains. ## Directory Structure for `server/` - `server/api` : API or proxy requests @@ -37,13 +37,13 @@ The basic development workflow follows these steps: - Keep components pure, avoid making HTTP requests directly within them. - They receive data via props and emit events upward. -### Business Logic in `components/flows` +### Business Logic in `components/flow` - This layer connects the UI with the logic (API calls, validations, navigation). -- It composes components from `components/app/`, `components/pub/`, and other flows. +- It composes components from `components/app/`, `components/pub/`, and other flow. - Also responsible for managing state, side effects, and interactions. ### Create Pages in `pages/` -- Pages load the appropriate flow from `components/flows/`. +- Pages load the appropriate flow from `components/flow/`. - They do not contain UI or logic directly, just route level layout or guards. ## Git Workflows diff --git a/app/components/app/auth/SignIn.vue b/app/components/app/auth/SignIn.vue deleted file mode 100644 index e019734b..00000000 --- a/app/components/app/auth/SignIn.vue +++ /dev/null @@ -1,70 +0,0 @@ - - - - - diff --git a/app/components/app/auth/login.vue b/app/components/app/auth/login.vue new file mode 100644 index 00000000..29fb60b4 --- /dev/null +++ b/app/components/app/auth/login.vue @@ -0,0 +1,74 @@ + + + diff --git a/app/components/app/patient/entry-form.vue b/app/components/app/patient/entry-form.vue new file mode 100644 index 00000000..89d8b1bf --- /dev/null +++ b/app/components/app/patient/entry-form.vue @@ -0,0 +1,9 @@ + + + diff --git a/app/components/app/patient/list.vue b/app/components/app/patient/list.vue new file mode 100644 index 00000000..e69de29b diff --git a/app/components/app/patient/picker.vue b/app/components/app/patient/picker.vue new file mode 100644 index 00000000..e69de29b diff --git a/app/components/app/patient/search.vue b/app/components/app/patient/search.vue new file mode 100644 index 00000000..e69de29b diff --git a/app/components/flow/auth/login.vue b/app/components/flow/auth/login.vue new file mode 100644 index 00000000..c933b813 --- /dev/null +++ b/app/components/flow/auth/login.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/app/components/flow/patient/add.vue b/app/components/flow/patient/add.vue new file mode 100644 index 00000000..fd48eeba --- /dev/null +++ b/app/components/flow/patient/add.vue @@ -0,0 +1,5 @@ + + + diff --git a/app/components/flow/patient/list.vue b/app/components/flow/patient/list.vue new file mode 100644 index 00000000..0cae3161 --- /dev/null +++ b/app/components/flow/patient/list.vue @@ -0,0 +1,36 @@ + + + diff --git a/app/components/layout/AppSidebar.vue b/app/components/layout/AppSidebar.vue index ef70fd93..599313c7 100644 --- a/app/components/layout/AppSidebar.vue +++ b/app/components/layout/AppSidebar.vue @@ -1,17 +1,59 @@ + + diff --git a/app/components/pub/nav/header/prep.vue b/app/components/pub/nav/header/prep.vue new file mode 100644 index 00000000..8d856333 --- /dev/null +++ b/app/components/pub/nav/header/prep.vue @@ -0,0 +1,49 @@ + + + diff --git a/app/components/pub/nav/types.ts b/app/components/pub/nav/types.ts new file mode 100644 index 00000000..91b53410 --- /dev/null +++ b/app/components/pub/nav/types.ts @@ -0,0 +1,84 @@ +import type { ComponentType } from '@unovis/ts' + +export interface ListItemDto { + id: number + name: string + code: string +} + +export interface RecComponent { + idx?: number + rec: object + props?: any + component: ComponentType +} + +export interface Col { + span?: number + classVal?: string + style?: string + width?: number // specific for width + widthUnit?: string // specific for width +} + +export interface Th { + label: string + colSpan?: number + rowSpan?: number + classVal?: string + childClassVal?: string + style?: string + childStyle?: string + hideOnSm?: boolean +} + +export interface ButtonNav { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'negative' | 'ghost' | 'link' + classVal?: string + classValExt?: string + icon?: string + label: string + onClick?: () => void +} + +export interface QuickSearchNav { + inputClass?: string + inputPlaceHolder?: string + btnClass?: string + btnIcon?: string + btnLabel?: string + mainField?: string + searchParams: object + onSubmit?: (searchParams: object) => void +} + +export interface RefSearchNav { + onInput: (val: string) => void + onClick: () => void + onClear: () => void +} + +// prepared header for relatively common usage +export interface HeaderPrep { + title?: string + icon?: string + refSearchNav?: RefSearchNav + quickSearchNav?: QuickSearchNav + filterNav?: ButtonNav + addNav?: ButtonNav + printNav?: ButtonNav +} + +export interface KeyLabel { + key: string + label: string +} +export type FuncRecUnknown = (rec: unknown, idx: number) => unknown +export type FuncComponent = (rec: unknown, idx: number) => RecComponent +export type RecStrFuncUnknown = Record +export type RecStrFuncComponent = Record + +export interface KeyNames { + key: string + label: string +} diff --git a/app/composables/useXfetch.ts b/app/composables/useXfetch.ts new file mode 100644 index 00000000..24518c18 --- /dev/null +++ b/app/composables/useXfetch.ts @@ -0,0 +1,70 @@ +export interface XError { + code: string + message: string + expectedVal?: string + givenVal?: string +} + +export type XErrors = Record + +export interface XfetchResult { + success: boolean + status_code: number + body: object | any + errors?: XErrors | undefined + error?: XError | undefined + message?: string +} + +export type XfetchMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE' + +export async function xfetch( + url: string, + method: XfetchMethod = 'GET', + input?: object | FormData, + headers?: any, + _type = 'json', +): Promise { + let success = false + let body: object | any = {} + let errors: XErrors = {} + let error: XError | undefined = { + code: '', + message: '', + } + let message: string | undefined = '' + + if (!headers) { + headers = {} + } + if (input && !(input instanceof FormData)) { + headers['Content-Type'] = 'application/json' + } + + try { + const res = await $fetch.raw(url, { + method, + headers, + body: input instanceof FormData ? input : JSON.stringify(input), + }) + + body = res._data + success = true + return { success, status_code: res.status, body, errors, error, message } + } catch (fetchError: any) { + const status = fetchError.response?.status || 500 + const resJson = fetchError.data + + if (resJson?.errors) { + errors = resJson.errors + } else if (resJson?.code && resJson?.message) { + error = { code: resJson.code, message: resJson.message } + } else if (resJson?.message) { + message = resJson.message + } else { + message = fetchError.message || 'Something went wrong' + } + + return { success, status_code: status, body, errors, error, message } + } +} diff --git a/app/middleware/auth.global.ts b/app/middleware/auth.global.ts new file mode 100644 index 00000000..e3b98900 --- /dev/null +++ b/app/middleware/auth.global.ts @@ -0,0 +1,18 @@ +export default defineNuxtRouteMiddleware((to) => { + const { $pinia } = useNuxtApp() + + if (import.meta.client) { + const userStore = useUserStore($pinia) + + console.log('currRole', userStore.userRole) + console.log('isAuth', userStore.isAuthenticated) + if (!userStore.isAuthenticated) { + return navigateTo('/auth/login') + } + + const allowedRoles = to.meta.roles as string[] | undefined + if (allowedRoles && !allowedRoles.includes(userStore.userRole)) { + return navigateTo('/unauthorized') + } + } +}) diff --git a/app/middleware/auth.ts b/app/middleware/auth.ts deleted file mode 100644 index f9be043d..00000000 --- a/app/middleware/auth.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default defineNuxtRouteMiddleware((to, from) => { - // TODO: change this to actual api - const user = true - if (!user) { - return navigateTo('/auth/login') - } -}) diff --git a/app/pages/(features)/patient/[id]/detail.vue b/app/pages/(features)/patient/[id]/detail.vue new file mode 100644 index 00000000..33a36f0f --- /dev/null +++ b/app/pages/(features)/patient/[id]/detail.vue @@ -0,0 +1,9 @@ + + + diff --git a/app/pages/(features)/patient/[id]/edit.vue b/app/pages/(features)/patient/[id]/edit.vue new file mode 100644 index 00000000..2b7e8a31 --- /dev/null +++ b/app/pages/(features)/patient/[id]/edit.vue @@ -0,0 +1,9 @@ + + + diff --git a/app/pages/(features)/patient/add.vue b/app/pages/(features)/patient/add.vue new file mode 100644 index 00000000..12063ef7 --- /dev/null +++ b/app/pages/(features)/patient/add.vue @@ -0,0 +1,9 @@ + + + diff --git a/app/pages/(features)/patient/index.vue b/app/pages/(features)/patient/index.vue new file mode 100644 index 00000000..f1db5b53 --- /dev/null +++ b/app/pages/(features)/patient/index.vue @@ -0,0 +1,11 @@ + + + diff --git a/app/pages/auth/login.vue b/app/pages/auth/login.vue index 7c39ee2b..eb157c8c 100644 --- a/app/pages/auth/login.vue +++ b/app/pages/auth/login.vue @@ -10,7 +10,7 @@ definePageMeta({

Login RSSA

- + diff --git a/app/pages/index.vue b/app/pages/index.vue index 5c7c6c81..f745ba70 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -2,9 +2,11 @@ import { Activity, CreditCard, DollarSign, Users } from 'lucide-vue-next' definePageMeta({ - middleware: 'auth', + roles: ['sys', 'doc'], }) +const { userRole } = useUserStore() + const dataCard = ref({ totalRevenue: 0, totalRevenueDesc: 0, @@ -61,7 +63,7 @@ onMounted(() => {