- Replace SelectDPJP with SelectDoctor component - Update schema naming from ActionReport to TreatmentReport - Add doctor selection functionality to treatment report form - Improve form layout and field organization - Update related model imports to use single quotes - add fragment for better form grouping - cherry pick form field from another branch
166 lines
4.2 KiB
Vue
166 lines
4.2 KiB
Vue
<script setup lang="ts">
|
|
import { Button } from '~/components/pub/ui/button'
|
|
import { cn } from '~/lib/utils'
|
|
|
|
/**
|
|
* Button Action Component untuk form
|
|
* Support preset: add, delete, save, cancel
|
|
*/
|
|
interface Props {
|
|
/**
|
|
* Preset button type dengan styling bawaan
|
|
* - add: Button tambah (outline primary)
|
|
* - delete: Button hapus (ghost)
|
|
* - save: Button simpan (primary)
|
|
* - cancel: Button batal (secondary)
|
|
* - custom: Custom styling
|
|
*/
|
|
preset?: 'add' | 'delete' | 'save' | 'cancel' | 'custom'
|
|
|
|
/**
|
|
* Icon name (UnoCSS/Lucide)
|
|
* Default akan diset berdasarkan preset
|
|
*/
|
|
icon?: string
|
|
|
|
/**
|
|
* Button text
|
|
* Set ke empty string ('') untuk icon-only mode
|
|
*/
|
|
label?: string
|
|
|
|
/**
|
|
* Button title (tooltip)
|
|
* Wajib untuk icon-only buttons (accessibility)
|
|
*/
|
|
title?: string
|
|
|
|
/**
|
|
* Icon only mode (no label)
|
|
* Otomatis true jika label kosong
|
|
*/
|
|
iconOnly?: boolean
|
|
|
|
/**
|
|
* Button type
|
|
*/
|
|
type?: 'button' | 'submit' | 'reset'
|
|
|
|
/**
|
|
* Disabled state
|
|
*/
|
|
disabled?: boolean
|
|
|
|
/**
|
|
* Custom class untuk override styling
|
|
*/
|
|
class?: string
|
|
|
|
/**
|
|
* Responsive width (full width on mobile)
|
|
*/
|
|
fullWidthMobile?: boolean
|
|
|
|
/**
|
|
* Button size
|
|
*/
|
|
size?: 'default' | 'sm' | 'lg' | 'icon'
|
|
|
|
/**
|
|
* Button variant (override preset variant)
|
|
*/
|
|
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
preset: 'custom',
|
|
type: 'button',
|
|
disabled: false,
|
|
fullWidthMobile: false,
|
|
iconOnly: false,
|
|
})
|
|
|
|
// Preset configurations
|
|
const presetConfig = {
|
|
add: {
|
|
variant: 'outline' as const,
|
|
icon: 'i-lucide-plus',
|
|
label: 'Tambah',
|
|
classes:
|
|
'border-primary bg-white text-primary hover:bg-primary hover:text-white dark:bg-slate-800 dark:border-primary dark:text-primary dark:hover:bg-primary dark:hover:text-white',
|
|
},
|
|
delete: {
|
|
variant: 'ghost' as const,
|
|
icon: 'i-lucide-trash-2',
|
|
label: '', // Default kosong untuk icon-only
|
|
classes:
|
|
'hover:bg-destructive hover:text-white hover:border-destructive dark:hover:bg-destructive dark:hover:text-white',
|
|
},
|
|
save: {
|
|
variant: 'default' as const,
|
|
icon: 'i-lucide-save',
|
|
label: 'Simpan',
|
|
classes: 'bg-primary text-primary-foreground hover:bg-primary/90 dark:bg-primary dark:hover:bg-primary/90',
|
|
},
|
|
cancel: {
|
|
variant: 'secondary' as const,
|
|
icon: 'i-lucide-x',
|
|
label: 'Batal',
|
|
classes: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 dark:bg-slate-700 dark:hover:bg-slate-600',
|
|
},
|
|
custom: {
|
|
variant: 'default' as const,
|
|
icon: '',
|
|
label: '',
|
|
classes: '',
|
|
},
|
|
}
|
|
|
|
const currentPreset = computed(() => presetConfig[props.preset])
|
|
const buttonVariant = computed(() => props.variant || currentPreset.value.variant)
|
|
const buttonIcon = computed(() => props.icon || currentPreset.value.icon)
|
|
|
|
// Label handling: gunakan prop label jika ada, fallback ke preset, atau undefined jika iconOnly
|
|
const buttonLabel = computed(() => {
|
|
if (props.label !== undefined) return props.label
|
|
return currentPreset.value.label
|
|
})
|
|
|
|
const buttonTitle = computed(() => props.title || buttonLabel.value)
|
|
|
|
// Deteksi icon-only mode
|
|
const isIconOnly = computed(() => {
|
|
return props.iconOnly || buttonLabel.value === '' || !buttonLabel.value
|
|
})
|
|
|
|
const buttonClasses = computed(() => {
|
|
// Base classes berbeda untuk icon-only vs with-label
|
|
const baseClasses = isIconOnly.value
|
|
? 'rounded-md p-2 w-9 h-9 transition-colors flex items-center justify-center' // Icon only: square button
|
|
: 'rounded-md px-4 py-2 transition-colors sm:text-sm' // With label: padding horizontal lebih besar
|
|
|
|
const widthClasses = props.fullWidthMobile && !isIconOnly.value ? 'w-full sm:w-auto' : ''
|
|
const presetClasses = currentPreset.value.classes
|
|
|
|
return cn(baseClasses, widthClasses, presetClasses, props.class)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<Button
|
|
:title="buttonTitle"
|
|
:type="type"
|
|
:variant="buttonVariant"
|
|
:size="size"
|
|
:disabled="disabled"
|
|
:class="buttonClasses"
|
|
>
|
|
<Icon
|
|
v-if="buttonIcon"
|
|
:name="buttonIcon"
|
|
:class="cn('h-4 w-4 align-middle transition-colors', !isIconOnly ? 'mr-2' : '')"
|
|
/>
|
|
<slot v-if="!isIconOnly">{{ buttonLabel }}</slot>
|
|
</Button>
|
|
</template>
|