Merge branch 'dev' into feat/surat-kontrol-135
This commit is contained in:
@@ -16,22 +16,23 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
'update:searchText': [value: string]
|
||||
}>()
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
const searchText = ref('')
|
||||
const debouncedSearchText = refDebounced(searchText, 500) // 500ms debounce
|
||||
const selectedItem = computed(() => props.items.find((item) => item.value === props.modelValue))
|
||||
|
||||
const displayText = computed(() => {
|
||||
console.log(selectedItem)
|
||||
if (selectedItem.value?.label) {
|
||||
return selectedItem.value.label
|
||||
}
|
||||
return props.placeholder || 'Pilih item'
|
||||
})
|
||||
|
||||
watch(props, () => {
|
||||
console.log(props.modelValue)
|
||||
watch(debouncedSearchText, (newValue) => {
|
||||
emit('update:searchText', newValue)
|
||||
})
|
||||
|
||||
const searchableItems = computed(() => {
|
||||
@@ -106,6 +107,11 @@ function onSelect(item: Item) {
|
||||
class="h-9 border-0 border-b border-gray-200 bg-white text-black focus:ring-0 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
|
||||
:placeholder="searchPlaceholder || 'Cari...'"
|
||||
:aria-label="`Cari ${displayText}`"
|
||||
@input="
|
||||
(evt: any) => {
|
||||
searchText = evt?.target?.value || ''
|
||||
}
|
||||
"
|
||||
/>
|
||||
<CommandEmpty class="py-6 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ emptyMessage || 'Item tidak ditemukan.' }}
|
||||
|
||||
@@ -20,4 +20,17 @@ export function recStrToItem(input: Record<string, string>): Item[] {
|
||||
return items
|
||||
}
|
||||
|
||||
export function objectsToItems(input: object[], key = 'id', label = 'name'): Item[] {
|
||||
const items: Item[] = []
|
||||
for (const item of input) {
|
||||
if (item.hasOwnProperty(key) && item.hasOwnProperty(label)) {
|
||||
items.push({
|
||||
value: item[key as keyof typeof item], // the hasOwnProperty check should be enough
|
||||
label: item[label as keyof typeof item], // the hasOwnProperty check should be enough
|
||||
})
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
export { default as Combobox } from './combobox.vue'
|
||||
|
||||
@@ -2,5 +2,6 @@ export interface TabItem {
|
||||
value: string
|
||||
label: string
|
||||
component?: any
|
||||
groups?: string[]
|
||||
props?: Record<string, any>
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const selected = ref<any[]>([])
|
||||
function toggleSelection(row: any, event?: Event) {
|
||||
if (event) event.stopPropagation() // cegah event bubble ke TableRow
|
||||
|
||||
const isMultiple = props.selectMode === 'multi' || props.selectMode === 'multiple'
|
||||
const isMultiple = props.selectMode === 'multiple' // props.selectMode === 'multi' ||
|
||||
|
||||
// gunakan pembanding berdasarkan id atau stringify data
|
||||
const findIndex = selected.value.findIndex((r) => JSON.stringify(r) === JSON.stringify(row))
|
||||
@@ -128,7 +128,7 @@ function handleActionCellClick(event: Event, _cellRef: string) {
|
||||
'bg-green-50':
|
||||
props.selectMode === 'single' && selected.some((r) => JSON.stringify(r) === JSON.stringify(row)),
|
||||
'bg-blue-50':
|
||||
(props.selectMode === 'multi' || props.selectMode === 'multiple') &&
|
||||
(props.selectMode === 'multiple') && // props.selectMode === 'multi' ||
|
||||
selected.some((r) => JSON.stringify(r) === JSON.stringify(row)),
|
||||
}"
|
||||
@click="toggleSelection(row)"
|
||||
|
||||
@@ -2,9 +2,14 @@
|
||||
import type { LinkItem, ListItemDto } from './types'
|
||||
import { ActionEvents } from './types'
|
||||
|
||||
const props = defineProps<{
|
||||
interface Props {
|
||||
rec: ListItemDto
|
||||
}>()
|
||||
size?: 'default' | 'sm' | 'lg'
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 'lg',
|
||||
})
|
||||
|
||||
const recId = inject<Ref<number>>('rec_id')!
|
||||
const recAction = inject<Ref<string>>('rec_action')!
|
||||
@@ -58,7 +63,7 @@ function del() {
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
:size="size"
|
||||
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white dark:data-[state=open]:bg-slate-800"
|
||||
>
|
||||
<Icon
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, type Ref } from 'vue'
|
||||
// Components
|
||||
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||
|
||||
const props = defineProps<{
|
||||
rec: { id: string; name: string; menu: string }
|
||||
selected?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
// No emits needed - using provide/inject
|
||||
}>()
|
||||
|
||||
const record = props.rec || {}
|
||||
const recId = inject('rec_select_id') as Ref<number>
|
||||
const recMenu = inject('rec_select_menu') as Ref<string>
|
||||
const selected = computed(() => recId.value === Number(record.id) ? record.id : '')
|
||||
|
||||
function handleSelection(value: string) {
|
||||
if (value === record.id) {
|
||||
recId.value = Number(record.id) || 0
|
||||
recMenu.value = record.menu || ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RadioGroup
|
||||
:model-value="selected"
|
||||
@update:model-value="handleSelection"
|
||||
>
|
||||
<RadioGroupItem
|
||||
:id="record.id"
|
||||
:value="record.id"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</template>
|
||||
@@ -8,6 +8,7 @@ export interface ListItemDto {
|
||||
}
|
||||
|
||||
export type ComponentType = Component
|
||||
export type ComponentWithProps = { component: Component, props: Record<string, any> }
|
||||
|
||||
export interface ButtonNav {
|
||||
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
|
||||
@@ -51,13 +52,13 @@ export interface RefExportNav {
|
||||
export interface HeaderPrep {
|
||||
title?: string
|
||||
icon?: string
|
||||
components?: ComponentWithProps[]
|
||||
refSearchNav?: RefSearchNav
|
||||
quickSearchNav?: QuickSearchNav
|
||||
filterNav?: ButtonNav
|
||||
addNav?: ButtonNav
|
||||
printNav?: ButtonNav
|
||||
}
|
||||
|
||||
export interface KeyLabel {
|
||||
key: string
|
||||
label: string
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
(e: 'click'): void
|
||||
}>()
|
||||
|
||||
function onClick() {
|
||||
emit('click')
|
||||
}
|
||||
</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>
|
||||
</template>
|
||||
@@ -36,9 +36,18 @@ const df = new DateFormatter('en-US', {
|
||||
dateStyle: 'medium',
|
||||
})
|
||||
|
||||
// Get current date
|
||||
const today = new Date()
|
||||
const todayCalendar = new CalendarDate(today.getFullYear(), today.getMonth() + 1, today.getDate())
|
||||
|
||||
// Get date 1 month ago
|
||||
const oneMonthAgo = new Date(today)
|
||||
oneMonthAgo.setMonth(today.getMonth() - 1)
|
||||
const oneMonthAgoCalendar = new CalendarDate(oneMonthAgo.getFullYear(), oneMonthAgo.getMonth() + 1, oneMonthAgo.getDate())
|
||||
|
||||
const value = ref({
|
||||
start: new CalendarDate(2022, 1, 20),
|
||||
end: new CalendarDate(2022, 1, 20).add({ days: 20 }),
|
||||
start: oneMonthAgoCalendar,
|
||||
end: todayCalendar,
|
||||
}) as Ref<DateRange>
|
||||
|
||||
function onFilterClick() {
|
||||
|
||||
@@ -30,15 +30,24 @@ function btnClick() {
|
||||
<div class="flex items-center">
|
||||
<div class="font-semibold text-gray-900 md:text-base 2xl:text-lg">
|
||||
<Icon
|
||||
:name="props.prep.icon!"
|
||||
:name="prep.icon!"
|
||||
class="mr-2 align-middle md:size-6"
|
||||
/>
|
||||
{{ props.prep.title }}
|
||||
{{ prep.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div v-if="prep.components">
|
||||
<template v-for="cwp in prep.components">
|
||||
<component
|
||||
:is="cwp.component"
|
||||
class="mr-2"
|
||||
v-bind="cwp.props"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.refSearchNav"
|
||||
v-if="refSearchNav"
|
||||
class="text-lg text-gray-900"
|
||||
>
|
||||
<Input
|
||||
|
||||
@@ -4,6 +4,10 @@ import Pagination from './pagination.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
paginationMeta: PaginationMeta
|
||||
conf?: {
|
||||
showInfo: boolean
|
||||
showControl: boolean
|
||||
}
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -17,8 +21,10 @@ function handlePageChange(page: number) {
|
||||
|
||||
<template>
|
||||
<Pagination
|
||||
v-if="props.paginationMeta && props.paginationMeta.pageSize > 0"
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
v-if="props.paginationMeta && props.paginationMeta.pageSize > 0"
|
||||
:pagination-meta="paginationMeta"
|
||||
:show-info="conf?.showInfo"
|
||||
:show-control="conf?.showControl"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -15,11 +15,13 @@ interface Props {
|
||||
paginationMeta: PaginationMeta
|
||||
onPageChange?: (page: number) => void
|
||||
showInfo?: boolean
|
||||
showControl?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
onPageChange: undefined,
|
||||
showInfo: true,
|
||||
showControl: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -65,7 +67,7 @@ const formattedRecordCount = computed(() => {
|
||||
})
|
||||
|
||||
const startRecord = computed(() => {
|
||||
const start = ((props.paginationMeta.page - 1) * props.paginationMeta.pageSize) + 1
|
||||
const start = (props.paginationMeta.page - 1) * props.paginationMeta.pageSize + 1
|
||||
return Number(start).toLocaleString('id-ID')
|
||||
})
|
||||
|
||||
@@ -77,53 +79,95 @@ const endRecord = computed(() => {
|
||||
function getButtonClass(pageNumber: number) {
|
||||
const digits = pageNumber.toString().length
|
||||
|
||||
if (digits >= 4) { // 1000+ (1k+)
|
||||
if (digits >= 4) {
|
||||
// 1000+ (1k+)
|
||||
return 'h-9 px-4 min-w-12'
|
||||
} else if (digits === 3) { // 100-999
|
||||
} else if (digits === 3) {
|
||||
// 100-999
|
||||
return 'h-9 px-3 min-w-10'
|
||||
} else { // 1-99
|
||||
} else {
|
||||
// 1-99
|
||||
return 'w-9 h-9 p-0'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-between px-2 py-2 w-full min-w-0">
|
||||
<div class="flex w-full min-w-0 items-center justify-between px-2 py-2">
|
||||
<!-- Info text -->
|
||||
<div v-if="showInfo && endRecord > 0" class="text-sm text-muted-foreground shrink-0">
|
||||
Menampilkan {{ startRecord }}
|
||||
hingga {{ Number(endRecord).toLocaleString('id-ID') }}
|
||||
dari {{ formattedRecordCount }} data
|
||||
<div
|
||||
v-if="showInfo && endRecord > 0"
|
||||
class="shrink-0 text-sm text-muted-foreground"
|
||||
>
|
||||
Menampilkan {{ startRecord }} hingga {{ Number(endRecord).toLocaleString('id-ID') }} dari
|
||||
{{ formattedRecordCount }} data
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="shrink-0"
|
||||
>
|
||||
-
|
||||
</div>
|
||||
<div v-else class="shrink-0">-</div>
|
||||
|
||||
<!-- Spacer untuk memastikan ada ruang di tengah -->
|
||||
<div class="flex-1 min-w-4"></div>
|
||||
<div class="min-w-4 flex-1"></div>
|
||||
|
||||
<!-- Pagination controls -->
|
||||
<div class="shrink-0">
|
||||
<div
|
||||
v-if="showControl"
|
||||
class="shrink-0"
|
||||
>
|
||||
<Pagination
|
||||
v-slot="{ page }" :total="paginationMeta.recordCount" :sibling-count="1" :page="paginationMeta.page"
|
||||
:items-per-page="paginationMeta.pageSize" show-edges
|
||||
>
|
||||
<PaginationList v-slot="{ items }" class="flex items-center gap-1">
|
||||
<PaginationFirst :disabled="!paginationMeta.hasPrev" @click="handleFirst" />
|
||||
<PaginationPrev :disabled="!paginationMeta.hasPrev" @click="handlePrev" />
|
||||
v-slot="{ page }"
|
||||
:total="paginationMeta.recordCount"
|
||||
:sibling-count="1"
|
||||
:page="paginationMeta.page"
|
||||
:items-per-page="paginationMeta.pageSize"
|
||||
show-edges
|
||||
>
|
||||
<PaginationList
|
||||
v-slot="{ items }"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<PaginationFirst
|
||||
:disabled="!paginationMeta.hasPrev"
|
||||
@click="handleFirst"
|
||||
/>
|
||||
<PaginationPrev
|
||||
:disabled="!paginationMeta.hasPrev"
|
||||
@click="handlePrev"
|
||||
/>
|
||||
|
||||
<template v-for="(item, index) in items">
|
||||
<PaginationListItem
|
||||
v-if="item.type === 'page'" :key="index" :value="item.value" as-child
|
||||
v-if="item.type === 'page'"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
as-child
|
||||
@click="handlePageChange(item.value)"
|
||||
>
|
||||
<Button :class="getButtonClass(item.value)" :variant="item.value === page ? 'default' : 'outline'">
|
||||
>
|
||||
<Button
|
||||
:class="getButtonClass(item.value)"
|
||||
:variant="item.value === page ? 'default' : 'outline'"
|
||||
>
|
||||
{{ item.value }}
|
||||
</Button>
|
||||
</PaginationListItem>
|
||||
<PaginationEllipsis v-else :key="item.type" :index="index" />
|
||||
<PaginationEllipsis
|
||||
v-else
|
||||
:key="item.type"
|
||||
:index="index"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<PaginationNext :disabled="!paginationMeta.hasNext" @click="handleNext" />
|
||||
<PaginationLast :disabled="!paginationMeta.hasNext" @click="handleLast" />
|
||||
<PaginationNext
|
||||
:disabled="!paginationMeta.hasNext"
|
||||
@click="handleNext"
|
||||
/>
|
||||
<PaginationLast
|
||||
:disabled="!paginationMeta.hasNext"
|
||||
@click="handleLast"
|
||||
/>
|
||||
</PaginationList>
|
||||
</Pagination>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, inject } from "vue"
|
||||
// import Toggle from '~/components/pub/ui/toggle/Toggle.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
label: string,
|
||||
providedKey?: string,
|
||||
variant?: 'default' | 'outline' | null | undefined
|
||||
}>()
|
||||
|
||||
const model = defineModel<boolean>()
|
||||
const provideKey = props.providedKey || 'toggle-provide'
|
||||
const { updateProvidedVal } = inject(provideKey)
|
||||
|
||||
watch(model, (newVal) => {
|
||||
updateProvidedVal(newVal)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button
|
||||
@click="() => model = !model"
|
||||
:variant="model ? 'default' : 'outline'"
|
||||
>
|
||||
{{ label }}
|
||||
</Button>
|
||||
<!-- <Toggle
|
||||
v-model="xval"
|
||||
:variant="variant ?? 'default'"
|
||||
:aria-label="label"
|
||||
>
|
||||
</Toggle> -->
|
||||
<!-- {{ xval }} -->
|
||||
</template>
|
||||
@@ -19,7 +19,7 @@ export const buttonVariants = cva(
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'md:h8 2xl:h-9 px-4 py-2',
|
||||
default: 'md:h-8 2xl:h-9 px-4 py-2',
|
||||
xs: 'h-7 rounded px-2',
|
||||
sm: 'h-8 rounded-md px-3 text-xs',
|
||||
lg: 'h-10 rounded-md px-8',
|
||||
|
||||
@@ -4,16 +4,16 @@ import { cva } from 'class-variance-authority'
|
||||
export { default as Toggle } from './Toggle.vue'
|
||||
|
||||
export const toggleVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
|
||||
'inline-flex items-center justify-center rounded-md text-xs 2xl:text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-transparent',
|
||||
outline:
|
||||
'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||
'border border-slate-300 bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-3',
|
||||
default: 'md:h-8 2xl:h-9 px-3',
|
||||
sm: 'h-8 px-2',
|
||||
lg: 'h-10 px-3',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user