Merge branch 'dev' of https://github.com/dikstub-rssa/simrs-fe into feat/user
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Default settings for all files
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# For Markdown files, don't trim trailing whitespace (karena kadang dipakai untuk line break)
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# For JSON, YAML, and config files
|
||||
[*.{json,yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# For JS, TS, Vue files
|
||||
[*.{js,ts,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# For CSS, SCSS, PostCSS
|
||||
[*.{css,scss,pcss}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,6 +9,7 @@ RSSA - Front End
|
||||
|
||||
- [Vue Style Guide](https://vuejs.org/style-guide)
|
||||
- [Nuxt Style Guide](https://nuxt.com/docs/4.x/guide)
|
||||
- [Shadcn Vue @radix-ui](https://radix.shadcn-vue.com/)
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
+38
-40
@@ -21,9 +21,9 @@
|
||||
--muted: 210 25% 95%;
|
||||
--muted-foreground: 210 15% 50%;
|
||||
|
||||
/* Accent - Professional Blue */
|
||||
--accent: 210 100% 50%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
/* Accent - Neutral Gray */
|
||||
--accent: 210 40% 96%;
|
||||
--accent-foreground: 222.2 84% 4.9%;
|
||||
--destructive: 0 75% 55%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--border: 210 20% 88%;
|
||||
@@ -67,45 +67,43 @@
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
/* .dark { */
|
||||
/* --background: 210 25% 8%; */
|
||||
/* --foreground: 210 20% 95%; */
|
||||
/* --card: 210 25% 10%; */
|
||||
/* --card-foreground: 210 20% 95%; */
|
||||
/* --popover: 210 25% 10%; */
|
||||
/* --popover-foreground: 210 20% 95%; */
|
||||
/* --primary: 150 75% 45%; */
|
||||
/* --primary-foreground: 0 0% 100%; */
|
||||
/* --primary-hover: 150 75% 50%; */
|
||||
/* --secondary: 210 25% 15%; */
|
||||
/* --secondary-foreground: 210 20% 90%; */
|
||||
/* --muted: 210 25% 15%; */
|
||||
/* --muted-foreground: 210 15% 65%; */
|
||||
/* --accent: 210 100% 55%; */
|
||||
/* --accent-foreground: 0 0% 100%; */
|
||||
/* --destructive: 0 75% 60%; */
|
||||
/* --destructive-foreground: 0 0% 100%; */
|
||||
/* --border: 210 25% 20%; */
|
||||
/* --input: 210 25% 15%; */
|
||||
/* --ring: 150 75% 45%; */
|
||||
/* --success: 150 75% 50%; */
|
||||
/* --warning: 45 95% 65%; */
|
||||
/* --info: 210 100% 60%; */
|
||||
/* --gradient-primary: linear-gradient(135deg, hsl(150 75% 45%), hsl(150 75% 55%)); */
|
||||
/* --gradient-medical: linear-gradient(135deg, hsl(150 75% 45%), hsl(210 100% 55%)); */
|
||||
/* --gradient-subtle: linear-gradient(180deg, hsl(210 25% 8%), hsl(210 25% 12%)); */
|
||||
/* --sidebar-background: 240 5.9% 10%; */
|
||||
/* --sidebar-foreground: 240 4.8% 95.9%; */
|
||||
/* --sidebar-primary: 224.3 76.3% 48%; */
|
||||
/* --sidebar-primary-foreground: 0 0% 100%; */
|
||||
/* --sidebar-accent: 240 3.7% 15.9%; */
|
||||
/* --sidebar-accent-foreground: 240 4.8% 95.9%; */
|
||||
/* --sidebar-border: 240 3.7% 15.9%; */
|
||||
/* --sidebar-ring: 217.2 91.2% 59.8%; */
|
||||
}
|
||||
|
||||
/* .dark { */
|
||||
/* --background: 210 25% 8%; */
|
||||
/* --foreground: 210 20% 95%; */
|
||||
/* --card: 210 25% 10%; */
|
||||
/* --card-foreground: 210 20% 95%; */
|
||||
/* --popover: 210 25% 10%; */
|
||||
/* --popover-foreground: 210 20% 95%; */
|
||||
/* --primary: 150 75% 45%; */
|
||||
/* --primary-foreground: 0 0% 100%; */
|
||||
/* --primary-hover: 150 75% 50%; */
|
||||
/* --secondary: 210 25% 15%; */
|
||||
/* --secondary-foreground: 210 20% 90%; */
|
||||
/* --muted: 210 25% 15%; */
|
||||
/* --muted-foreground: 210 15% 65%; */
|
||||
/* --accent: 210 100% 55%; */
|
||||
/* --accent-foreground: 0 0% 100%; */
|
||||
/* --destructive: 0 75% 60%; */
|
||||
/* --destructive-foreground: 0 0% 100%; */
|
||||
/* --border: 210 25% 20%; */
|
||||
/* --input: 210 25% 15%; */
|
||||
/* --ring: 150 75% 45%; */
|
||||
/* --success: 150 75% 50%; */
|
||||
/* --warning: 45 95% 65%; */
|
||||
/* --info: 210 100% 60%; */
|
||||
/* --gradient-primary: linear-gradient(135deg, hsl(150 75% 45%), hsl(150 75% 55%)); */
|
||||
/* --gradient-medical: linear-gradient(135deg, hsl(150 75% 45%), hsl(210 100% 55%)); */
|
||||
/* --gradient-subtle: linear-gradient(180deg, hsl(210 25% 8%), hsl(210 25% 12%)); */
|
||||
/* --sidebar-background: 240 5.9% 10%; */
|
||||
/* --sidebar-foreground: 240 4.8% 95.9%; */
|
||||
/* --sidebar-primary: 224.3 76.3% 48%; */
|
||||
/* --sidebar-primary-foreground: 0 0% 100%; */
|
||||
/* --sidebar-accent: 240 3.7% 15.9%; */
|
||||
/* --sidebar-accent-foreground: 240 4.8% 95.9%; */
|
||||
/* --sidebar-border: 240 3.7% 15.9%; */
|
||||
/* --sidebar-ring: 217.2 91.2% 59.8%; */
|
||||
/* } */
|
||||
|
||||
/* Keyframes for Animations */
|
||||
@keyframes accordion-down {
|
||||
from {
|
||||
|
||||
@@ -11,8 +11,9 @@ import { defineAsyncComponent } from 'vue'
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
|
||||
const statusBadge = defineAsyncComponent(() => import('./status-badge.vue'))
|
||||
|
||||
const doctorStatus = {
|
||||
const _doctorStatus = {
|
||||
0: 'Tidak Aktif',
|
||||
1: 'Aktif',
|
||||
}
|
||||
@@ -26,10 +27,10 @@ export const cols: Col[] = [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{ width: 120 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 50 },
|
||||
{},
|
||||
{},
|
||||
]
|
||||
|
||||
export const header: Th[][] = [
|
||||
@@ -43,6 +44,7 @@ export const header: Th[][] = [
|
||||
{ label: 'Fee Ranap' },
|
||||
{ label: 'Fee Rajal' },
|
||||
{ label: 'Status' },
|
||||
{ label: '' },
|
||||
],
|
||||
]
|
||||
|
||||
@@ -66,7 +68,6 @@ export const delKeyNames: KeyLabel[] = [
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
name: (rec: unknown): unknown => {
|
||||
console.log(rec)
|
||||
const recX = rec as SmallDetailDto
|
||||
return `${recX.frontTitle} ${recX.name} ${recX.endTitle}`.trim()
|
||||
},
|
||||
@@ -85,10 +86,6 @@ export const funcParsed: RecStrFuncUnknown = {
|
||||
const recX = rec as SmallDetailDto
|
||||
return Number(recX.outPatient_itemPrice.price).toLocaleString('id-ID')
|
||||
},
|
||||
status: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return doctorStatus[recX.status_code as keyof typeof doctorStatus]
|
||||
},
|
||||
}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
@@ -100,6 +97,14 @@ export const funcComponent: RecStrFuncComponent = {
|
||||
}
|
||||
return res
|
||||
},
|
||||
status(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: statusBadge,
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { Badge } from '~/components/pub/ui/badge'
|
||||
|
||||
const props = defineProps<{
|
||||
rec: any
|
||||
idx?: number
|
||||
}>()
|
||||
|
||||
const doctorStatus = {
|
||||
0: 'Tidak Aktif',
|
||||
1: 'Aktif',
|
||||
}
|
||||
|
||||
const statusText = computed(() => {
|
||||
return doctorStatus[props.rec.status_code as keyof typeof doctorStatus]
|
||||
})
|
||||
|
||||
const badgeVariant = computed(() => {
|
||||
return props.rec.status_code === 1 ? 'default' : 'destructive'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<Badge :variant="badgeVariant">
|
||||
{{ statusText }}
|
||||
</Badge>
|
||||
</div>
|
||||
</template>
|
||||
@@ -11,6 +11,7 @@ import { defineAsyncComponent } from 'vue'
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
|
||||
const statusBadge = defineAsyncComponent(() => import('./status-badge.vue'))
|
||||
|
||||
export const cols: Col[] = [
|
||||
{},
|
||||
@@ -110,6 +111,17 @@ export const funcComponent: RecStrFuncComponent = {
|
||||
}
|
||||
return res
|
||||
},
|
||||
status(rec, idx) {
|
||||
if (rec.status === null) {
|
||||
rec.status_code = 0
|
||||
}
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: statusBadge,
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { Badge } from '~/components/pub/ui/badge'
|
||||
|
||||
const props = defineProps<{
|
||||
rec: any
|
||||
idx?: number
|
||||
}>()
|
||||
|
||||
const doctorStatus = {
|
||||
0: 'Tidak Aktif',
|
||||
1: 'Aktif',
|
||||
}
|
||||
|
||||
const statusText = computed(() => {
|
||||
return doctorStatus[props.rec.status_code as keyof typeof doctorStatus]
|
||||
})
|
||||
|
||||
const badgeVariant = computed(() => {
|
||||
return props.rec.status_code === 1 ? 'default' : 'destructive'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<Badge :variant="badgeVariant">
|
||||
{{ statusText }}
|
||||
</Badge>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Col, KeyLabel, RecComponent, RecStrFuncComponent, RecStrFuncUnknown, Th } from '../../pub/nav/types'
|
||||
import type { Col, KeyLabel, RecComponent, RecStrFuncComponent, RecStrFuncUnknown, Th } from '~/components/pub/custom-ui/data/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/nav/dropdown-action-dud.vue'))
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const cols: Col[] = [
|
||||
{},
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
rec: any
|
||||
idx?: number
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col justify-center">
|
||||
<p class="font-semibold text-sm">{{ props.rec.patient.name }}</p>
|
||||
<p class="text-xs text-muted-foreground">{{ props.rec.patient.mrn }}</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import Badge from './badge.vue'
|
||||
import { rowStatus } from './list-cfg'
|
||||
|
||||
const props = defineProps<{
|
||||
rec: { status: number }
|
||||
idx?: number
|
||||
}>()
|
||||
|
||||
const variants = {
|
||||
0: 'error',
|
||||
1: 'warning',
|
||||
2: 'info',
|
||||
} as const
|
||||
|
||||
const statusText = computed(() => {
|
||||
return rowStatus[props.rec.status as keyof typeof rowStatus]
|
||||
})
|
||||
|
||||
const badgeStatus = computed((): 'error' | 'warning' | 'success' | 'info' => {
|
||||
return variants[props.rec.status as keyof typeof variants] ?? 'info'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex">
|
||||
<Badge :status="badgeStatus" :text="statusText" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
interface BadgeProps {
|
||||
status: 'success' | 'info' | 'warning' | 'error'
|
||||
text: string
|
||||
}
|
||||
|
||||
const props = defineProps<BadgeProps>()
|
||||
|
||||
const badgeVariants = cva(
|
||||
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
success: 'rounded-full border-transparent bg-green-500 text-white hover:bg-green-600',
|
||||
info: 'rounded-full border-transparent bg-blue-500 text-white hover:bg-blue-600',
|
||||
warning: 'rounded-full border-transparent bg-yellow-500 text-white hover:bg-yellow-600',
|
||||
error: 'rounded-full border-transparent bg-red-500 text-white hover:bg-red-600',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'info',
|
||||
},
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex">
|
||||
<div :class="cn(badgeVariants({ variant: props.status }))">
|
||||
{{ props.text }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
text: string
|
||||
icon: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button
|
||||
variant="outline" class="text-primary border-primary bg-white hover:bg-gray-50 focus:bg-gray-150
|
||||
focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
|
||||
>
|
||||
<Icon :name="props.icon" class="size-4 me-2" />
|
||||
{{ props.text }}
|
||||
</Button>
|
||||
</template>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import type { Summary } from '~/components/pub/base/summary-card/type'
|
||||
|
||||
const props = defineProps<{
|
||||
isLoading: boolean
|
||||
summaryData: Summary[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid gap-4 md:grid-cols-2 md:gap-8 lg:grid-cols-4">
|
||||
<template v-if="props.isLoading">
|
||||
<PubBaseSummaryCard v-for="n in 4" :key="n" is-skeleton :stat="summaryData[n]" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<PubBaseSummaryCard v-for="card in summaryData" :key="card.title" :stat="card" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import Block from '~/components/pub/form/block.vue'
|
||||
import FieldGroup from '~/components/pub/form/field-group.vue'
|
||||
import Field from '~/components/pub/form/field.vue'
|
||||
import Label from '~/components/pub/form/label.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="entry-form">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<Icon name="i-lucide-user" class="me-2" />
|
||||
<span class="font-semibold">Tambah</span> Pasien
|
||||
</div>
|
||||
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<Block>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nama</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nama</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup :column="3">
|
||||
<Label>Nomor RM</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label dynamic>Alamat</Label>
|
||||
<Field>
|
||||
<Input type="text" name="name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Block>
|
||||
</div>
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<PubNavFooterCsd />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { Col, KeyLabel, RecComponent, RecStrFuncComponent, RecStrFuncUnknown, Th } from '../../pub/nav/types'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
export const rowType = {
|
||||
1: 'Patient',
|
||||
2: 'Encounter',
|
||||
3: 'Observation',
|
||||
}
|
||||
|
||||
export const rowStatus = {
|
||||
0: 'Gagal',
|
||||
1: 'Pending',
|
||||
2: 'Terkirim',
|
||||
}
|
||||
|
||||
const patientBadge = defineAsyncComponent(() => import('./badge-patient.vue'))
|
||||
const statusBadge = defineAsyncComponent(() => import('./badge-status.vue'))
|
||||
|
||||
export const cols: Col[] = [
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
]
|
||||
|
||||
export const header: Th[][] = [
|
||||
[
|
||||
{ label: 'ID' },
|
||||
{ label: 'Jenis' },
|
||||
{ label: 'Pasien' },
|
||||
{ label: 'Status' },
|
||||
{ label: 'Terakhir Update' },
|
||||
{ label: 'FHIR ID' },
|
||||
],
|
||||
]
|
||||
|
||||
export const keys = ['id', 'resource_type', 'patient', 'status', 'updated_at', 'fhir_id']
|
||||
|
||||
export const delKeyNames: KeyLabel[] = [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
]
|
||||
|
||||
export const funcParsed: RecStrFuncUnknown = {
|
||||
name: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return `${recX.firstName} ${recX.middleName || ''} ${recX.lastName || ''}`
|
||||
},
|
||||
}
|
||||
|
||||
export const funcComponent: RecStrFuncComponent = {
|
||||
patient(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: patientBadge,
|
||||
}
|
||||
return res
|
||||
},
|
||||
status(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: statusBadge,
|
||||
}
|
||||
return res
|
||||
},
|
||||
}
|
||||
|
||||
export const funcHtml: RecStrFuncUnknown = {
|
||||
patient_address(_rec) {
|
||||
return '-'
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
|
||||
|
||||
defineProps<{
|
||||
data: any[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PubBaseDataTable
|
||||
:rows="data" :cols="cols" :header="header" :keys="keys" :func-parsed="funcParsed"
|
||||
:func-html="funcHtml" :func-component="funcComponent"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import type { DateRange } from 'radix-vue'
|
||||
import type { Ref } from 'vue'
|
||||
import {
|
||||
CalendarDate,
|
||||
DateFormatter,
|
||||
getLocalTimeZone,
|
||||
} from '@internationalized/date'
|
||||
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
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>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
:class="cn(
|
||||
'w-[280px] justify-start text-left font-normal',
|
||||
!value && 'text-muted-foreground',
|
||||
)"
|
||||
>
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
<template v-if="value.start">
|
||||
<template v-if="value.end">
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{ df.format(value.end.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
Pick a date
|
||||
</template>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<RangeCalendar v-model="value" initial-focus :number-of-months="2" @update:start-value="(startDate) => value.start = startDate" />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { Search } from 'lucide-vue-next'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full max-w-sm items-center">
|
||||
<Input id="search" type="text" placeholder="Search..." class="pl-10" />
|
||||
<span class="absolute start-0 inset-y-0 flex items-center justify-center px-2">
|
||||
<Search class="size-6 text-muted-foreground" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Activity, CreditCard, DollarSign, Users, UserCheck, UsersRound, Calendar, Hospital } from 'lucide-vue-next'
|
||||
import { Calendar, Hospital, UserCheck, UsersRound } from 'lucide-vue-next'
|
||||
|
||||
const dataCard = ref({
|
||||
totalRevenue: 0,
|
||||
@@ -161,8 +161,11 @@ onMounted(() => {
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="grid cursor-pointer gap-8 md:grid-cols-4 md:gap-8">
|
||||
<Card v-for="item in linkItems" :key="item"
|
||||
class="border-primary hover:bg-primary my-2 h-32 border transition-colors duration-200 hover:bg-gray-200">
|
||||
<Card
|
||||
v-for="item in linkItems"
|
||||
:key="item.title"
|
||||
class="border-primary hover:bg-primary my-2 h-32 border transition-colors duration-200 hover:bg-gray-200"
|
||||
>
|
||||
<NuxtLink :to="item.link">
|
||||
<CardContent class="my-2 grid h-full grid-rows-2 place-items-center">
|
||||
<Icon :name="item.icon" class="text-primary h-9 w-[60px]" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { Summary } from '~/components/pub/base/summary-card.type'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/nav/types'
|
||||
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
|
||||
import type { Summary } from '~/components/pub/base/summary-card/type'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
|
||||
import { Calendar, Hospital, UserCheck, UsersRound } from 'lucide-vue-next'
|
||||
import SummaryCard from '~/components/pub/base/summary-card/summary-card.vue'
|
||||
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||
@@ -20,10 +21,11 @@ const refSearchNav: RefSearchNav = {
|
||||
}
|
||||
|
||||
// Loading state management
|
||||
const isLoading = reactive({
|
||||
const isLoading = reactive<DataTableLoader>({
|
||||
summary: false,
|
||||
table: false,
|
||||
isTableLoading: false,
|
||||
})
|
||||
|
||||
const recId = ref<number>(0)
|
||||
const recAction = ref<string>('')
|
||||
const recItem = ref<any>(null)
|
||||
@@ -84,11 +86,12 @@ async function getPatientSummary() {
|
||||
}
|
||||
|
||||
async function getPatientList() {
|
||||
isLoading.isTableLoading = true
|
||||
const resp = await xfetch('/api/v1/patient')
|
||||
console.log('data patient', resp)
|
||||
if (resp.success) {
|
||||
data.value = (resp.body as Record<string, any>).data
|
||||
}
|
||||
isLoading.isTableLoading = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -99,6 +102,7 @@ onMounted(() => {
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/nav/types'
|
||||
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
|
||||
import RehabSepProsedurList from '~/components/app/rehab/registration/sep-prosedur/list.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -21,9 +22,9 @@ const refSearchNav: RefSearchNav = {
|
||||
}
|
||||
|
||||
// Loading state management
|
||||
const isLoading = reactive({
|
||||
summary: false,
|
||||
table: false,
|
||||
const isLoading = reactive<DataTableLoader>({
|
||||
isTableLoading: false,
|
||||
|
||||
})
|
||||
const recId = ref<number>(0)
|
||||
const recAction = ref<string>('')
|
||||
@@ -40,7 +41,6 @@ const hreaderPrep: HeaderPrep = {
|
||||
|
||||
async function getPatientList() {
|
||||
const resp = await xfetch('/api/v1/patient')
|
||||
console.log('data patient', resp)
|
||||
if (resp.success) {
|
||||
data.value = (resp.body as Record<string, any>).data
|
||||
}
|
||||
@@ -53,12 +53,12 @@ onMounted(() => {
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PubNavHeaderPrep :prep="{ ...hreaderPrep }" :ref-search-nav="refSearchNav" />
|
||||
<Header :prep="{ ...hreaderPrep }" :ref-search-nav="refSearchNav" />
|
||||
<div class="my-4 flex flex-1 flex-col gap-4 md:gap-8">
|
||||
<RehabSepProsedurList :data="data" />
|
||||
</div>
|
||||
</template>
|
||||
/template>
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import type { ServiceStatus } from '~/components/pub/base/service-status/type'
|
||||
import type { Summary } from '~/components/pub/base/summary-card/type'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
|
||||
import { CircleCheckBig, CircleDashed, CircleX, Send } from 'lucide-vue-next'
|
||||
|
||||
export const tabs = [
|
||||
{
|
||||
value: 'all',
|
||||
label: 'Semua Resource',
|
||||
},
|
||||
{
|
||||
value: 'patient',
|
||||
label: 'Patient',
|
||||
},
|
||||
{
|
||||
value: 'encounter',
|
||||
label: 'Encounter',
|
||||
},
|
||||
{
|
||||
value: 'observation',
|
||||
label: 'Observation',
|
||||
},
|
||||
]
|
||||
|
||||
export const actions = [
|
||||
{
|
||||
value: 'export',
|
||||
label: 'Ekspor',
|
||||
icon: 'i-lucide-download',
|
||||
},
|
||||
]
|
||||
|
||||
// Status filter options
|
||||
export const statusOptions = [
|
||||
{ value: '0', label: 'Failed' },
|
||||
{ value: '1', label: 'Pending' },
|
||||
{ value: '2', label: 'Success' },
|
||||
]
|
||||
export const summaryData: Summary[] = [
|
||||
{
|
||||
title: 'Resource Terkirim',
|
||||
icon: Send,
|
||||
metric: 1245,
|
||||
trend: 0,
|
||||
timeframe: 'daily',
|
||||
},
|
||||
{
|
||||
title: 'Sync Success',
|
||||
icon: CircleCheckBig,
|
||||
metric: '97%',
|
||||
trend: 0,
|
||||
timeframe: 'daily',
|
||||
},
|
||||
{
|
||||
title: 'Pending Queue',
|
||||
icon: CircleDashed,
|
||||
metric: 32,
|
||||
trend: 0,
|
||||
timeframe: 'daily',
|
||||
},
|
||||
{
|
||||
title: 'Failed Items',
|
||||
icon: CircleX,
|
||||
metric: 10,
|
||||
trend: 0,
|
||||
timeframe: 'daily',
|
||||
},
|
||||
]
|
||||
|
||||
// SATUSEHAT Service integration
|
||||
export const service = reactive<ServiceStatus>({
|
||||
serviceName: 'SATUSEHAT',
|
||||
serviceDesc: 'SATUSEHAT - FHIR R4 Compliant',
|
||||
sessionActive: false,
|
||||
status: 'connecting',
|
||||
isSkeleton: false,
|
||||
})
|
||||
|
||||
export const headerPrep: HeaderPrep = {
|
||||
title: 'SATUSEHAT Integration',
|
||||
icon: 'i-lucide-box',
|
||||
addNav: {
|
||||
label: 'Kirim Resource',
|
||||
icon: 'i-lucide-send',
|
||||
// onClick: () => navigateTo('/patient/add'),
|
||||
},
|
||||
}
|
||||
|
||||
export const refSearchNav: RefSearchNav = {
|
||||
onClick: () => {
|
||||
// open filter modal
|
||||
},
|
||||
onInput: (_val: string) => {
|
||||
},
|
||||
onClear: () => {
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
<script setup lang="ts">
|
||||
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
|
||||
import { useUrlSearchParams } from '@vueuse/core'
|
||||
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
||||
import { actions, headerPrep, refSearchNav, service, summaryData, tabs } from './const'
|
||||
import { defaultQuery, querySchema, tabSwitcher } from './schema.query'
|
||||
|
||||
// State management
|
||||
const data = ref([])
|
||||
const recId = ref<number>(0)
|
||||
const recAction = ref<string>('')
|
||||
const recItem = ref<any>(null)
|
||||
const isLoading = reactive<DataTableLoader>({
|
||||
satusehatConn: true,
|
||||
isTableLoading: false,
|
||||
})
|
||||
const queryParams = useUrlSearchParams('history', {
|
||||
initialValue: defaultQuery,
|
||||
removeFalsyValues: true,
|
||||
})
|
||||
const params = computed(() => {
|
||||
const result = querySchema.safeParse(queryParams)
|
||||
return result.data
|
||||
})
|
||||
// Pagination state
|
||||
const pagination = ref({
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total_pages: 0,
|
||||
has_next: false,
|
||||
has_prev: false,
|
||||
})
|
||||
|
||||
// API function to fetch data
|
||||
async function fetchData() {
|
||||
try {
|
||||
isLoading.isTableLoading = true
|
||||
data.value = []
|
||||
const response = await xfetch('/api/v1/satusehat/list', 'POST', {
|
||||
resource_type: params.value?.resource_type,
|
||||
date_from: params.value?.date_from,
|
||||
date_to: params.value?.date_to,
|
||||
search: params.value?.q,
|
||||
page: params.value?.page,
|
||||
limit: params.value?.limit,
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
data.value = response.body.data
|
||||
pagination.value = response.body.meta
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error)
|
||||
data.value = []
|
||||
} finally {
|
||||
isLoading.isTableLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function callSatuSehat() {
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
service.status = 'connected'
|
||||
// service.status = 'error'
|
||||
service.sessionActive = true
|
||||
// service.sessionActive = false
|
||||
} finally {
|
||||
isLoading.satusehatConn = false
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize params from URL on mount
|
||||
onMounted(async () => {
|
||||
await callSatuSehat()
|
||||
await fetchData()
|
||||
})
|
||||
|
||||
// Watch for url param changed state and trigger refetch
|
||||
watch(params, () => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
// Sync active tab filter with queryparams
|
||||
// fallback and default to `all` filter
|
||||
const activeTabFilter = computed({
|
||||
get: () => {
|
||||
const result = tabSwitcher.parse(queryParams.resource_type)
|
||||
return result
|
||||
},
|
||||
set: (value) => {
|
||||
queryParams.resource_type = value
|
||||
queryParams.q = defaultQuery.q
|
||||
queryParams.page = defaultQuery.page
|
||||
queryParams.limit = defaultQuery.limit
|
||||
queryParams.date_from = defaultQuery.date_from
|
||||
queryParams.date_to = defaultQuery.date_to
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-md border p-4">
|
||||
<Header :prep="headerPrep" :ref-search-nav="refSearchNav" />
|
||||
<div class="my-4 flex flex-1 flex-col gap-3 md:gap-4">
|
||||
<PubBaseServiceStatus v-bind="service" />
|
||||
<AppSatusehatCardSummary :is-loading="isLoading.satusehatConn!" :summary-data="summaryData" />
|
||||
</div>
|
||||
<div class="rounded-md border p-4">
|
||||
<h2 class="text-md font-semibold py-2">FHIR Resource</h2>
|
||||
<Tabs v-model="activeTabFilter">
|
||||
<div class="scrollbar-hide overflow-x-auto flex gap-2 justify-between">
|
||||
<TabsList>
|
||||
<TabsTrigger
|
||||
v-for="tab in tabs" :key="tab.value" :value="tab.value"
|
||||
class="flex-shrink-0 px-4 py-2 text-sm font-medium data-[state=active]:bg-green-600 data-[state=inactive]:bg-gray-100 data-[state=active]:text-white data-[state=inactive]:text-gray-700"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div class="flex gap-2 items-center">
|
||||
|
||||
<!-- Search Input -->
|
||||
|
||||
<AppSatusehatPicker />
|
||||
<div class="relative w-full max-w-sm">
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger>
|
||||
<Input type="text" placeholder="Cari pasien..." class="pl-3 h-9" />
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader class="border-b-1">
|
||||
<DialogTitle class="pb-2">Pencarian</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form>
|
||||
<FormField v-slot="{ componentField }" name="username">
|
||||
<FormItem>
|
||||
<FormLabel>Pasien</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="nama pasien, id pasien" v-bind="componentField" />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField v-slot="{ componentField }" name="status">
|
||||
<FormItem>
|
||||
<FormLabel>Status</FormLabel>
|
||||
<FormControl>
|
||||
<Select v-bind="componentField">
|
||||
<SelectTrigger class="bg-white border border-gray-300">
|
||||
<SelectValue class="text-gray-400" placeholder="-- select item" />
|
||||
</SelectTrigger>
|
||||
<SelectContent class="bg-white ">
|
||||
<SelectItem value="0">
|
||||
Gagal
|
||||
</SelectItem>
|
||||
<SelectItem value="1">
|
||||
Pending
|
||||
</SelectItem>
|
||||
<SelectItem value="2">
|
||||
Terkirim
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField v-slot="{ componentField }" name="fhirId">
|
||||
<FormItem>
|
||||
<FormLabel>FHIR ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="fhir id" v-bind="componentField" />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Form>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline">
|
||||
Reset
|
||||
</Button>
|
||||
<Button>
|
||||
Apply
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<AppSatusehatButtonAction
|
||||
v-for="action in actions" :key="action.value" :icon="action.icon"
|
||||
:text="action.label"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<TabsContent v-for="tab in tabs" :key="`content-${tab.value}`" :value="tab.value">
|
||||
<div class="rounded-md border p-4">
|
||||
<AppSatusehatList v-if="!isLoading.satusehatConn" :data="data" />
|
||||
|
||||
<!-- Pagination -->
|
||||
<div
|
||||
v-if="!isLoading.satusehatConn && !isLoading.isTableLoading && pagination.total > 0"
|
||||
class="mt-4 flex justify-between items-center"
|
||||
>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
Menampilkan {{ ((pagination.page - 1) * pagination.limit) + 1 }} -
|
||||
{{ Math.min(pagination.page * pagination.limit, pagination.total) }}
|
||||
dari {{ pagination.total }} data
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button v-if="pagination.has_prev" variant="outline" size="sm" @click="queryParams.page--">
|
||||
Sebelumnya
|
||||
</Button>
|
||||
<Button v-if="pagination.has_next" variant="outline" size="sm" @click="queryParams.page++">
|
||||
Selanjutnya
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as z from 'zod'
|
||||
|
||||
const resourceTabEnum = z.enum(['all', 'patient', 'encounter', 'observation'])
|
||||
|
||||
export const tabSwitcher = resourceTabEnum.default('all').catch('all')
|
||||
export const querySchema = z.object({
|
||||
q: z.string().min(3).optional().catch(''),
|
||||
resource_type: tabSwitcher,
|
||||
date_from: z.string().optional().catch(''),
|
||||
date_to: z.string().optional().catch(''),
|
||||
page: z.coerce.number().int().min(1).default(1).catch(1),
|
||||
limit: z.coerce.number().int().min(1).max(20).default(10).catch(10),
|
||||
})
|
||||
|
||||
export const defaultQuery = {
|
||||
q: '',
|
||||
status: '',
|
||||
resource_type: 'all',
|
||||
date_from: '',
|
||||
date_to: '',
|
||||
page: 1,
|
||||
limit: 10,
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/nav/types'
|
||||
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
|
||||
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
|
||||
|
||||
const data = ref([])
|
||||
@@ -16,6 +17,10 @@ const refSearchNav: RefSearchNav = {
|
||||
},
|
||||
}
|
||||
|
||||
const isLoading = reactive<DataTableLoader>({
|
||||
isTableLoading: false,
|
||||
})
|
||||
|
||||
const recId = ref<number>(0)
|
||||
const recAction = ref<string>('')
|
||||
const recItem = ref<any>(null)
|
||||
@@ -29,13 +34,15 @@ const headerPrep: HeaderPrep = {
|
||||
},
|
||||
}
|
||||
|
||||
useAsyncData('getDoctor', () => xfetch('/api/v1/doctor'), { server: false, immediate: true })
|
||||
|
||||
async function getDoctorList() {
|
||||
isLoading.dataListLoading = true
|
||||
|
||||
const resp = await xfetch('/api/v1/doctor')
|
||||
if (resp.success) {
|
||||
data.value = (resp.body as Record<string, any>).data
|
||||
}
|
||||
|
||||
isLoading.dataListLoading = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -45,9 +52,14 @@ onMounted(() => {
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header :prep="{ ...headerPrep }" :ref-search-nav="refSearchNav" />
|
||||
<AppDoctorList :data="data" />
|
||||
<div class="rounded-md border p-4">
|
||||
<Header :prep="{ ...headerPrep }" :ref-search-nav="refSearchNav" />
|
||||
<div class="rounded-md border p-4">
|
||||
<AppDoctorList v-if="!isLoading.dataListLoading" :data="data" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -12,12 +12,12 @@ const teams: {
|
||||
logo: string
|
||||
plan: string
|
||||
}[] = [
|
||||
{
|
||||
name: 'SIMRS - RSSA',
|
||||
logo: '/rssa-logo.png',
|
||||
plan: 'Saiful Anwar Hospital',
|
||||
},
|
||||
]
|
||||
{
|
||||
name: 'SIMRS - RSSA',
|
||||
logo: '/rssa-logo.png',
|
||||
plan: 'Saiful Anwar Hospital',
|
||||
},
|
||||
]
|
||||
const sidebar = {
|
||||
collapsible: 'offcanvas', // 'offcanvas' | 'icon' | 'none'
|
||||
side: 'left', // 'left' | 'right'
|
||||
@@ -59,8 +59,10 @@ async function setMenu() {
|
||||
<SidebarGroupLabel>
|
||||
{{ navMenu.heading }}
|
||||
</SidebarGroupLabel>
|
||||
<component :is="resolveNavItemComponent(item)" v-for="(item, index) in navMenu.items" :key="index" :item="item"
|
||||
class="my-2 mb-2" />
|
||||
<component
|
||||
:is="resolveNavItemComponent(item)" v-for="(item, index) in navMenu.items" :key="index" :item="item"
|
||||
class="my-2 mb-2"
|
||||
/>
|
||||
</SidebarGroup>
|
||||
<template v-else>
|
||||
<div class="p-5">
|
||||
@@ -68,8 +70,10 @@ async function setMenu() {
|
||||
</div>
|
||||
</template>
|
||||
<SidebarGroup class="mt-auto">
|
||||
<component :is="resolveNavItemComponent(item)" v-for="(item, index) in navMenuBottom" :key="index" :item="item"
|
||||
size="sm" />
|
||||
<component
|
||||
:is="resolveNavItemComponent(item)" v-for="(item, index) in navMenuBottom" :key="index" :item="item"
|
||||
size="sm"
|
||||
/>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { SidebarMenuButtonVariants } from '~/components/comps-pub/ui/sidebar'
|
||||
import type { SidebarMenuButtonVariants } from '~/components/pub/ui/sidebar'
|
||||
import { useSidebar } from '~/components/pub/ui/sidebar'
|
||||
|
||||
withDefaults(
|
||||
|
||||
@@ -1,43 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
import type { DataTableLoader } from './type'
|
||||
import { Info } from 'lucide-vue-next'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '~/components/pub/ui/table'
|
||||
|
||||
defineProps<{
|
||||
rows: unknown[]
|
||||
cols: object
|
||||
header: object[]
|
||||
cols: any[]
|
||||
header: any[]
|
||||
keys: string[]
|
||||
funcParsed: object
|
||||
funcHtml: object
|
||||
funcComponent: object
|
||||
funcParsed: Record<string, (row: any) => any>
|
||||
funcHtml: Record<string, (row: any) => string>
|
||||
funcComponent: Record<string, (row: any, idx: number) => any>
|
||||
}>()
|
||||
|
||||
const loader = inject('table_data_loader') as DataTableLoader
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableHeader class="bg-gray-50">
|
||||
<TableRow>
|
||||
<TableHead
|
||||
v-for="(h, idx) in header[0]"
|
||||
:key="`head-${idx}`"
|
||||
v-for="(h, idx) in header[0]" :key="`head-${idx}`" class="border"
|
||||
:style="{ width: cols[idx]?.width ? `${cols[idx].width}px` : undefined }"
|
||||
>
|
||||
>
|
||||
{{ h.label }}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
<TableBody v-if="loader.isTableLoading">
|
||||
<!-- Loading state with 5 skeleton rows -->
|
||||
<TableRow v-for="n in 5" :key="`skeleton-${n}`">
|
||||
<TableCell v-for="(key, cellIndex) in keys" :key="`cell-skel-${n}-${cellIndex}`" class="border">
|
||||
<Skeleton class="bg-gray-100 animate-pulse text-muted-foreground w-full h-6" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
<TableBody v-else-if="rows.length === 0">
|
||||
<TableRow>
|
||||
<TableCell :colspan="keys.length" class="text-center py-8">
|
||||
<div class="flex items-center justify-center">
|
||||
<Info class="size-5 text-muted-foreground" />
|
||||
<span class="ml-2">Tidak ada data tersedia</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
<TableBody v-else>
|
||||
<TableRow v-for="(row, rowIndex) in rows" :key="`row-${rowIndex}`">
|
||||
<TableCell v-for="(key, cellIndex) in keys" :key="`cell-${rowIndex}-${cellIndex}`">
|
||||
<TableCell v-for="(key, cellIndex) in keys" :key="`cell-${rowIndex}-${cellIndex}`" class="border">
|
||||
<!-- If funcComponent has a renderer -->
|
||||
<component
|
||||
:is="funcComponent[key](row, rowIndex).component"
|
||||
v-if="funcComponent[key]"
|
||||
:is="funcComponent[key](row, rowIndex).component" v-if="funcComponent[key]"
|
||||
v-bind="funcComponent[key](row, rowIndex)"
|
||||
/>
|
||||
/>
|
||||
<!-- If funcParsed or funcHtml returns a value -->
|
||||
<template v-else>
|
||||
{{ funcParsed[key]?.(row) ?? funcHtml[key]?.(row) ?? row[key] }}
|
||||
<!-- Use v-html for funcHtml to render HTML content -->
|
||||
<div v-if="funcHtml[key]" v-html="funcHtml[key]?.(row)"></div>
|
||||
<!-- Use normal interpolation for funcParsed and regular data -->
|
||||
<template v-else>
|
||||
{{ funcParsed[key]?.(row) ?? (row as any)[key] }}
|
||||
</template>
|
||||
</template>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface DataTableLoader {
|
||||
isTableLoading: boolean
|
||||
[key: string]: boolean
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import type { ServiceStatus } from './type'
|
||||
import { Loader, Loader2 } from 'lucide-vue-next'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<ServiceStatus>()
|
||||
|
||||
const tokenStatus = computed((): string => {
|
||||
return props.sessionActive ? 'Valid' : 'Invalid'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card v-if="props.isSkeleton" class="py-6">
|
||||
<div class="flex gap-4 justify-between px-6">
|
||||
<div class="flex gap-2 items-center">
|
||||
<span class="bg-gray-100">
|
||||
<Skeleton class="bg-gray-100 w-6 h-6 sm:w-8 sm:h-8" />
|
||||
</span>
|
||||
<div>
|
||||
<Skeleton class="w-64 h-8 bg-gray-100 text-xs md:text-sm text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right flex flex-col items-end">
|
||||
<Skeleton class="w-32 h-2 bg-gray-100 text-xs md:text-md text-muted-foreground" />
|
||||
<Skeleton class="w-32 h-3 bg-gray-100 text-xs md:text-md font-bold" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card v-else class="py-6">
|
||||
<div class="flex gap-4 justify-between px-6">
|
||||
<div class="flex gap-2 items-center">
|
||||
<span
|
||||
:class="cn(' rounded-md w-12 h-12 flex items-center justify-center',
|
||||
{ 'bg-red-500': props.status === 'error' },
|
||||
{ 'bg-blue-500': props.status !== 'error' },
|
||||
)"
|
||||
>
|
||||
<Icon v-if="props.status === 'error'" name="i-lucide-cable" class="text-white w-6 h-6 sm:w-8 sm:h-8" />
|
||||
<Icon v-else name="i-lucide-bring-to-front" class="text-white w-6 h-6 sm:w-8 sm:h-8" />
|
||||
</span>
|
||||
<div>
|
||||
<p v-if="props.status === 'connected'" class="text-xs md:text-md font-bold">Koneksi {{ props.serviceName }}
|
||||
Aktif</p>
|
||||
<p v-if="props.status === 'connecting'" class="flex flex-row text-xs md:text-md font-bold">Menghubungkan ke
|
||||
API {{
|
||||
props.serviceName }}
|
||||
<Loader2 class="ml-2 h-4 w-4 animate-spin" />
|
||||
</p>
|
||||
<p v-if="props.status === 'error'" class="text-xs md:text-md font-bold">Koneksi ke API {{ props.serviceName
|
||||
}}
|
||||
Gagal</p>
|
||||
<p v-if="props.status === 'connected'" class="text-xs md:text-sm text-muted-foreground">Koneksi Terhubung ke
|
||||
API {{ props.serviceDesc }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right flex flex-col items-end">
|
||||
<p class="text-xs md:text-md text-muted-foreground">Session Token</p>
|
||||
<p v-if="props.status === 'connecting'">
|
||||
<Loader class="ml-2 h-4 w-4 animate-spin" />
|
||||
</p>
|
||||
<p
|
||||
v-else :class="cn('text-xs md:text-md font-bold',
|
||||
{ 'text-blue-500': props.sessionActive },
|
||||
{ 'text-red-500': !props.sessionActive },
|
||||
)"
|
||||
>{{ tokenStatus }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface ServiceStatus {
|
||||
serviceName: string
|
||||
serviceDesc: string
|
||||
sessionActive: boolean
|
||||
status: 'connected' | 'connecting' | 'error' | 'disconnected'
|
||||
isSkeleton?: boolean
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { Summary } from './summary-card.type'
|
||||
import type { Summary } from './type'
|
||||
import { ChevronDown, ChevronUp } from 'lucide-vue-next'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
@@ -41,7 +41,7 @@ const isTrending = computed<boolean>(() => (props.stat?.trend ?? 0) > 0)
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Skeleton class="mb-2 h-6 w-48 bg-gray-100 text-2xl" />
|
||||
<Skeleton class="h-4 w-64 bg-gray-100 text-xs font-medium" />
|
||||
<Skeleton v-if="props.stat?.trend" class="h-4 w-64 bg-gray-100 text-xs font-medium" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card v-else-if="props.stat && !props.isSkeleton" class="h-42">
|
||||
@@ -55,9 +55,9 @@ const isTrending = computed<boolean>(() => (props.stat?.trend ?? 0) > 0)
|
||||
</div>
|
||||
<p v-if="props.stat.trend !== 0" class="text-muted-foreground flex items-center gap-1 text-xs">
|
||||
<component
|
||||
:is="isTrending ? ChevronUp : ChevronDown"
|
||||
:is="isTrending ? ChevronUp : ChevronDown"
|
||||
:class="cn('h-4 w-4', { 'text-green-500': isTrending }, { 'text-red-500': !isTrending })"
|
||||
/>
|
||||
/>
|
||||
<span :class="cn('font-medium', { 'text-green-500': isTrending }, { 'text-red-500': !isTrending })">
|
||||
{{ props.stat.trend.toFixed(1) }}%
|
||||
<!-- {{ Math.abs(props.stat.trend).toFixed(1) }}% -->
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
export interface Summary {
|
||||
title: string
|
||||
icon: Component
|
||||
metric: number
|
||||
metric: number | string
|
||||
trend: number
|
||||
timeframe: 'yearly' | 'monthly' | 'weekly' | 'daily'
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { HeaderPrep, RefSearchNav } from '../types.ts'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
|
||||
|
||||
const props = defineProps<{
|
||||
prep: HeaderPrep
|
||||
@@ -11,8 +11,8 @@ const props = defineProps<{
|
||||
<header>
|
||||
<div class="flex items-center">
|
||||
<div class="ml-3 text-lg font-bold text-gray-900">
|
||||
<Icon :name="prep.icon" class="mr-2 h-4 w-4 align-middle" />
|
||||
{{ prep.title }}
|
||||
<Icon :name="props.prep.icon!" class="mr-2 h-4 w-4 align-middle" />
|
||||
{{ props.prep.title }}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import type { HeaderPrep, RefSearchNav } from '../types.ts'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
|
||||
|
||||
const props = defineProps<{
|
||||
prep: HeaderPrep
|
||||
refSearchNav: RefSearchNav
|
||||
refSearchNav?: RefSearchNav
|
||||
}>()
|
||||
|
||||
function emitSearchNavClick() {
|
||||
props.refSearchNav.onClick()
|
||||
props.refSearchNav?.onClick()
|
||||
}
|
||||
|
||||
function onInput(event: Event) {
|
||||
props.refSearchNav.onInput((event.target as HTMLInputElement).value)
|
||||
props.refSearchNav?.onInput((event.target as HTMLInputElement).value)
|
||||
}
|
||||
|
||||
function btnClick() {
|
||||
props.prep.addNav?.onClick()
|
||||
props.prep?.addNav?.onClick?.()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -24,19 +24,17 @@ function btnClick() {
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="ml-3 text-lg font-bold text-gray-900">
|
||||
<Icon :name="prep.icon" class="mr-2 h-4 w-4 align-middle" />
|
||||
{{ prep.title }}
|
||||
<Icon :name="props.prep.icon!" class="mr-2 size-4 md:size-6 align-middle" />
|
||||
{{ props.prep.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="ml-3 text-lg text-gray-900">
|
||||
<div v-if="props.refSearchNav" class="ml-3 text-lg text-gray-900">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
class="w-full rounded-md border bg-white px-4 py-2 text-gray-900 sm:text-sm"
|
||||
@click="emitSearchNavClick"
|
||||
type="text" placeholder="Search"
|
||||
class="w-full rounded-md border bg-white px-4 py-2 text-gray-900 sm:text-sm" @click="emitSearchNavClick"
|
||||
@input="onInput"
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
<div v-if="prep.addNav" class="m-2 flex items-center">
|
||||
<Button size="md" class="rounded-md border border-gray-300 px-4 py-2 text-white sm:text-sm" @click="btnClick">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { HeaderPrep, RefSearchNav } from '../types.ts'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
|
||||
|
||||
const props = defineProps<{
|
||||
prep: HeaderPrep
|
||||
@@ -15,7 +15,7 @@ function onInput(event: Event) {
|
||||
}
|
||||
|
||||
function btnClick() {
|
||||
props.prep.addNav?.onClick()
|
||||
props.prep?.addNav?.onClick?.()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { AccordionContent } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { AccordionItem, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<AlertDialogContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<AlertDialogContentEmits>()
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<AlertDialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -5,7 +5,6 @@ import { AlertDialogTitle } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<AlertDialogTitleProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { HTMLAttributes } from 'vue'
|
||||
import { MoreHorizontal } from 'lucide-vue-next'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { HTMLAttributes } from 'vue'
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = withDefaults(defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
as: 'a',
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { HTMLAttributes } from 'vue'
|
||||
import { ChevronRight } from 'lucide-vue-next'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from 'reka-ui'
|
||||
import type { PrimitiveProps } from 'radix-vue'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { ButtonVariants } from '.'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { Primitive } from 'radix-vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
import { buttonVariants } from '.'
|
||||
|
||||
@@ -19,7 +19,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
data-slot="button"
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||
|
||||
@@ -4,27 +4,26 @@ import { cva } from 'class-variance-authority'
|
||||
export { default as Button } from './Button.vue'
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
||||
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
||||
outline:
|
||||
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
||||
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
||||
ghost:
|
||||
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
||||
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
||||
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
||||
icon: 'size-9',
|
||||
default: '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',
|
||||
icon: 'h-9 w-9',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { CalendarCell, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<CalendarCellProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { CalendarGrid, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<CalendarGridProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { CalendarGridRow, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<CalendarGridRowProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { CalendarHeadCell, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<CalendarHeadCellProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { CalendarHeader, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<CalendarHeaderProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { CalendarHeading, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<CalendarHeadingProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -5,7 +5,6 @@ import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'radix-vue
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<CheckboxRootEmits>()
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ComboboxRoot, useForwardPropsEmits } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = withDefaults(defineProps<ComboboxRootProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
open: true,
|
||||
modelValue: '',
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ComboboxEmpty } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ComboboxGroup, ComboboxLabel } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<ComboboxGroupProps & {
|
||||
class?: HTMLAttributes['class']
|
||||
heading?: string
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ComboboxInput, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ComboboxItem, useForwardPropsEmits } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<ComboboxItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<ComboboxItemEmits>()
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ComboboxContent, useForwardPropsEmits } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = withDefaults(defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
dismissable: false,
|
||||
})
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ComboboxSeparator } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<ContextMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<ContextMenuCheckboxItemEmits>()
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<ContextMenuContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<ContextMenuContentEmits>()
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<ContextMenuItemProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
const emits = defineEmits<ContextMenuItemEmits>()
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ContextMenuLabel } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<ContextMenuLabelProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<ContextMenuRadioItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<ContextMenuRadioItemEmits>()
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<ContextMenuSeparatorProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DropdownMenuSubContentEmits>()
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<ContextMenuSubTriggerProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { DialogDescription, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { DialogTitle, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { DrawerDescription } from 'vaul-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<DrawerDescriptionProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HtmlHTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HtmlHTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { HtmlHTMLAttributes } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HtmlHTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
@@ -5,7 +5,6 @@ import { DrawerOverlay } from 'vaul-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { DrawerTitle } from 'vaul-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<DrawerTitleProps & { class?: HtmlHTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes['class'] }>(),
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ import { DropdownMenuItem, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<DropdownMenuItemProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { DropdownMenuLabel, useForwardProps } from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
|
||||
const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user