Merge branch 'dev' of github.com:dikstub-rssa/simrs-fe into feat/laporan-tindakan-185

This commit is contained in:
Khafid Prayoga
2025-12-05 08:35:15 +07:00
210 changed files with 9091 additions and 950 deletions
@@ -110,8 +110,8 @@ function handleActionCellClick(event: Event, _cellRef: string) {
<TableBody v-else-if="rows.length === 0">
<TableRow>
<TableCell
:colspan="keys.length"
class="py-8 text-center"
:colspan="keys.length + 1"
class="py-5 text-center"
>
<div class="flex items-center justify-center">
<Info class="size-5 text-muted-foreground" />
@@ -0,0 +1,59 @@
<script setup lang="ts">
const model = defineModel<string>()
const props = defineProps<{
class: string
defaultClass?: string
disabled?: boolean
width?: number
widthUnit?: string
}>()
const InputComp = defineAsyncComponent(() => import('~/components/pub/ui/input/Input.vue'))
const activeState = ref(false)
let defaultClass = props.defaultClass ?? 'h-8 xl:h-9'
let widthStyle = '';
if(props.width) {
widthStyle = `width: ${props.width}${props.widthUnit ?? 'px'};`
} else {
widthStyle = `width: 100%;`
}
const recId = inject<Ref<number>>('rec_id')!
const recAction = inject<Ref<string>>('rec_action')!
const recItem = inject<Ref<any>>('rec_item')!
watch(activeState, () => {
nextTick(() => {
if (asyncInputRef.value && typeof asyncInputRef.value.focus === 'function') {
asyncInputRef.value.focus()
}
})
// document.getElementById('editable-div')?.scrollIntoView({ behavior: 'smooth' })
InputComp.value.focus()
// console.log(inputComp.__defaults)
})
</script>
<template>
<div
v-if="!activeState || disabled" @click="() => activeState = true"
:class="`${defaultClass}`"
:style="widthStyle">
{{ model }}
</div>
<InputComp v-else
v-model="model"
:class="`${defaultClass}`"
:style="widthStyle"
@blur="() => activeState = false"
/>
<!-- {{ inputComp.value }} -->
<!-- <Input
v-else v-model="model" @blur="() => activeState = false"
:class="`${defaultClass}`"
:style="widthStyle"
autofocus
/> -->
</template>
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { ActionEvents, type LinkItem, type ListItemDto } from '~/components/pub/my-ui/data/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)
function process() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showProcess
recItem.value = props.rec
timestamp.value = new Date().getTime()
}
</script>
<template>
<Button @click="process" variant="outline"
class="text-orange-400 border-orange-400 bg-transparent">
Pilih
<Icon name="i-lucide-arrow-right" class="h-4 w-4 align-middle transition-colors" />
</Button>
</template>
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { ActionEvents, type LinkItem, type ListItemDto } from '~/components/pub/my-ui/data/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)
function process() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showProcess
recItem.value = props.rec
timestamp.value = new Date().getTime()
}
</script>
<template>
<Button @click="process" variant="outline"
class="text-orange-400 border-orange-400 bg-transparent">
Detail
<Icon name="i-lucide-search" class="h-4 w-4 align-middle transition-colors" />
</Button>
</template>
@@ -0,0 +1,31 @@
<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')!
function print() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showPrint
recItem.value = props.rec
timestamp.value = new Date().getTime()
}
</script>
<template>
<div>
<Button type="button" variant="outline"
class="text-orange-500 border border-orange-400"
@click="print">
<Icon name="i-lucide-printer" class="h-4 w-4 align-middle transition-colors" />
Preview
</Button>
</div>
</template>
@@ -0,0 +1,94 @@
<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: 'Edit',
onClick: () => {
edit()
},
icon: 'i-lucide-pencil',
},
{
label: 'Print',
onClick: () => {
print()
},
icon: 'i-lucide-printer',
},
{
label: 'Delete',
onClick: () => {
del()
},
icon: 'i-lucide-trash',
},
]
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
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
timestamp.value = new Date().getTime()
}
function del() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmDelete
recItem.value = props.rec
timestamp.value = new Date().getTime()
}
</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>
+1 -1
View File
@@ -75,6 +75,7 @@ export interface LinkItem {
icon?: string
href?: string // to cover the needs of stating full external origins full url
action?: string // for local paths
groups?: string[]
onClick?: (event: Event) => void
headerStatus?: boolean
}
@@ -89,7 +90,6 @@ export const ActionEvents = {
showVerify: 'showVerify',
showConfirmVerify: 'showConfirmVerify',
showValidate: 'showValidate',
showConfirmVerify: 'showConfirmVerify',
showPrint: 'showPrint',
}
+33 -1
View File
@@ -5,7 +5,7 @@ import { cn } from '~/lib/utils'
import * as DE from '~/components/pub/my-ui/doc-entry'
const props = defineProps<{
interface Props {
fieldName: string
label?: string
placeholder?: string
@@ -18,6 +18,19 @@ const props = defineProps<{
isRequired?: boolean
isDisabled?: boolean
icons?: string
}
const props = withDefaults(defineProps<Props>(), {
label: '',
placeholder: 'Choose file...',
maxSizeMb: 1,
isDisabled: false,
isRequired: false,
})
const emit = defineEmits<{
(e: 'update:modelValue', value: File | null): void
(e: 'fileSelected', file: File | null): void
}>()
const hintMsg = computed(() => {
@@ -32,7 +45,26 @@ async function onFileChange(event: Event, handleChange: (value: any) => void) {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file) {
handleChange(null)
emit('update:modelValue', null)
emit('fileSelected', null)
return
}
// Validate file size
const maxSizeBytes = props.maxSizeMb * 1024 * 1024
if (file.size > maxSizeBytes) {
console.warn(`File size exceeds ${props.maxSizeMb}MB limit`)
handleChange(null)
emit('update:modelValue', null)
emit('fileSelected', null)
return
}
handleChange(file)
emit('update:modelValue', file)
emit('fileSelected', file)
}
</script>
+11 -14
View File
@@ -1,25 +1,22 @@
<script setup lang="ts">
type ClickType = 'back'
const emit = defineEmits<{
(e: 'click'): void
(e: 'click', type: ClickType): void
}>()
function onClick() {
emit('click')
function onClick(type: ClickType) {
emit('click', type)
}
</script>
<template>
<div class="m-2 flex gap-2 px-2">
<Button
class="bg-gray-400"
type="button"
@click="onClick"
>
<Icon
name="i-lucide-arrow-left"
class="me-2 align-middle"
/>
Back
</Button>
<div>
<Button variant="ghost"@click="onClick('back')" >
<Icon name="i-lucide-arrow-left" class="" />
Back
</Button>
</div>
</div>
</template>
@@ -7,7 +7,7 @@ const props = defineProps<{
const defaultClass = props.defaultClass ?? 'm-2 flex gap-2 px-2'
const additionalClass = props.class ?? ''
const btnClass = props.smallMode ? '[&_button]:w-7 [&_button]:h-7 [&_button]:2xl:w-8 [&_button]:2xl:h-9 [&_button]:!p-0' : ''
const btnClass = props.smallMode ? '[&_button]:w-7 [&_button]:h-7 [&_button]:2xl:w-8 [&_button]:2xl:h-8 [&_button]:!p-0' : ''
type ClickType = 'cancel' | 'edit' | 'submit'