feat (patient): add error page for patient add page permission

This commit is contained in:
Abizrh
2025-08-13 10:12:12 +07:00
parent 6e5d5863ab
commit db9a87d825
18 changed files with 88 additions and 49 deletions
+16 -3
View File
@@ -1,53 +1,66 @@
# SIMRS - FE
RSSA - Front End
## Framework Guide
- [Vue Style Guide](https://vuejs.org/style-guide)
- [Nuxt Style Guide](https://nuxt.com/docs/4.x/guide)
## Configuration
- `nuxt.config.ts`<br />Nuxt configuration file
- `.env`<br />Some environment variables
## Directory Structure for `app/`
- `app.vue`: Main layout
- `components` : Contains all reusable UI components.
- `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/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`
## Directory Structure for `app/pages`
- `pages/auth` : Authentication related pages.
- `pages/(features)` : Grouped feature modules that reflect specific business flow or domains.
## Directory Structure for `server/`
## Directory Structure for `server/`
- `server/api` : API or proxy requests
## Workflows
The basic development workflow follows these steps:
### Define Your Data in `models/`
- Create data definitions or interfaces.
- These should represent the structure of the data used across your app.
### Build UI Components in `components/app`
- Create reusable UI and app specific components.
- Keep components pure, avoid making HTTP requests directly within them.
- They receive data via props and emit events upward.
### 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 flow.
- Also responsible for managing state, side effects, and interactions.
### Create Pages in `pages/`
- Pages load the appropriate flow from `components/flow/`.
- They do not contain UI or logic directly, just route level layout or guards.
## Git Workflows
The basic git workflow follows these steps:
1. Create a new branch on `dev`
- branch name should be `feat/<feature-name>` or `fix/<bug-name>`
2. Make your changes
+1 -1
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { cols, header, keys, funcParsed, funcHtml, funcComponent } from './list-cfg.ts'
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg.ts'
defineProps<{
data: any[]
+1 -1
View File
@@ -31,7 +31,7 @@ async function getPatientList() {
const resp = await xfetch('/api/v1/patient')
console.log('data patient', resp)
if (resp.success) {
data.value = (resp.body as Record<string, any>)['data']
data.value = (resp.body as Record<string, any>).data
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ withDefaults(
}>(),
{
size: 'default',
}
},
)
const { setOpenMobile } = useSidebar()
+50
View File
@@ -0,0 +1,50 @@
<script setup lang="ts">
defineProps<{
statusCode: number
}>()
const router = useRouter()
</script>
<template>
<div class="h-svh">
<div class="m-auto flex h-full w-full flex-col items-center justify-center gap-2">
<template v-if="statusCode === 403">
<h1 class="text-[7rem] font-bold leading-tight">403</h1>
<span class="font-medium">Access Forbidden</span>
<p class="text-muted-foreground text-center">
You don't have necessary permission <br />
to access this resource.
</p>
</template>
<template v-else-if="statusCode === 404">
<h1 class="text-[7rem] font-bold leading-tight">404</h1>
<span class="font-medium">Page Not Found</span>
<p class="text-muted-foreground text-center">
The page you are looking for <br />
doesn't exist.
</p>
</template>
<template v-else-if="statusCode === 401">
<h1 class="text-[7rem] font-bold leading-tight">401</h1>
<span class="font-medium">Unauthorized Access</span>
<p class="text-muted-foreground text-center">
Please log in with the appropriate credentials <br />
to access this resource.
</p>
</template>
<template v-else>
<h1 class="text-[7rem] font-bold leading-tight">500</h1>
<span class="font-medium">Internal Server Error</span>
<p class="text-muted-foreground text-center">
Something went wrong on our end. <br />
Please try again later.
</p>
</template>
<div class="mt-6 flex gap-4">
<Button variant="outline" @click="router.back()"> Kembali </Button>
<Button @click="router.push('/')"> Kembali Ke Dashboard </Button>
</div>
</div>
</div>
</template>
+3 -3
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from '@/components/pub/ui/table'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/pub/ui/table'
defineProps<{
rows: unknown[]
@@ -19,7 +19,7 @@ defineProps<{
<TableHead
v-for="(h, idx) in header[0]"
:key="`head-${idx}`"
:style="{ width: cols[idx]?.width ? cols[idx].width + 'px' : undefined }"
:style="{ width: cols[idx]?.width ? `${cols[idx].width}px` : undefined }"
>
{{ h.label }}
</TableHead>
@@ -31,8 +31,8 @@ defineProps<{
<TableCell v-for="(key, cellIndex) in keys" :key="`cell-${rowIndex}-${cellIndex}`">
<!-- If funcComponent has a renderer -->
<component
v-if="funcComponent[key]"
:is="funcComponent[key](row, rowIndex).component"
v-if="funcComponent[key]"
v-bind="funcComponent[key](row, rowIndex)"
/>
<!-- If funcParsed or funcHtml returns a value -->
@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { ListItemDto, LinkItem } from './types'
import type { LinkItem, ListItemDto } from './types'
const props = defineProps<{
rec: ListItemDto
@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { ListItemDto, LinkItem } from './types'
import type { LinkItem, ListItemDto } from './types'
const props = defineProps<{
rec: ListItemDto
@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { ListItemDto, LinkItem } from './types'
import type { LinkItem, ListItemDto } from './types'
const props = defineProps<{
rec: ListItemDto
@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { ListItemDto, LinkItem } from './types'
import type { LinkItem, ListItemDto } from './types'
const props = defineProps<{
rec: ListItemDto
+1 -16
View File
@@ -2,25 +2,10 @@
definePageMeta({
layout: 'blank',
})
const router = useRouter()
</script>
<template>
<div class="h-svh">
<div class="m-auto flex h-full w-full flex-col items-center justify-center gap-2">
<h1 class="text-[7rem] font-bold leading-tight">404</h1>
<span class="font-medium">Oops! Page Not Found!</span>
<p class="text-muted-foreground text-center">
It seems like the page you're looking for <br />
does not exist or might have been removed.
</p>
<div class="mt-6 flex gap-4">
<Button variant="outline" @click="router.back()"> Go Back </Button>
<Button @click="router.push('/')"> Back to Home </Button>
</div>
</div>
</div>
<PubBaseError :status-code="401" />
</template>
<style scoped></style>
+3 -1
View File
@@ -1,3 +1,5 @@
import type { RoleAccess } from '~/models/role'
export const PAGE_PERMISSIONS = {
'/patient': {
doctor: ['R'],
@@ -7,4 +9,4 @@ export const PAGE_PERMISSIONS = {
billing: ['R'],
management: ['R'],
},
} as const
} as const satisfies Record<string, RoleAccess>
-9
View File
@@ -11,14 +11,5 @@ export default defineNuxtRouteMiddleware((to) => {
if (!userStore.isAuthenticated) {
return navigateTo('/401')
}
// const allowedRoles = to.meta.roles as string[] | undefined
// if (allowedRoles && !allowedRoles.includes(userStore.userRole)) {
// return navigateTo('/unauthorized')
// }
// const allowedRoles = to.meta.roles as string[] | undefined
// if (allowedRoles && !userStore.userRole.some((r) => allowedRoles.includes(r))) {
// return navigateTo('/unauthorized')
// }
}
})
+1 -3
View File
@@ -30,7 +30,5 @@ const canCreate = hasCreateAccess(roleAccess)
<div v-if="canCreate">
<FlowPatientAdd />
</div>
<div v-else>
<p>You don't have permission to create patient records.</p>
</div>
<PubBaseError v-else :status-code="403" />
</template>
+4 -4
View File
@@ -18,14 +18,14 @@ export default withNuxt(
{
rules: {
// Basic rules
quotes: ['error', 'single', { avoidEscape: true }],
'quotes': ['error', 'single', { avoidEscape: true }],
'style/no-trailing-spaces': ['error', { ignoreComments: true }],
'no-console': 'off',
// Relax strict formatting rules
'style/brace-style': 'off', // Allow inline if
curly: ['error', 'multi-line'], // Only require braces for multi-line
'curly': ['error', 'multi-line'], // Only require braces for multi-line
'style/arrow-parens': 'off',
// UnoCSS - make it warning instead of error, or disable completely
@@ -48,6 +48,6 @@ export default withNuxt(
rules: {
'style/no-trailing-spaces': 'off',
},
}
)
},
),
)
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
+1 -1
View File
@@ -23,7 +23,7 @@ export default defineEventHandler(async (event) => {
if (headers['content-type']) forwardHeaders.set('Content-Type', headers['content-type'])
forwardHeaders.set('Authorization', `Bearer ${bearer}`)
let body: any = undefined
let body: any
if (['POST', 'PATCH'].includes(method!)) {
if (headers['content-type']?.includes('multipart/form-data')) {
body = await readBody(event)
+1 -1
View File
@@ -1,4 +1,4 @@
import { defineConfig, presetWind, presetAttributify, presetIcons } from 'unocss'
import { defineConfig, presetAttributify, presetIcons, presetWind } from 'unocss'
export default defineConfig({
presets: [