✨ feat (patient): add error page for patient add page permission
This commit is contained in:
@@ -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,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[]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ withDefaults(
|
||||
}>(),
|
||||
{
|
||||
size: 'default',
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const { setOpenMobile } = useSidebar()
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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')
|
||||
// }
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
@@ -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
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "nuxt-app",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
import { defineConfig, presetWind, presetAttributify, presetIcons } from 'unocss'
|
||||
import { defineConfig, presetAttributify, presetIcons, presetWind } from 'unocss'
|
||||
|
||||
export default defineConfig({
|
||||
presets: [
|
||||
|
||||
Reference in New Issue
Block a user