Merge pull request #34 from dikstub-rssa/30-fe---item-itemprice

Feat Item, Item Price and Rename flow > content
This commit is contained in:
Munawwirul Jamal
2025-09-08 08:53:15 +07:00
committed by GitHub
50 changed files with 566 additions and 23 deletions
+10 -6
View File
@@ -20,16 +20,20 @@ RSSA - Front End
- `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/content` : Entry point for business logic and workflows. Pages or routes call these content components to handle API requests and process application logic
- `components/app` : View-layer components that manage and present data. These are used within `content/` to render or handle specific parts of the UI, and return results back to the content
- `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.
- `models` : Contains data definitions or interfaces.
- `schemas` : Contains JSON schemas used for validation.
- `services` : Contains reusable API calls and business logic.
## Directory Structure for `app/pages`
- `pages/auth` : Authentication related pages.
- `pages/(features)` : Grouped feature modules that reflect specific business flow or domains.
- `pages/(features)` : Grouped feature modules that reflect specific business content or domains.
## Directory Structure for `server/`
@@ -50,16 +54,16 @@ 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/flow`
### Business Logic in `components/content`
- This layer connects the UI with the logic (API calls, validations, navigation).
- It composes components from `components/app/`, `components/pub/`, and other flow.
- It composes components from `components/app/`, `components/pub/`, and other content.
- Also responsible for managing state, side effects, and interactions.
### Create Pages in `pages/`
- Define permissions and guards for each page.
- Pages load the appropriate flow from `components/flow/`.
- Pages load the appropriate content from `components/content/`.
- They do not contain UI or logic directly, just route level layout or guards.
## Code Conventions
@@ -0,0 +1,50 @@
<script setup lang="ts">
import Block from '~/components/pub/custom-ui/form/block.vue'
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
import Field from '~/components/pub/custom-ui/form/field.vue'
import Label from '~/components/pub/custom-ui/form/label.vue'
const props = defineProps<{ modelValue: any }>()
const emit = defineEmits(['update:modelValue', 'event'])
const data = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
})
const items = [
{ value: '1', label: 'item 1' },
{ value: '2', label: 'item 2' },
{ value: '3', label: 'item 3' },
{ value: '4', label: 'item 4' },
]
</script>
<template>
<form id="entry-form">
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
<div class="flex flex-col justify-between">
<Block>
<FieldGroup>
<Label>Items</Label>
<Field>
<Select :items="items" />
</Field>
</FieldGroup>
<FieldGroup>
<Label>Perusahaan Insuransi</Label>
<Field>
<Select :items="items" />
</Field>
</FieldGroup>
<FieldGroup>
<Label>Harga</Label>
<Field>
<Input v-model="data.price" />
</Field>
</FieldGroup>
</Block>
</div>
</div>
</form>
</template>
+46
View File
@@ -0,0 +1,46 @@
import type {
Col,
KeyLabel,
RecComponent,
RecStrFuncComponent,
RecStrFuncUnknown,
Th,
} from '~/components/pub/custom-ui/data/types'
import { defineAsyncComponent } from 'vue'
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
const _doctorStatus = {
0: 'Tidak Aktif',
1: 'Aktif',
}
export const cols: Col[] = [{}, {}, { width: 50 }]
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Aksi' }]]
export const keys = ['code', 'name', 'action']
export const delKeyNames: KeyLabel[] = [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
]
export const funcParsed: RecStrFuncUnknown = {}
export const funcComponent: RecStrFuncComponent = {
action(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: action,
}
return res
},
}
export const funcHtml: RecStrFuncUnknown = {
patient_address(_rec) {
return '-'
},
}
+19
View File
@@ -0,0 +1,19 @@
<script setup lang="ts">
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
defineProps<{
data: any[]
}>()
</script>
<template>
<PubBaseDataTable
:rows="data"
:cols="cols"
:header="header"
:keys="keys"
:func-parsed="funcParsed"
:func-html="funcHtml"
:func-component="funcComponent"
/>
</template>
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { Badge } from '~/components/pub/ui/badge'
const props = defineProps<{
rec: any
idx?: number
}>()
const doctorStatus = {
0: 'Tidak Aktif',
1: 'Aktif',
}
const statusText = computed(() => {
return doctorStatus[props.rec.status_code as keyof typeof doctorStatus]
})
const badgeVariant = computed(() => {
return props.rec.status_code === 1 ? 'default' : 'destructive'
})
</script>
<template>
<div class="flex justify-center">
<Badge :variant="badgeVariant">
{{ statusText }}
</Badge>
</div>
</template>
+68
View File
@@ -0,0 +1,68 @@
<script setup lang="ts">
import Block from '~/components/pub/custom-ui/form/block.vue'
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
import Field from '~/components/pub/custom-ui/form/field.vue'
import Label from '~/components/pub/custom-ui/form/label.vue'
const props = defineProps<{ modelValue: any }>()
const emit = defineEmits(['update:modelValue', 'event'])
const data = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
})
const items = [
{ value: '1', label: 'item 1' },
{ value: '2', label: 'item 2' },
{ value: '3', label: 'item 3' },
{ value: '4', label: 'item 4' },
]
</script>
<template>
<form id="entry-form">
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
<div class="flex flex-col justify-between">
<Block>
<FieldGroup>
<Label>Nama</Label>
<Field>
<Input v-model="data.name" />
</Field>
</FieldGroup>
<FieldGroup>
<Label>Kode</Label>
<Field>
<Input v-model="data.code" />
</Field>
</FieldGroup>
<FieldGroup>
<Label>Item Group</Label>
<Field>
<Select :items="items" />
</Field>
</FieldGroup>
<FieldGroup>
<Label>UOM</Label>
<Field>
<Select :items="items" />
</Field>
</FieldGroup>
<FieldGroup>
<Label>Infra</Label>
<Field>
<Select :items="items" />
</Field>
</FieldGroup>
<FieldGroup>
<Label>Harga</Label>
<Field>
<Input v-model="data.price" />
</Field>
</FieldGroup>
</Block>
</div>
</div>
</form>
</template>
+46
View File
@@ -0,0 +1,46 @@
import type {
Col,
KeyLabel,
RecComponent,
RecStrFuncComponent,
RecStrFuncUnknown,
Th,
} from '~/components/pub/custom-ui/data/types'
import { defineAsyncComponent } from 'vue'
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
const _doctorStatus = {
0: 'Tidak Aktif',
1: 'Aktif',
}
export const cols: Col[] = [{}, {}, { width: 50 }]
export const header: Th[][] = [[{ label: 'Kode' }, { label: 'Nama' }, { label: 'Aksi' }]]
export const keys = ['code', 'name', 'action']
export const delKeyNames: KeyLabel[] = [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
]
export const funcParsed: RecStrFuncUnknown = {}
export const funcComponent: RecStrFuncComponent = {
action(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: action,
}
return res
},
}
export const funcHtml: RecStrFuncUnknown = {
patient_address(_rec) {
return '-'
},
}
+19
View File
@@ -0,0 +1,19 @@
<script setup lang="ts">
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
defineProps<{
data: any[]
}>()
</script>
<template>
<PubBaseDataTable
:rows="data"
:cols="cols"
:header="header"
:keys="keys"
:func-parsed="funcParsed"
:func-html="funcHtml"
:func-component="funcComponent"
/>
</template>
View File
View File
+29
View File
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { Badge } from '~/components/pub/ui/badge'
const props = defineProps<{
rec: any
idx?: number
}>()
const doctorStatus = {
0: 'Tidak Aktif',
1: 'Aktif',
}
const statusText = computed(() => {
return doctorStatus[props.rec.status_code as keyof typeof doctorStatus]
})
const badgeVariant = computed(() => {
return props.rec.status_code === 1 ? 'default' : 'destructive'
})
</script>
<template>
<div class="flex justify-center">
<Badge :variant="badgeVariant">
{{ statusText }}
</Badge>
</div>
</template>
+71
View File
@@ -0,0 +1,71 @@
<script setup lang="ts">
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
import Modal from '~/components/pub/base/modal/modal.vue'
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
const data = ref([])
const entry = ref<any>({})
const refSearchNav: RefSearchNav = {
onClick: () => {
// open filter modal
},
onInput: (_val: string) => {
// filter patient list
},
onClear: () => {
// clear url param
},
}
// Loading state management
const isLoading = reactive<DataTableLoader>({
summary: false,
isTableLoading: false,
})
const isOpen = ref(false)
const recId = ref<number>(0)
const recAction = ref<string>('')
const recItem = ref<any>(null)
const hreaderPrep: HeaderPrep = {
title: 'Item',
icon: 'i-lucide-users',
addNav: {
label: 'Tambah',
onClick: () => (isOpen.value = true),
},
}
async function getPatientList() {
isLoading.isTableLoading = true
const resp = await xfetch('/api/v1/medicine-group')
if (resp.success) {
data.value = (resp.body as Record<string, any>).data
}
isLoading.isTableLoading = false
}
onMounted(() => {
getPatientList()
})
provide('rec_id', recId)
provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
</script>
<template>
<Header :prep="{ ...hreaderPrep }" :ref-search-nav="refSearchNav" />
<div class="my-4 flex flex-1 flex-col gap-4 md:gap-8">
<AppItemList :data="data" />
</div>
<Modal v-model:open="isOpen" title="Tambah Golongan Obat" size="xl" prevent-outside>
<AppItemEntryForm v-model="entry" />
</Modal>
</template>
@@ -0,0 +1,75 @@
<script setup lang="ts">
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
import Modal from '~/components/pub/base/modal/modal.vue'
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
const data = ref([])
const entry = ref<any>({})
const page = ref(1)
const rowsPerPage = ref(10)
const totalPages = 20
const refSearchNav: RefSearchNav = {
onClick: () => {
// open filter modal
},
onInput: (_val: string) => {
// filter patient list
},
onClear: () => {
// clear url param
},
}
// Loading state management
const isLoading = reactive<DataTableLoader>({
summary: false,
isTableLoading: false,
})
const isOpen = ref(false)
const recId = ref<number>(0)
const recAction = ref<string>('')
const recItem = ref<any>(null)
const hreaderPrep: HeaderPrep = {
title: 'Golongan Obat',
icon: 'i-lucide-users',
addNav: {
label: 'Tambah',
onClick: () => (isOpen.value = true),
},
}
async function getPatientList() {
isLoading.isTableLoading = true
const resp = await xfetch('/api/v1/medicine-group')
if (resp.success) {
data.value = (resp.body as Record<string, any>).data
}
isLoading.isTableLoading = false
}
onMounted(() => {
getPatientList()
})
provide('rec_id', recId)
provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
</script>
<template>
<Header :prep="{ ...hreaderPrep }" :ref-search-nav="refSearchNav" />
<div class="my-4 flex flex-1 flex-col gap-4 md:gap-8">
<AppMedicineGroupList :data="data" />
<Pagination v-model:page="page" v-model:rows-per-page="rowsPerPage" :total-pages="totalPages" />
</div>
<Modal v-model:open="isOpen" title="Tambah Golongan Obat" size="lg" prevent-outside>
<AppMedicineGroupEntryForm v-model="entry" />
</Modal>
</template>
+39
View File
@@ -0,0 +1,39 @@
export interface ItemPrice {
id: string
item_id: number
price: number
insuranceCompany_code: string
}
export interface CreateDto {
item_id: number
price: number
insuranceCompany_code: string
}
export interface GetListDto {
page: number
size: number
name?: string
code?: string
}
export interface GetDetailDto {
id?: string
}
export interface UpdateDto extends CreateDto {
id?: number
}
export interface DeleteDto {
id?: string
}
export function genMedicine(): CreateDto {
return {
item_id: 1,
price: 1,
insuranceCompany_code: 'test',
}
}
+48
View File
@@ -0,0 +1,48 @@
export interface Item {
id: string
name: string
code: string
itemGroup_code: string
uom_code: string
infra_id: number
stock: number
}
export interface CreateDto {
name: string
code: string
itemGroup_code: string
uom_code: string
infra_id: number
stock: number
}
export interface GetListDto {
page: number
size: number
name?: string
code?: string
}
export interface GetDetailDto {
id?: string
}
export interface UpdateDto extends CreateDto {
id?: number
}
export interface DeleteDto {
id?: string
}
export function genMedicine(): CreateDto {
return {
name: 'test',
code: 'test',
itemGroup_code: 'test',
uom_code: 'test',
infra_id: 1,
stock: 1,
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ const canCreate = hasCreateAccess(roleAccess)
<template>
<div v-if="canCreate">
<FlowDoctorAdd />
<ContentDoctorAdd />
</div>
<Error v-else :status-code="403" />
</template>
+1 -1
View File
@@ -33,7 +33,7 @@ const canRead = hasReadAccess(roleAccess)
<template>
<div>
<div v-if="canRead">
<FlowDoctorList />
<ContentDoctorList />
</div>
<Error v-else :status-code="403" />
</div>
+1 -1
View File
@@ -35,7 +35,7 @@ const canCreate = hasCreateAccess(roleAccess)
<template>
<div v-if="canCreate">
<FlowUserEntry />
<ContentUserEntry />
</div>
<Error v-else :status-code="403" />
</template>
@@ -33,7 +33,7 @@ const canRead = hasReadAccess(roleAccess)
<template>
<div>
<div v-if="canRead">
<FlowUserList />
<ContentUserList />
</div>
<Error v-else :status-code="403" />
</div>
+1 -1
View File
@@ -35,7 +35,7 @@ const canCreate = hasCreateAccess(roleAccess)
<template>
<div v-if="canCreate">
<FlowPatientAdd />
<ContentPatientAdd />
</div>
<Error v-else :status-code="403" />
</template>
+1 -1
View File
@@ -33,7 +33,7 @@ const canRead = hasReadAccess(roleAccess)
<template>
<div>
<div v-if="canRead">
<FlowPatientList />
<ContentPatientList />
</div>
<Error v-else :status-code="403" />
</div>
@@ -34,7 +34,7 @@ const canCreate = hasCreateAccess(roleAccess)
<template>
<div v-if="canCreate">
<FlowDoctorAdd />
<ContentDoctorAdd />
</div>
<PubBaseError v-else :status-code="403" />
</template>
+1 -1
View File
@@ -34,7 +34,7 @@ const canCreate = hasCreateAccess(roleAccess)
<template>
<div v-if="canCreate">
<FlowPatientAdd />
<ContentPatientAdd />
</div>
<PubBaseError v-else :status-code="403" />
</template>
+1 -1
View File
@@ -32,7 +32,7 @@ const canRead = hasReadAccess(roleAccess)
<template>
<div>
<div v-if="canRead">
<FlowSatusehatList />
<ContentSatusehatList />
</div>
<PubBaseError v-else :status-code="403" />
</div>
@@ -33,7 +33,7 @@ const canRead = hasReadAccess(roleAccess)
<template>
<div>
<div v-if="canRead">
<FlowMedicineMethodList />
<ContentMedicineMethodList />
</div>
<Error v-else :status-code="403" />
</div>
@@ -33,7 +33,7 @@ const canRead = hasReadAccess(roleAccess)
<template>
<div>
<div v-if="canRead">
<FlowMedicineGroupList />
<ContentMedicineGroupList />
</div>
<Error v-else :status-code="403" />
</div>
@@ -35,7 +35,7 @@ const canCreate = hasCreateAccess(roleAccess)
<template>
<div v-if="canCreate">
<FlowMedicineEntry />
<ContentMedicineEntry />
</div>
<Error v-else :status-code="403" />
</template>
@@ -35,7 +35,7 @@ const canCreate = hasCreateAccess(roleAccess)
<template>
<div v-if="canCreate">
<FlowMedicineEntry />
<ContentMedicineEntry />
</div>
<Error v-else :status-code="403" />
</template>
@@ -33,7 +33,7 @@ const canRead = hasReadAccess(roleAccess)
<template>
<div>
<div v-if="canRead">
<FlowMedicineList />
<ContentMedicineList />
</div>
<Error v-else :status-code="403" />
</div>
+1 -1
View File
@@ -49,7 +49,7 @@ const navMenu = ref({
{
title: 'Test Medicine List',
icon: 'i-lucide-user',
component: defineAsyncComponent(() => import('~/components/flow/medicine-method/list.vue')),
component: defineAsyncComponent(() => import('~/components/content/item/list.vue')),
},
],
})
+1 -1
View File
@@ -11,7 +11,7 @@ definePageMeta({
<div class="grid gap-2 text-center">
<h1 class="text-2xl font-semibold tracking-tight">Login RSSA</h1>
</div>
<FlowAuthLogin />
<ContentAuthLogin />
</div>
</LayoutAuth>
</template>
+1 -1
View File
@@ -13,5 +13,5 @@ useHead({
</script>
<template>
<FlowDashboard />
<ContentDashboard />
</template>