fix: resolve conflict in lib date

This commit is contained in:
riefive
2025-11-17 17:08:58 +07:00
57 changed files with 2807 additions and 50 deletions
@@ -0,0 +1,27 @@
<script setup lang="ts">
import { cn } from '~/lib/utils';
const props = withDefaults(defineProps<{
text?: string
description?: string | string[]
class?: string
}>(), {
})
</script>
<template>
<div :class="cn('flex items-center gap-4 p-3 rounded-md text-orange-500 border border-orange-400 bg-orange-50',
props.class
)">
<Icon name="i-lucide-triangle-alert" class="h-12 w-12 align-middle transition-colors" />
<div class="">
<p class="font-medium text-base">{{text}}</p>
<ul class="list-disc list-inside">
<li v-for="(desc, index) in (Array.isArray(description) ? description : [description])" :key="index">
{{ desc }}
</li>
</ul>
</div>
</div>
</template>
@@ -0,0 +1,26 @@
<script setup lang="ts">
import { Badge } from '~/components/pub/ui/badge'
import { activeStatusCodes } from '~/lib/constants';
const props = defineProps<{
rec: any
idx?: number
}>()
const statusText = computed(() => {
const code: keyof typeof activeStatusCodes = props.rec.status_code === 1 ? `active` : `inactive`
return activeStatusCodes[code]
})
const badgeVariant = computed(() => {
return props.rec.status_code === 1 ? 'default' : 'destructive'
})
</script>
<template>
<div class="flex justify-center">
<Badge :variant="badgeVariant" class="rounded-2xl text-[0.6rem]" >
{{ statusText }}
</Badge>
</div>
</template>
@@ -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'
@@ -71,7 +71,7 @@ function handleCancel() {
<Dialog v-model:open="isOpen" :title="title" :size="size">
<div class="space-y-4">
<!-- Icon dan pesan -->
<div class="flex items-start gap-3">
<div class="flex items-center gap-3">
<div :class="[variantClasses.icon, variantClasses.iconColor]" class="w-6 h-6 mt-1 flex-shrink-0" />
<div class="flex-1">
<p class="text-sm text-muted-foreground leading-relaxed">
@@ -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)"
+8 -1
View File
@@ -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'
@@ -41,17 +42,23 @@ export interface RefSearchNav {
onClear: () => void
}
export interface RefExportNav {
onExportPdf?: () => void
onExportCsv?: () => void
onExportExcel?: () => void
}
// prepared header for relatively common usage
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
+5 -1
View File
@@ -19,6 +19,8 @@ const props = defineProps<{
maxLength?: number
isRequired?: boolean
isDisabled?: boolean
rightLabel?: string
bottomLabel?: string
}>()
function handleInput(event: Event) {
@@ -61,7 +63,7 @@ function handleInput(event: Event) {
v-slot="{ componentField }"
:name="fieldName"
>
<FormItem>
<FormItem :class="`relative`">
<FormControl>
<Input
:disabled="isDisabled"
@@ -76,10 +78,12 @@ function handleInput(event: Event) {
spellcheck="false"
@input="handleInput"
/>
<p v-show="rightLabel" class="text-gray-400 absolute top-0 right-3">{{ rightLabel }}</p>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</DE.Field>
<p v-show="bottomLabel" class="text-gray-400">{{ bottomLabel }}</p>
</DE.Cell>
</template>
@@ -1,10 +1,12 @@
<script setup lang="ts">
const props = defineProps<{
enableDraft?: boolean
smallMode?: boolean
defaultClass?: string
class?: string
}>()
const enableDraft = props.enableDraft ?? true
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' : ''
@@ -29,7 +31,7 @@ function onClick(type: ClickType) {
</Button>
</div>
<div>
<Button variant="secondary" type="button" @click="onClick('draft')">
<Button v-show="enableDraft" variant="secondary" type="button" @click="onClick('draft')">
<Icon name="i-lucide-file" />
Draft
</Button>
@@ -0,0 +1,85 @@
<script setup lang="ts">
import { Calendar as CalendarIcon, Filter as FilterIcon, Search } from 'lucide-vue-next'
import { ref } from 'vue'
import type { Ref } from 'vue'
import type { DateRange } from 'radix-vue'
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date'
import { cn } from '~/lib/utils'
import type { HeaderPrep, RefExportNav, RefSearchNav } from '~/components/pub/my-ui/data/types'
const props = defineProps<{
prep: HeaderPrep
refSearchNav?: RefSearchNav
enableExport?: boolean
refExportNav?: RefExportNav
}>()
// function emitSearchNavClick() {
// props.refSearchNav?.onClick()
// }
//
// function onInput(event: Event) {
// props.refSearchNav?.onInput((event.target as HTMLInputElement).value)
// }
//
// function btnClick() {
// props.prep?.addNav?.onClick?.()
// }
const searchQuery = ref('')
const dateRange = ref<{ from: Date | null; to: Date | null }>({
from: new Date(),
to: new Date(),
})
const df = new DateFormatter('en-US', {
dateStyle: 'medium',
})
const value = ref({
start: new CalendarDate(2022, 1, 20),
end: new CalendarDate(2022, 1, 20).add({ days: 20 }),
}) as Ref<DateRange>
function onFilterClick() {
console.log('Search:', searchQuery.value)
console.log('Date Range:', dateRange.value)
props.refSearchNav?.onClick()
}
</script>
<template>
<header>
<div class="flex items-center gap-2 mb-4 2xl:mb-5">
<Button variant="outline" class="border-orange-500 text-orange-600 hover:bg-orange-50" @click="onFilterClick">
<FilterIcon class="mr-2 size-4" />
Filter
</Button>
<DropdownMenu v-show="props.enableExport">
<DropdownMenuTrigger 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
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem v-show="props.refExportNav?.onExportPdf"
@click="props.refExportNav?.onExportPdf">
Ekspor PDF
</DropdownMenuItem>
<DropdownMenuItem v-show="props.refExportNav?.onExportCsv"
@click="props.refExportNav?.onExportCsv">
Ekspor CSV
</DropdownMenuItem>
<DropdownMenuItem v-show="props.refExportNav?.onExportExcel"
@click="props.refExportNav?.onExportExcel">
Ekspor Excel
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
</template>
+28 -2
View File
@@ -5,11 +5,13 @@ import type { Ref } from 'vue'
import type { DateRange } from 'radix-vue'
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date'
import { cn } from '~/lib/utils'
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
import type { HeaderPrep, RefExportNav, RefSearchNav } from '~/components/pub/my-ui/data/types'
const props = defineProps<{
prep: HeaderPrep
refSearchNav?: RefSearchNav
enableExport?: boolean
refExportNav?: RefExportNav
}>()
// function emitSearchNavClick() {
@@ -57,7 +59,7 @@ function onFilterClick() {
<template>
<header>
<div class="flex items-center space-x-2 mb-4 2xl:mb-5">
<div class="flex items-center gap-2 mb-4 2xl:mb-5">
<div 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" />
@@ -97,6 +99,30 @@ function onFilterClick() {
<FilterIcon class="mr-2 size-4" />
Filter
</Button>
<DropdownMenu v-show="props.enableExport">
<DropdownMenuTrigger 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
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem v-show="props.refExportNav?.onExportPdf"
@click="props.refExportNav?.onExportPdf">
Ekspor PDF
</DropdownMenuItem>
<DropdownMenuItem v-show="props.refExportNav?.onExportCsv"
@click="props.refExportNav?.onExportCsv">
Ekspor CSV
</DropdownMenuItem>
<DropdownMenuItem v-show="props.refExportNav?.onExportExcel"
@click="props.refExportNav?.onExportExcel">
Ekspor Excel
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
</template>
+12 -3
View File
@@ -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
@@ -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>
+1 -1
View File
@@ -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',
+3 -3
View File
@@ -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',
},