Phase 3: Typed Vue components in scripts-v2/

Migrate all shared components to TypeScript SFCs with script setup
lang=ts. 72 files, 7144 lines, zero any types.

- components/base/ (42 files): Button, Input, Textarea, Checkbox,
  Radio, Switch, Badge, Card, Modal, Dialog, Dropdown, DatePicker,
  TimePicker, Money, FileUploader, Select, Icon, Loader, Multiselect,
  TabGroup, Wizard, CustomerSelect, ItemSelect, CustomInput, alerts,
  status badges (Invoice/Estimate/Paid/RecurringInvoice), List/ListItem
- components/table/ (3 files): DataTable, TablePagination
- components/form/ (4 files): FormGroup, FormGrid, SwitchSection
- components/layout/ (11 files): Page, PageHeader, Breadcrumb,
  FilterWrapper, EmptyPlaceholder, ContentPlaceholders, SettingCard
- components/editor/ (2 files): RichEditor with Tiptap
- components/charts/ (2 files): LineChart with Chart.js
- components/notifications/ (3 files): NotificationRoot, NotificationItem
- components/icons/ (2 files): MainLogo

All use defineProps<Props>(), defineEmits<Emits>(), typed refs,
and import domain types from types/domain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Darko Gjorgjijoski
2026-04-04 05:45:00 +02:00
parent 2b996d30bf
commit e43e515614
72 changed files with 7144 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
<template>
<div
:class="success || info ? 'bg-surface' : 'bg-alert-error-bg'"
class="
max-w-sm
mb-3
rounded-lg
shadow-lg
cursor-pointer
pointer-events-auto
w-full
md:w-96
"
@click.stop="hideNotificationAction"
@mouseenter="clearNotificationTimeOut"
@mouseleave="setNotificationTimeOut"
>
<div class="overflow-hidden rounded-lg shadow-xs">
<div class="p-4">
<div class="flex items-start">
<div class="shrink-0">
<svg
v-if="success"
class="w-6 h-6 text-alert-success-text"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<svg
v-if="info"
class="w-6 h-6 text-status-blue"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clip-rule="evenodd"
></path>
</svg>
<svg
v-if="error"
class="w-6 h-6 text-alert-error-text"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class="flex-1 w-0 ml-3 text-left">
<p
:class="`text-sm leading-5 font-medium ${
success || info ? 'text-heading' : 'text-alert-error-text'
}`"
>
{{
notification.title
? notification.title
: success
? 'Success!'
: 'Error'
}}
</p>
<p
:class="`mt-1 text-sm leading-5 ${
success || info ? 'text-muted' : 'text-alert-error-text'
}`"
>
{{
notification.message
? notification.message
: success
? 'Successful'
: 'Something went wrong'
}}
</p>
</div>
<div class="flex shrink-0">
<button
:class="
success || info
? ' text-subtle focus:text-muted'
: 'text-alert-error-text focus:text-alert-error-text'
"
class="
inline-flex
w-5
h-5
transition
duration-150
ease-in-out
focus:outline-hidden
"
@click="hideNotificationAction"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, computed, ref } from 'vue'
import { useNotificationStore } from '@/scripts/stores/notification'
export type NotificationType = 'success' | 'error' | 'info'
export interface Notification {
id: string
type: NotificationType
title?: string
message?: string
time?: number
}
interface Props {
notification: Notification
}
const props = defineProps<Props>()
const notificationStore = useNotificationStore()
const notiTimeOut = ref<ReturnType<typeof setTimeout> | null>(null)
const success = computed<boolean>(() => {
return props.notification.type === 'success'
})
const error = computed<boolean>(() => {
return props.notification.type === 'error'
})
const info = computed<boolean>(() => {
return props.notification.type === 'info'
})
function hideNotificationAction(): void {
notificationStore.hideNotification(props.notification)
}
function clearNotificationTimeOut(): void {
if (notiTimeOut.value) {
clearTimeout(notiTimeOut.value)
}
}
function setNotificationTimeOut(): void {
notiTimeOut.value = setTimeout(() => {
notificationStore.hideNotification(props.notification)
}, props.notification.time || 5000)
}
onMounted(() => {
setNotificationTimeOut()
})
</script>

View File

@@ -0,0 +1,45 @@
<template>
<div
class="
fixed
inset-0
z-50
flex flex-col
items-end
justify-start
w-full
px-4
py-6
pointer-events-none
sm:p-6
"
>
<transition-group
enter-active-class="transition duration-300 ease-out"
enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
leave-active-class="transition duration-100 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<NotificationItem
v-for="notification in notifications"
:key="notification.id"
:notification="notification"
/>
</transition-group>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useNotificationStore } from '@/scripts/stores/notification'
import NotificationItem from './NotificationItem.vue'
import type { Notification } from './NotificationItem.vue'
const notificationStore = useNotificationStore()
const notifications = computed<Notification[]>(() => {
return notificationStore.notifications
})
</script>

View File

@@ -0,0 +1,4 @@
export { default as NotificationRoot } from './NotificationRoot.vue'
export { default as NotificationItem } from './NotificationItem.vue'
export type { Notification, NotificationType } from './NotificationItem.vue'