Merge branch 'dev' of github.com:dikstub-rssa/simrs-fe into feat/patient-63-adjustment
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import type { Item } from './index'
|
||||
|
||||
const props = defineProps<{
|
||||
items: Item[]
|
||||
useFlex?: boolean
|
||||
}>()
|
||||
|
||||
const model = defineModel<string[]>()
|
||||
const checks: Record<string, any> = ref({})
|
||||
|
||||
model.value = []
|
||||
props.items.forEach((item) => {
|
||||
checks.value[item.value] = item.checked || false
|
||||
})
|
||||
|
||||
watch(checks, () => {
|
||||
const list: string[] = []
|
||||
Object.keys(checks.value).forEach((key) => {
|
||||
if (checks.value[key]) {
|
||||
list.push(key)
|
||||
}
|
||||
})
|
||||
model.value = list
|
||||
}, { deep: true })
|
||||
|
||||
function check(value: string, status: boolean) {
|
||||
checks.value[value] = status
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="useFlex ? 'flex gap-2' : ''">
|
||||
<label v-for="item, idx in items" :key="item.value" class="flex pe-4">
|
||||
<Checkbox @update:checked="(status) => check(item.value, status)" :checked="checks[item.value]" />
|
||||
<div class="pt-0.5 ps-2">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
export interface Item {
|
||||
value: string
|
||||
label: string
|
||||
checked?: boolean
|
||||
}
|
||||
|
||||
export function checkItems(items: Item[], value: string[]) {
|
||||
items.forEach((item, idx) => {
|
||||
items[idx]!.checked = value.includes(item.value)
|
||||
// item.checked = value.includes(item.value)
|
||||
})
|
||||
}
|
||||
|
||||
export { default as Checkboxes } from './checkboxes.vue'
|
||||
@@ -4,7 +4,7 @@ import { type Item } from './index'
|
||||
|
||||
const props = defineProps<{
|
||||
id?: string
|
||||
modelValue?: string
|
||||
modelValue?: string | number
|
||||
items: Item[]
|
||||
placeholder?: string
|
||||
searchPlaceholder?: string
|
||||
@@ -16,8 +16,8 @@ const props = defineProps<{
|
||||
const model = defineModel()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
'update:searchText': [value: string]
|
||||
'update:modelValue': [value: string | number]
|
||||
'update:searchText': [value: string | number]
|
||||
}>()
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface Item {
|
||||
value: string
|
||||
value: string | number
|
||||
label: string
|
||||
code?: string
|
||||
priority?: number
|
||||
@@ -7,12 +7,12 @@ export interface Item {
|
||||
|
||||
export function recStrToItem(input: Record<string, string>): Item[] {
|
||||
const items: Item[] = []
|
||||
let idx = 0;
|
||||
let idx = 0
|
||||
for (const key in input) {
|
||||
if (input.hasOwnProperty(key)) {
|
||||
items.push({
|
||||
value: key || ('unknown-' + idx),
|
||||
label: input[key] || ('unknown-' + idx),
|
||||
value: key || 'unknown-' + idx,
|
||||
label: input[key] || 'unknown-' + idx,
|
||||
})
|
||||
}
|
||||
idx++
|
||||
|
||||
@@ -1,33 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
height?: number
|
||||
class?: string
|
||||
activeTab?: 1 | 2
|
||||
}>()
|
||||
|
||||
const classVal = computed(() => {
|
||||
return props.class ? props.class : ''
|
||||
})
|
||||
const activeTab = ref(props.activeTab || 1)
|
||||
|
||||
function switchActiveTab() {
|
||||
activeTab.value = activeTab.value === 1 ? 2 : 1
|
||||
function handleClick(value: 1 | 2) {
|
||||
activeTab.value = value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="`content-switcher ${classVal}`" :style="height ? `height:${200}px` : ''">
|
||||
<div class="wrapper">
|
||||
<div :class="`item item-1 ${activeTab === 1 ? 'active' : 'inactive'}`">
|
||||
<slot name="content1" />
|
||||
<div class="content-switcher" :style="`height: ${height || 200}px`">
|
||||
<div :class="`${activeTab === 1 ? 'active' : 'inactive'}`">
|
||||
<div class="content-wrapper">
|
||||
<div>
|
||||
<slot name="content1" />
|
||||
</div>
|
||||
</div>
|
||||
<div :class="`nav border-slate-300 ${ activeTab == 1 ? 'border-l' : 'border-r'}`">
|
||||
<button @click="switchActiveTab()" class="!p-0 w-full h-full">
|
||||
<Icon :name="activeTab == 1 ? 'i-lucide-chevron-left' : 'i-lucide-chevron-right'" class="text-3xl" />
|
||||
<div class="content-nav">
|
||||
<button @click="handleClick(1)">
|
||||
<Icon name="i-lucide-chevron-right" />
|
||||
</button>
|
||||
</div>
|
||||
<div :class="`item item-2 ${activeTab === 2 ? 'active' : 'inactive'}`">
|
||||
<slot name="content2" />
|
||||
</div>
|
||||
<div :class="`${activeTab === 2 ? 'active' : 'inactive'}`">
|
||||
<div class="content-nav">
|
||||
<button @click="handleClick(2)">
|
||||
<Icon name="i-lucide-chevron-left" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="content-wrapper">
|
||||
<div>
|
||||
<slot name="content2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,24 +42,45 @@ function switchActiveTab() {
|
||||
|
||||
<style>
|
||||
.content-switcher {
|
||||
@apply overflow-hidden
|
||||
@apply flex overflow-hidden gap-3
|
||||
}
|
||||
.wrapper {
|
||||
@apply flex w-[200%] h-full
|
||||
}
|
||||
.item {
|
||||
@apply w-[calc(50%-60px)]
|
||||
.content-switcher > * {
|
||||
@apply border border-slate-300 rounded-md flex overflow-hidden
|
||||
}
|
||||
|
||||
.item-1.active {
|
||||
@apply ms-0 transition-all duration-500 ease-in-out
|
||||
.content-wrapper {
|
||||
@apply p-4 2xl:p-5 overflow-hidden grow
|
||||
}
|
||||
.item-1.inactive {
|
||||
@apply -ms-[calc(50%-60px)] transition-all duration-500 ease-in-out
|
||||
.inactive .content-wrapper {
|
||||
@apply p-0 w-0
|
||||
}
|
||||
|
||||
.nav {
|
||||
@apply h-full w-[60px] flex flex-row items-center justify-center content-center !text-2xl overflow-hidden
|
||||
.content-nav {
|
||||
@apply h-full flex flex-row items-center justify-center content-center !text-2xl overflow-hidden
|
||||
}
|
||||
|
||||
.content-nav button {
|
||||
@apply pt-2 px-2 h-full w-full
|
||||
}
|
||||
|
||||
/* .content-switcher .inactive > .content-wrapper {
|
||||
@apply w-0 p-0 opacity-0 transition-all duration-500 ease-in-out
|
||||
} */
|
||||
.content-switcher .inactive {
|
||||
@apply w-16 transition-all duration-500 ease-in-out
|
||||
}
|
||||
.content-switcher .inactive > .content-nav {
|
||||
@apply w-full transition-all duration-100 ease-in-out
|
||||
}
|
||||
|
||||
.content-switcher .active {
|
||||
@apply grow transition-all duration-500 ease-in-out
|
||||
}
|
||||
.content-switcher .active > .content-nav {
|
||||
@apply w-0 transition-all duration-100 ease-in-out
|
||||
}
|
||||
/* .content-switcher .active > .content-wrapper {
|
||||
@apply w-full delay-1000 transition-all duration-1000 ease-in-out
|
||||
} */
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import type { LinkItem, ListItemDto } from './types'
|
||||
import { ActionEvents } from './types'
|
||||
|
||||
const props = defineProps<{
|
||||
rec: ListItemDto
|
||||
}>()
|
||||
|
||||
const recId = inject<Ref<number>>('rec_id')!
|
||||
const recAction = inject<Ref<string>>('rec_action')!
|
||||
const recItem = inject<Ref<any>>('rec_item')!
|
||||
const timestamp = inject<Ref<number>>('timestamp')!
|
||||
const activeKey = ref<string | null>(null)
|
||||
const linkItems: LinkItem[] = [
|
||||
{
|
||||
label: 'Detail',
|
||||
onClick: () => {
|
||||
detail()
|
||||
},
|
||||
icon: 'i-lucide-eye',
|
||||
},
|
||||
]
|
||||
|
||||
function detail() {
|
||||
recId.value = props.rec.id || 0
|
||||
recAction.value = ActionEvents.showDetail
|
||||
recItem.value = props.rec
|
||||
timestamp.value = Date.now()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white dark:data-[state=open]:bg-slate-800"
|
||||
>
|
||||
<Icon
|
||||
name="i-lucide-chevrons-up-down"
|
||||
class="ml-auto size-4"
|
||||
/>
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg border border-slate-200 bg-white text-black dark:border-slate-700 dark:bg-slate-800 dark:text-white"
|
||||
align="end"
|
||||
>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
v-for="item in linkItems"
|
||||
:key="item.label"
|
||||
class="hover:bg-gray-100 dark:hover:bg-slate-700"
|
||||
@click="item.onClick"
|
||||
@mouseenter="activeKey = item.label"
|
||||
@mouseleave="activeKey = null"
|
||||
>
|
||||
<Icon :name="item.icon ?? ''" />
|
||||
<span :class="activeKey === item.label ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -53,8 +53,8 @@ const settingClass = computed(() => {
|
||||
'[&_.cell]:2xl:flex',
|
||||
][getBreakpointIdx(props.cellFlexPoint)]
|
||||
cls += ' [&_.label]:flex-shrink-0 ' + [
|
||||
'[&_.label]:md:w-16 [&_.label]:xl:w-20',
|
||||
'[&_.label]:md:w-20 [&_.label]:xl:w-24',
|
||||
'[&_.label]:md:w-12 [&_.label]:xl:w-20',
|
||||
'[&_.label]:md:w-16 [&_.label]:xl:w-24',
|
||||
'[&_.label]:md:w-24 [&_.label]:xl:w-32',
|
||||
'[&_.label]:md:w-32 [&_.label]:xl:w-40',
|
||||
'[&_.label]:md:w-44 [&_.label]:xl:w-52',
|
||||
|
||||
@@ -7,6 +7,8 @@ import { useFieldError } from 'vee-validate'
|
||||
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
|
||||
type InputType = 'text' | 'number' | 'password' | 'email' | 'date' | 'time' | 'datetime-local' | 'search' | 'tel'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
placeholder: string
|
||||
@@ -22,8 +24,11 @@ const props = defineProps<{
|
||||
bottomLabel?: string
|
||||
suffixMsg?: string
|
||||
iconName?: string
|
||||
inputType?: InputType
|
||||
}>()
|
||||
|
||||
const { class: containerClass } = props
|
||||
|
||||
function handleInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement
|
||||
let value = target.value
|
||||
@@ -69,23 +74,21 @@ const hasError = computed(() => !!fieldError.value)
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl class="relative">
|
||||
<FormControl :class="cn('relative', containerClass)">
|
||||
<div class="relative w-full max-w-sm items-center">
|
||||
<Input
|
||||
:disabled="isDisabled"
|
||||
v-bind="componentField"
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxLength"
|
||||
:class="cn(
|
||||
'focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0',
|
||||
hasError && 'border-red-500 focus:border-red-500 focus:ring-red-500'
|
||||
)"
|
||||
:class="cn(hasError && 'border-red-500 focus-visible:border-red-500 focus-visible:ring-red-500')"
|
||||
autocomplete="off"
|
||||
aria-autocomplete="none"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
@input="handleInput"
|
||||
:type="inputType"
|
||||
/>
|
||||
<span
|
||||
v-if="suffixMsg"
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
||||
import Field from '~/components/pub/my-ui/form/field.vue'
|
||||
import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import { Input } from '~/components/pub/ui/input'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
@@ -19,8 +15,11 @@ const props = defineProps<{
|
||||
maxLength?: number
|
||||
isRequired?: boolean
|
||||
isDisabled?: boolean
|
||||
resize?: 'none' | 'y' | 'x'
|
||||
rows?: number
|
||||
}>()
|
||||
|
||||
const { resize = 'none', class: className } = props
|
||||
function handleInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement
|
||||
let value = target.value
|
||||
@@ -47,7 +46,7 @@ function handleInput(event: Event) {
|
||||
<template>
|
||||
<DE.Cell :col-span="colSpan || 1">
|
||||
<DE.Label
|
||||
class="font-medium mb-1"
|
||||
:class="className"
|
||||
v-if="label !== ''"
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired && !isDisabled"
|
||||
@@ -65,11 +64,12 @@ function handleInput(event: Event) {
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
:rows="rows"
|
||||
:disabled="isDisabled"
|
||||
v-bind="componentField"
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxLength"
|
||||
:class="cn('focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0', props.class)"
|
||||
:class="cn('focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0')"
|
||||
autocomplete="off"
|
||||
aria-autocomplete="none"
|
||||
autocorrect="off"
|
||||
@@ -83,4 +83,4 @@ function handleInput(event: Event) {
|
||||
</FormField>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -4,10 +4,6 @@ import { type EncounterItem } from "~/handlers/encounter-init.handler";
|
||||
const props = defineProps<{
|
||||
initialActiveMenu: string
|
||||
data: EncounterItem[]
|
||||
canCreate?: boolean
|
||||
canRead?: boolean
|
||||
canUpdate?: boolean
|
||||
canDelete?: boolean
|
||||
}>()
|
||||
|
||||
const activeMenu = ref(props.initialActiveMenu)
|
||||
@@ -42,12 +38,7 @@ function changeMenu(value: string) {
|
||||
class="flex-1 rounded-md border bg-white p-4 shadow-sm dark:bg-neutral-950">
|
||||
<component
|
||||
:is="data.find((m) => m.id === activeMenu)?.component"
|
||||
v-bind="data.find((m) => m.id === activeMenu)?.props"
|
||||
:can-create="canCreate"
|
||||
:can-read="canRead"
|
||||
:can-update="canUpdate"
|
||||
:can-delete="canDelete"
|
||||
/>
|
||||
v-bind="data.find((m) => m.id === activeMenu)?.props" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@ function getButtonClass(pageNumber: number) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full min-w-0 items-center justify-between px-2 py-2">
|
||||
<div class="flex w-full min-w-0 items-center justify-between overflow-x-scroll px-2 py-2 md:overflow-hidden">
|
||||
<!-- Info text -->
|
||||
<div
|
||||
v-if="showInfo && endRecord > 0"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ErrorMessage } from 'vee-validate'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
class?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ErrorMessage
|
||||
:name="props.name"
|
||||
v-slot="{ message }"
|
||||
>
|
||||
<p :class="cn('font-sans text-[0.8rem] text-destructive', props.class)">
|
||||
{{ message }}
|
||||
</p>
|
||||
</ErrorMessage>
|
||||
</template>
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as ArrayMessage } from './array-message.vue'
|
||||
export { default as FormControl } from './FormControl.vue'
|
||||
export { default as FormDescription } from './FormDescription.vue'
|
||||
export { default as FormItem } from './FormItem.vue'
|
||||
|
||||
@@ -24,7 +24,7 @@ const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
v-model="modelValue"
|
||||
:class="
|
||||
cn(
|
||||
'border-input dark:bg-slate-950 ring-offset-background placeholder:text-muted-foreground flex h-9 md:h-8 2xl:h-9 w-full rounded-md border border-gray-400 px-3 py-2 md:text-xs 2xl:text-sm file:border-0 file:bg-transparent md:file:!text-xs xl:file:!text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'border-input dark:bg-slate-950 ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary focus-visible:ring-offset-0 focus-visible:border-primary flex h-9 md:h-8 2xl:h-9 w-full rounded-md border border-gray-400 px-3 py-2 md:text-xs 2xl:text-sm file:border-0 file:bg-transparent md:file:!text-xs xl:file:!text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
|
||||
Reference in New Issue
Block a user