DONE: layout letter with letterhead and footer, notification for newly...

This commit is contained in:
ISMAIL MASSERAN
2026-06-28 02:16:49 +00:00
parent 94ecbe5887
commit 034ecf947f
400 changed files with 29802 additions and 5446 deletions
@@ -1,10 +1,6 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { datePickerMonthSelect } from "@mykopkb/core/styles/datepicker.styles";
import {
NativeSelect,
NativeSelectOption,
} from "@/components/ui/native-select";
import type { Api } from "@zag-js/date-picker";
import { inject } from "vue";
@@ -1,9 +1,6 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import {
NativeSelect,
NativeSelectOption,
} from "@/components/ui/native-select";
import { datePickerYearSelect } from "@mykopkb/core/styles/datepicker.styles";
import type { Api } from "@zag-js/date-picker";
import { inject } from "vue";
@@ -1,15 +0,0 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { input } from "@mykopkb/core/styles/input.styles";
import { nativeSelect } from "@mykopkb/core/styles/native-select.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<select :class="cn(input, nativeSelect, className)" v-bind="{ ...props, ...$attrs }">
<slot />
</select>
</template>
@@ -1,14 +0,0 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { nativeSelectOption } from "@mykopkb/core/styles/native-select.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<option :class="cn(nativeSelectOption, className)" v-bind="{ ...props, ...$attrs }">
<slot />
</option>
</template>
@@ -1,14 +0,0 @@
<script lang="ts" setup>
import { cn } from "@mykopkb/core/utils/cn";
import { NativeSelectOptGroup } from "@mykopkb/core/styles/native-select.styles";
const { class: className, ...props } = defineProps<{
class?: string;
}>();
</script>
<template>
<optgroup :class="cn(NativeSelectOptGroup, className)" v-bind="{ ...props, ...$attrs }">
<slot />
</optgroup>
</template>
@@ -1,3 +0,0 @@
export { default as NativeSelect } from "./NativeSelect.vue";
export { default as NativeSelectOption } from "./NativeSelectOption.vue";
export { default as NativeSelectOptionGroup } from "./NativeSelectOptionGroup.vue";
+4 -2
View File
@@ -2,11 +2,13 @@
import { cn } from "@mykopkb/core/utils/cn";
import { textarea } from "@mykopkb/core/styles/textarea.styles";
const { class: className, ...props } = defineProps<{
const modelValue = defineModel<string>();
const { class: className } = defineProps<{
class?: string;
}>();
</script>
<template>
<textarea :class="cn(textarea, className)" v-bind="{ ...props }" />
<textarea v-model="modelValue" :class="cn(textarea, className)" v-bind="$attrs" />
</template>
+57 -14
View File
@@ -43,10 +43,17 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
import * as select from '@zag-js/select';
import {
NativeSelect,
NativeSelectOption,
} from '@/components/ui/native-select';
SelectRoot,
SelectControl,
SelectTrigger,
SelectValueText,
SelectContent,
SelectItemGroup,
SelectItem,
SelectItemText,
} from '@/components/ui/select';
// Type definitions
type ColumnType = 'index' | 'select' | 'action' | 'status' | 'id' | 'date' | 'email' | 'phone' | 'priority' | 'category' | 'registration' | 'model' | 'unit' | 'text';
@@ -129,6 +136,22 @@ const resizingColumn = ref<string | null>(null);
const startX = ref(0);
const perPageOptions = [10, 25, 50, 100, 250, 500, 1000];
type PerPageSelectOption = { label: string; value: number };
const perPageSelectOptions: PerPageSelectOption[] = perPageOptions.map((option) => ({
label: String(option),
value: option,
}));
const perPageCollection = select.collection({
items: perPageSelectOptions,
itemToValue: (item) => item.label,
});
const perPageInitial = computed(() => [
String(itemsPerPageModel.value ?? props.pagination?.per_page ?? props.itemsPerPage ?? 10),
]);
const startWidth = ref(0);
// Helper function to detect column type from key and title
@@ -136,9 +159,10 @@ const detectColumnType = (header: TableHeader): ColumnType => {
const key = header.key.toLowerCase();
const title = header.title.toLowerCase();
// Index/Number columns
if (key === '#' || key === 'index' || key === 'bil' || key === 'no' ||
title.includes('bil') || title.includes('no') || title.includes('#')) {
// Index/Number columns (Bil./# row counters — not field labels like "No. Permohonan")
if (key === '#' || key === 'index' || key === 'bil' ||
title.includes('bil') || title === '#' ||
/^no\.?\s*$/i.test(title.trim())) {
return 'index';
}
@@ -416,8 +440,9 @@ const headerAlignClass = (align?: TableHeader['align']) => {
return 'text-left';
};
const handleItemsPerPageChange = (event: Event) => {
const value = Number((event.target as HTMLSelectElement).value);
const setItemsPerPageValue = (details: { value: string[] }) => {
const value = Number(details.value[0]);
if (Number.isNaN(value)) return;
itemsPerPageModel.value = value;
emit('update:items-per-page', value);
};
@@ -791,12 +816,30 @@ watch(sortBy, (newSort) => {
class="flex flex-wrap items-center justify-between gap-4 border-t border-foreground/10 bg-foreground/2 px-4 py-3">
<div class="flex items-center gap-2 text-sm text-muted-foreground">
<span>Bilangan rekod per halaman:</span>
<NativeSelect class="h-9 w-auto min-w-20 px-2 py-1 text-sm"
:value="String(itemsPerPageModel ?? pagination.per_page)" @change="handleItemsPerPageChange">
<NativeSelectOption v-for="option in perPageOptions" :key="option" :value="String(option)">
{{ option }}
</NativeSelectOption>
</NativeSelect>
<SelectRoot
:key="perPageInitial[0]"
class="w-auto min-w-20"
:collection="perPageCollection"
:default-value="perPageInitial"
@value-change="setItemsPerPageValue"
>
<SelectControl>
<SelectTrigger class="h-9 w-auto min-w-20">
<SelectValueText />
</SelectTrigger>
</SelectControl>
<SelectContent>
<SelectItemGroup>
<SelectItem
v-for="item in perPageCollection.items"
:key="item.label"
:item="item"
>
<SelectItemText>{{ item.label }}</SelectItemText>
</SelectItem>
</SelectItemGroup>
</SelectContent>
</SelectRoot>
</div>
<div class="text-sm text-muted-foreground">
@@ -1,72 +0,0 @@
<script setup lang="ts">
import {
NativeSelect,
NativeSelectOption,
NativeSelectOptionGroup,
} from "@/components/ui/native-select";
</script>
<template>
<div class="flex flex-col gap-20">
<div class="grid sm:grid-cols-2">
<div
class="justify-center items-center flex gap-2 border-b border-e border-foreground/10 p-5 flex-wrap"
>
<NativeSelect class="w-56">
<NativeSelectOption value="">Select status</NativeSelectOption>
<NativeSelectOption value="todo">Todo</NativeSelectOption>
<NativeSelectOption value="in-progress"
>In Progress</NativeSelectOption
>
<NativeSelectOption value="done">Done</NativeSelectOption>
<NativeSelectOption value="cancelled">Cancelled</NativeSelectOption>
</NativeSelect>
</div>
<div
class="justify-center items-center flex gap-2 border-b border-e border-foreground/10 p-5 flex-wrap"
>
<NativeSelect class="w-56">
<NativeSelectOption value="">Select department</NativeSelectOption>
<NativeSelectOptionGroup label="Engineering">
<NativeSelectOption value="frontend">Frontend</NativeSelectOption>
<NativeSelectOption value="backend">Backend</NativeSelectOption>
<NativeSelectOption value="devops">DevOps</NativeSelectOption>
</NativeSelectOptionGroup>
<NativeSelectOptionGroup label="Sales">
<NativeSelectOption value="sales-rep">Sales Rep</NativeSelectOption>
<NativeSelectOption value="account-manager">
Account Manager
</NativeSelectOption>
<NativeSelectOption value="sales-director">
Sales Director
</NativeSelectOption>
</NativeSelectOptionGroup>
<NativeSelectOptionGroup label="Operations">
<NativeSelectOption value="support">
Customer Support
</NativeSelectOption>
<NativeSelectOption value="product-manager">
Product Manager
</NativeSelectOption>
<NativeSelectOption value="ops-manager">
Operations Manager
</NativeSelectOption>
</NativeSelectOptionGroup>
</NativeSelect>
</div>
<div
class="justify-center items-center flex gap-2 border-b border-e border-foreground/10 p-5 flex-wrap"
>
<NativeSelect class="w-56" disabled>
<NativeSelectOption value="">Select status</NativeSelectOption>
<NativeSelectOption value="todo">Todo</NativeSelectOption>
<NativeSelectOption value="in-progress"
>In Progress</NativeSelectOption
>
<NativeSelectOption value="done">Done</NativeSelectOption>
<NativeSelectOption value="cancelled">Cancelled</NativeSelectOption>
</NativeSelect>
</div>
</div>
</div>
</template>