Merge branch 'dev' into feat/micro-lab-order-50

This commit is contained in:
Andrian Roshandy
2025-12-03 10:16:45 +07:00
152 changed files with 9865 additions and 374 deletions
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { cn } from '~/lib/utils'
import { type Item } from './index'
const props = defineProps<{
@@ -14,6 +13,8 @@ const props = defineProps<{
isDisabled?: boolean
}>()
const model = defineModel()
const emit = defineEmits<{
'update:modelValue': [value: string]
'update:searchText': [value: string]
@@ -57,6 +58,7 @@ const searchableItems = computed(() => {
})
function onSelect(item: Item) {
model.value = item.value
emit('update:modelValue', item.value)
open.value = false
}
@@ -0,0 +1,77 @@
<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 activeKey = ref<string | null>(null)
const linkItems: LinkItem[] = [
{
label: 'Detail',
onClick: () => {
detail()
},
icon: 'i-lucide-eye',
},
{
label: 'Print',
onClick: () => {
print()
},
icon: 'i-lucide-printer',
},
]
function detail() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
}
function print() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showPrint
recItem.value = props.rec
}
</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>
@@ -0,0 +1,106 @@
<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<any>>('timestamp')!
const activeKey = ref<string | null>(null)
const linkItems: LinkItem[] = [
{
label: 'Detail',
onClick: () => {
detail()
},
icon: 'i-lucide-eye',
},
{
label: 'Edit',
onClick: () => {
edit()
},
icon: 'i-lucide-pencil',
},
{
label: 'Verifikasi',
onClick: () => {
verify()
},
icon: 'i-lucide-shield-check',
},
{
label: 'Print',
onClick: () => {
print()
},
icon: 'i-lucide-printer',
},
]
function detail() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
}
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
function verify() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmVerify
recItem.value = props.rec
timestamp.value = new Date().getTime()
}
function print() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showPrint
recItem.value = props.rec
}
</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>
@@ -0,0 +1,64 @@
<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 activeKey = ref<string | null>(null)
const linkItems: LinkItem[] = [
{
label: 'Process',
onClick: () => {
process()
},
icon: 'i-lucide-arrow-right',
},
]
function process() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showProcess
recItem.value = props.rec
}
</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>
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { ActionEvents, type ListItemDto } from '~/components/pub/my-ui/data/types';
const props = defineProps<{
url: string
btnTxt?: string
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<any>>('timestamp')!
function handlePrint() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showPrint
recItem.value = props.rec
timestamp.value = new Date().toISOString()
}
</script>
<template>
<Button
class="gap-3 items-center border-orange-400 text-orange-400"
variant="outline" @click="handlePrint">
<Icon name="i-lucide-printer" class="h-4 w-4" />
{{ props.btnTxt || 'Preview' }}
</Button>
</template>
+2
View File
@@ -87,7 +87,9 @@ export const ActionEvents = {
showProcess: 'showProcess',
showCancel: 'showCancel',
showVerify: 'showVerify',
showConfirmVerify: 'showConfirmVerify',
showValidate: 'showValidate',
showConfirmVerify: 'showConfirmVerify',
showPrint: 'showPrint',
}
@@ -47,7 +47,7 @@ function handleInput(event: Event) {
<template>
<DE.Cell :col-span="colSpan || 1">
<DE.Label
class="mb-1"
class="font-medium mb-1"
v-if="label !== ''"
:label-for="fieldName"
:is-required="isRequired && !isDisabled"
@@ -69,7 +69,7 @@ function handleInput(event: Event) {
v-bind="componentField"
:placeholder="placeholder"
:maxlength="maxLength"
:class="cn('focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0')"
:class="cn('focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0', props.class)"
autocomplete="off"
aria-autocomplete="none"
autocorrect="off"
@@ -1,13 +1,17 @@
<script setup lang="ts">
defineProps<{
import { cn } from '~/lib/utils';
const props = defineProps<{
label: string
class?: string
labelClass?: string
}>()
</script>
<template>
<div class="flex flex-col gap-1 lg:grid lg:grid-cols-[180px_minmax(0,1fr)] lg:gap-x-3">
<div :class="cn(`flex flex-col gap-1 lg:grid lg:grid-cols-[180px_minmax(0,1fr)] lg:gap-x-3`, props.class)">
<!-- Label -->
<span class="text-md font-normal text-muted-foreground">
<span :class="cn(`text-md font-normal text-muted-foreground`, props.labelClass)">
{{ label }}
</span>
+5 -8
View File
@@ -45,15 +45,12 @@ const isOpen = computed({
<template>
<Dialog v-model:open="isOpen">
<DialogContent
:class="sizeClass"
@interact-outside="(e: any) => preventOutside && e.preventDefault()"
@pointer-down-outside="(e: any) => preventOutside && e.preventDefault()"
>
<DialogContent :class="sizeClass" @interact-outside="(e: any) => preventOutside && e.preventDefault()"
@pointer-down-outside="(e: any) => preventOutside && e.preventDefault()">
<DialogHeader>
<DialogTitle :class="`text-sm 2xl:text-base font-semibold flex ${titleClass || ''}`">
<div v-if="props.titleIcon" class="me-2 pt-0.5">
<Icon :name="props.titleIcon" :class="`!pt-2`" />
<div class="me-2 pt-0.5">
<Icon v-if="props.titleIcon" :name="props.titleIcon" :class="`!pt-2`" />
</div>
<div>
{{ props.title }}
@@ -66,4 +63,4 @@ const isOpen = computed({
</DialogDescription>
</DialogContent>
</Dialog>
</template>
</template>
@@ -26,7 +26,7 @@ function onClick(type: ClickType) {
<div :class="`${defaultClass} ${additionalClass} ${btnClass}`">
<div>
<Button variant="ghost" type="button" @click="onClick('back')">
<Icon name="i-lucide-arrow-left" />
<Icon name="i-lucide-arrow-left" />
Back
</Button>
</div>
@@ -38,7 +38,7 @@ function onClick(type: ClickType) {
</div>
<div>
<Button type="button" @click="onClick('submit')">
<Icon name="i-lucide-check" />
<Icon name="i-lucide-check" />
Submit
</Button>
</div>
@@ -50,8 +50,11 @@ const value = ref({
end: todayCalendar,
}) as Ref<DateRange>
function onInput(event: Event) {
props.refSearchNav?.onInput((event.target as HTMLInputElement).value)
}
function onFilterClick() {
console.log('Search:', searchQuery.value)
console.log('Date Range:', dateRange.value)
props.refSearchNav?.onClick()
}
@@ -60,9 +63,11 @@ function onFilterClick() {
<template>
<header>
<div class="flex items-center gap-2 mb-4 2xl:mb-5">
<div class="relative w-64">
<div v-if="refSearchNav" class="relative w-64">
<Search class="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-gray-400" />
<Input v-model="searchQuery" type="text" placeholder="Cari Nama /No.RM" class="pl-9" />
<Input v-model="searchQuery" @input="onInput"
type="text" placeholder="Cari .." class="pl-9" />
</div>
<Popover>
@@ -101,7 +106,7 @@ function onFilterClick() {
</Button>
<DropdownMenu v-show="props.enableExport">
<DropdownMenuTrigger as-child>
<DropdownMenuTrigger v-show="props.enableExport" as-child>
<Button variant="outline" class="ml-auto border-orange-500 text-orange-600 hover:bg-orange-50">
<Icon name="i-lucide-download" class="h-4 w-4" />
Ekspor