refactor: reorder component imports and optimize code structure across UI forms and composables

This commit is contained in:
Yoga Pangestu 2026-06-14 01:26:00 +07:00
parent c22252f44c
commit 8609c68a6d
39 changed files with 193 additions and 111 deletions

View File

@ -1,38 +1,43 @@
<script setup lang="ts">
import { useForm } from '@inertiajs/vue3'
import type { HTMLAttributes } from "vue"
import { toast } from 'vue-sonner'
import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import {
Field, FieldError,
Field,
FieldError,
FieldGroup,
FieldLabel
} from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import { formErrors } from '@/lib/form'
import { cn } from "@/lib/utils"
import FieldSeparator from './ui/field/FieldSeparator.vue'
FieldLabel,
} from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { formErrors } from '@/lib/form';
import { cn } from '@/lib/utils';
import { useForm } from '@inertiajs/vue3';
import { Eye, EyeOff } from '@lucide/vue';
import { ref, type HTMLAttributes } from 'vue';
import { toast } from 'vue-sonner';
import FieldSeparator from './ui/field/FieldSeparator.vue';
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
class?: HTMLAttributes['class'];
}>();
const showPassword = ref(false);
const form = useForm({
login: '',
password: '',
})
});
function submit() {
form.post('/auth/login', {
onError: (errors) => {
const message = errors.login
?? errors.password
?? 'Gagal masuk. Periksa kembali data Anda.';
const message =
errors.login ??
errors.password ??
'Gagal masuk. Periksa kembali data Anda.';
toast.error(message);
},
})
});
}
</script>
@ -42,33 +47,58 @@ function submit() {
<CardContent class="grid p-0 md:grid-cols-2">
<form class="p-6 md:p-8" @submit.prevent="submit">
<FieldGroup>
<div class="flex flex-col items-center gap-2 text-center">
<h1 class="text-2xl font-bold">
Selamat Datang
</h1>
<p class="text-muted-foreground text-balance">
Silakan masukkan email dan username untuk akses dasbor.
<div
class="flex flex-col items-center gap-2 text-center"
>
<h1 class="text-2xl font-bold">Selamat Datang</h1>
<p class="text-balance text-muted-foreground">
Silakan masukkan email dan username untuk akses
dasbor.
</p>
</div>
<Field>
<FieldLabel for="login" required>
Email/Username
</FieldLabel>
<Input id="login" v-model="form.login" type="text" placeholder="m@example.com" required />
<Input
id="login"
v-model="form.login"
type="text"
placeholder="m@example.com"
required
/>
<FieldError :errors="formErrors(form, 'login')" />
</Field>
<Field>
<div class="flex items-center">
<FieldLabel for="password" required>
Kata Sandi
</FieldLabel>
<a href="#" class="ml-auto text-sm underline-offset-2 hover:underline">
Lupa kata sandi?
</a>
<FieldLabel for="password" required>
Kata Sandi
</FieldLabel>
<div class="relative">
<Input
id="password"
v-model="form.password"
:type="showPassword ? 'text' : 'password'"
placeholder="********"
required
class="pr-10"
/>
<Button
variant="ghost"
size="icon"
type="button"
@click="showPassword = !showPassword"
class="absolute inset-y-0 right-0"
>
<Eye v-if="!showPassword" class="h-5 w-5" />
<EyeOff v-else class="h-5 w-5" />
</Button>
</div>
<Input id="password" v-model="form.password" type="password" placeholder="********"
required />
<FieldError :errors="formErrors(form, 'password')" />
<FieldError
:errors="formErrors(form, 'password')"
/>
</Field>
<Field>
<Button type="submit" :disabled="form.processing">
@ -76,24 +106,33 @@ function submit() {
</Button>
</Field>
<FieldSeparator class="*:data-[slot=field-separator-content]:bg-card">
<FieldSeparator
class="*:data-[slot=field-separator-content]:bg-card"
>
Atau lanjutkan dengan
</FieldSeparator>
<Field class="grid gap-4">
<Button variant="outline" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
fill="currentColor" />
fill="currentColor"
/>
</svg>
<span>Google</span>
</Button>
</Field>
</FieldGroup>
</form>
<div class="bg-muted relative hidden md:block">
<img src="https://images.pexels.com/photos/37080685/pexels-photo-37080685.jpeg" alt="Image"
class="absolute inset-0 h-full w-full object-cover">
<div class="relative hidden bg-muted md:block">
<img
src="https://images.pexels.com/photos/37080685/pexels-photo-37080685.jpeg"
alt="Image"
class="absolute inset-0 h-full w-full object-cover"
/>
</div>
</CardContent>
</Card>

View File

@ -3,6 +3,8 @@ import { useForm } from '@inertiajs/vue3';
import { Save } from '@lucide/vue';
import { computed, ref } from 'vue';
import { toast } from 'vue-sonner';
import ImageUploadField from '@/components/form/image-upload-field/ImageUploadField.vue';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Button } from '@/components/ui/button';
import {
Dialog,
@ -18,8 +20,6 @@ import {
FieldLabel,
FieldSet,
} from '@/components/ui/field';
import ImageUploadField from '@/components/form/image-upload-field/ImageUploadField.vue';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Textarea } from '@/components/ui/textarea';
import { useFormDialog } from '@/composables/useFormDialog';
import { FIELD_LIMITS } from '@/lib/field-limits';

View File

@ -1,8 +1,8 @@
import type { ColumnDef } from '@tanstack/vue-table';
import { h } from 'vue';
import DataTableActions from '@/components/admin/finance/cash/data-table-actions.vue';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import { DataTableColumnHeader } from '@/components/data-table';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import { Badge } from '@/components/ui/badge';
import type { CashTransactionListItem } from '@/types/cash';

View File

@ -3,6 +3,8 @@ import { useForm } from '@inertiajs/vue3';
import { Save } from '@lucide/vue';
import { computed, ref } from 'vue';
import { toast } from 'vue-sonner';
import ImageUploadField from '@/components/form/image-upload-field/ImageUploadField.vue';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Button } from '@/components/ui/button';
import { DatePicker } from '@/components/ui/date-picker';
import {
@ -19,8 +21,6 @@ import {
FieldLabel,
FieldSet,
} from '@/components/ui/field';
import ImageUploadField from '@/components/form/image-upload-field/ImageUploadField.vue';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Textarea } from '@/components/ui/textarea';
import { useFormDialog } from '@/composables/useFormDialog';
import { FIELD_LIMITS } from '@/lib/field-limits';

View File

@ -1,8 +1,8 @@
import type { ColumnDef } from '@tanstack/vue-table';
import { h } from 'vue';
import DataTableActions from '@/components/admin/finance/employee-advances/data-table-actions.vue';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import { DataTableColumnHeader } from '@/components/data-table';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import { Badge } from '@/components/ui/badge';
import type { EmployeeAdvanceListItem } from '@/types/employee-advance';

View File

@ -3,6 +3,8 @@ import { useForm } from '@inertiajs/vue3';
import { Save } from '@lucide/vue';
import { computed, ref } from 'vue';
import { toast } from 'vue-sonner';
import { ImageUploadField } from '@/components/form/image-upload-field';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Button } from '@/components/ui/button';
import {
Dialog,
@ -18,8 +20,6 @@ import {
FieldLabel,
FieldSet,
} from '@/components/ui/field';
import { ImageUploadField } from '@/components/form/image-upload-field';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Textarea } from '@/components/ui/textarea';
import { useFormDialog } from '@/composables/useFormDialog';
import { FIELD_LIMITS } from '@/lib/field-limits';

View File

@ -1,8 +1,8 @@
import type { ColumnDef } from '@tanstack/vue-table';
import { h } from 'vue';
import DataTableActions from '@/components/admin/finance/expenses/data-table-actions.vue';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import { DataTableColumnHeader } from '@/components/data-table';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import type { ExpenseListItem } from '@/types/expense';
export function createColumns(onEdit: (expense: ExpenseListItem) => void): ColumnDef<ExpenseListItem>[] {

View File

@ -3,6 +3,7 @@ import { useForm } from '@inertiajs/vue3';
import { Save } from '@lucide/vue';
import { watch } from 'vue';
import { toast } from 'vue-sonner';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Button } from '@/components/ui/button';
import {
Dialog,
@ -20,7 +21,6 @@ import {
} from '@/components/ui/field';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Textarea } from '@/components/ui/textarea';
import { FIELD_LIMITS } from '@/lib/field-limits';
import { parseRupiah } from '@/lib/rupiah';

View File

@ -2,6 +2,8 @@
import { useForm } from '@inertiajs/vue3';
import { Save } from '@lucide/vue';
import { toast } from 'vue-sonner';
import { PhoneNumberInput } from '@/components/form/phone-number-input';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { DatePicker } from '@/components/ui/date-picker';
@ -14,9 +16,7 @@ import {
} from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { PhoneNumberInput } from '@/components/form/phone-number-input';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { RupiahInput } from '@/components/form/rupiah-input';
import {
Select,
SelectContent,

View File

@ -7,7 +7,7 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
const props = defineProps<{
defineProps<{
checkInPhotoUrl: string | null;
checkOutPhotoUrl: string | null;
checkInLocationTag?: string | null;

View File

@ -1,8 +1,8 @@
import type { ColumnDef } from '@tanstack/vue-table';
import { h } from 'vue';
import { DataTableColumnHeader } from '@/components/data-table';
import DataTableActions from '@/components/admin/hr/employees/data-table-actions.vue';
import EmployeeStatusToggle from '@/components/admin/hr/employees/employee-status-toggle.vue';
import { DataTableColumnHeader } from '@/components/data-table';
import { Badge } from '@/components/ui/badge';
import type { EmployeeListItem } from '@/types/employee';

View File

@ -19,11 +19,11 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
import type { CuttingListItem } from '@/types/cutting';
import type {
DataTablePagination,
DataTablePaginationLink,
} from '@/types/data-table';
import type { CuttingListItem } from '@/types/cutting';
const props = defineProps<{
cuttings: CuttingListItem[];
@ -104,6 +104,7 @@ function getGroupedMaterials(materials: any[]): GroupedCuttingMaterials[] {
items: [],
};
}
groups[rawMaterialId].items.push(item);
});
@ -125,6 +126,7 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
items: [],
};
}
groups[productId].items.push(item);
});

View File

@ -1,6 +1,12 @@
<script setup lang="ts">
import { useForm } from '@inertiajs/vue3';
import { Minus, Plus, Save, Scissors, Search, Trash2 } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import { toast } from 'vue-sonner';
import PosCatalogCard from '@/components/admin/manage/PosCatalogCard.vue';
import PosCatalogVariantThumb from '@/components/admin/manage/PosCatalogVariantThumb.vue';
import { DecimalInput } from '@/components/form/decimal-input';
import { NumberInput } from '@/components/form/number-input';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@ -18,8 +24,6 @@ import {
FieldSet,
} from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { DecimalInput } from '@/components/form/decimal-input';
import { NumberInput } from '@/components/form/number-input';
import { Separator } from '@/components/ui/separator';
import { Textarea } from '@/components/ui/textarea';
import { getFirstCoverImage } from '@/lib/catalog-cover';
@ -31,10 +35,6 @@ import type {
CuttingRawMaterialCatalogItem,
CuttingResultCartItem,
} from '@/types/cutting';
import { useForm } from '@inertiajs/vue3';
import { Minus, Plus, Save, Scissors, Search, Trash2 } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import { toast } from 'vue-sonner';
const props = defineProps<{
rawMaterialCatalog: CuttingRawMaterialCatalogItem[];
@ -124,9 +124,11 @@ function decreaseMaterialQty(priceId: number) {
const index = materialCart.value.findIndex(
(item) => item.raw_material_price_id === priceId,
);
if (index !== -1) {
const item = materialCart.value[index];
const nextQty = (Number(item.material_usage) || 0) - 1;
if (nextQty <= 0) {
removeMaterial(index);
} else {
@ -147,6 +149,7 @@ function addMaterial(
existing.material_usage = String(
(Number(existing.material_usage) || 0) + 1,
);
return;
}
@ -178,9 +181,11 @@ function decreaseResultQty(variantId: number) {
const index = resultCart.value.findIndex(
(item) => item.product_variant_id === variantId,
);
if (index !== -1) {
const item = resultCart.value[index];
const nextQty = (Number(item.cutting_result) || 0) - 1;
if (nextQty <= 0) {
removeResult(index);
} else {
@ -210,6 +215,7 @@ function addResult(
(Number(existing.warehouse_stock) || 0) + 1,
);
syncResultTotals(existing);
return;
}

View File

@ -1,4 +1,8 @@
<script setup lang="ts">
import { Link, router, useForm } from '@inertiajs/vue3';
import { Check, Pencil, RotateCcw, Scissors, Trash2, X } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import { toast } from 'vue-sonner';
import ConfirmDialog from '@/components/ConfirmDialog.vue';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@ -26,10 +30,6 @@ import {
import { useCan } from '@/composables/useCan';
import { FIELD_LIMITS } from '@/lib/field-limits';
import type { CuttingListItem, CuttingStatusAction } from '@/types/cutting';
import { Link, router, useForm } from '@inertiajs/vue3';
import { Check, Pencil, RotateCcw, Scissors, Trash2, X } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import { toast } from 'vue-sonner';
const props = defineProps<{
cutting: CuttingListItem;
@ -108,6 +108,7 @@ function statusConfirmDescription(action: CuttingStatusAction): string {
function openStatusConfirm(action: CuttingStatusAction) {
if (action.status === 'verified') {
verifyDialogOpen.value = true;
return;
}

View File

@ -5,6 +5,8 @@ import { computed, ref, watch } from 'vue';
import { toast } from 'vue-sonner';
import PosCatalogCard from '@/components/admin/manage/PosCatalogCard.vue';
import PosCatalogVariantThumb from '@/components/admin/manage/PosCatalogVariantThumb.vue';
import { NumberInput } from '@/components/form/number-input';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
@ -21,8 +23,6 @@ import {
FieldSet,
} from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { NumberInput } from '@/components/form/number-input';
import { RupiahInput } from '@/components/form/rupiah-input';
import {
Select,
SelectContent,
@ -185,6 +185,7 @@ function getCartItem(variantId: number): OrderCartItem | undefined {
async function decreaseVariantQty(variantId: number) {
const index = cart.value.findIndex((item) => item.product_variant_id === variantId);
if (index !== -1) {
await adjustQuantity(index, -1);
}

View File

@ -83,6 +83,7 @@ function getGroupedItems(items: any[]): GroupedPurchaseItems[] {
items: [],
};
}
groups[rawMaterialId].items.push(item);
});

View File

@ -5,6 +5,9 @@ import { computed, ref, watch } from 'vue';
import { toast } from 'vue-sonner';
import PosCatalogCard from '@/components/admin/manage/PosCatalogCard.vue';
import PosCatalogVariantThumb from '@/components/admin/manage/PosCatalogVariantThumb.vue';
import { DecimalInput } from '@/components/form/decimal-input';
import { ImageUploadField } from '@/components/form/image-upload-field';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@ -21,10 +24,7 @@ import {
FieldLabel,
FieldSet,
} from '@/components/ui/field';
import { ImageUploadField } from '@/components/form/image-upload-field';
import { Input } from '@/components/ui/input';
import { DecimalInput } from '@/components/form/decimal-input';
import { RupiahInput } from '@/components/form/rupiah-input';
import {
Select,
SelectContent,
@ -162,6 +162,7 @@ function getCartItem(priceId: number): PurchaseCartItem | undefined {
async function decreasePriceQty(priceId: number) {
const index = cart.value.findIndex((item) => item.raw_material_price_id === priceId);
if (index !== -1) {
await adjustQuantity(index, -1);
}

View File

@ -3,6 +3,7 @@ import { useForm } from '@inertiajs/vue3';
import { Save } from '@lucide/vue';
import { computed } from 'vue';
import { toast } from 'vue-sonner';
import { PhoneNumberInput } from '@/components/form/phone-number-input';
import { Button } from '@/components/ui/button';
import {
Dialog,
@ -19,7 +20,6 @@ import {
FieldSet,
} from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { PhoneNumberInput } from '@/components/form/phone-number-input';
import { Textarea } from '@/components/ui/textarea';
import { useFormDialog } from '@/composables/useFormDialog';
import { FIELD_LIMITS } from '@/lib/field-limits';

View File

@ -3,6 +3,9 @@ import { useForm } from '@inertiajs/vue3';
import { Copy, Plus, Save, Trash2 } from '@lucide/vue';
import { computed, ref } from 'vue';
import { toast } from 'vue-sonner';
import { MultipleImageUploadField } from '@/components/form/image-upload-field';
import { NumberInput } from '@/components/form/number-input';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
@ -12,10 +15,7 @@ import {
FieldLabel,
FieldSet,
} from '@/components/ui/field';
import { MultipleImageUploadField } from '@/components/form/image-upload-field';
import { Input } from '@/components/ui/input';
import { NumberInput } from '@/components/form/number-input';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Textarea } from '@/components/ui/textarea';
import { FIELD_LIMITS } from '@/lib/field-limits';
import { formErrors } from '@/lib/form';

View File

@ -3,8 +3,8 @@ import { Link } from '@inertiajs/vue3';
import { computed } from 'vue';
import DataTableActions from '@/components/admin/master/products/data-table-actions.vue';
import ProductStatusToggle from '@/components/admin/master/products/product-status-toggle.vue';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import DataTableToolbar from '@/components/data-table/DataTableToolbar.vue';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
@ -28,9 +28,10 @@ import type {
} from '@/types/data-table';
import {
PRICE_TYPES,
PRICE_TYPE_LABELS,
type ProductListItem,
PRICE_TYPE_LABELS
} from '@/types/product';
import type {ProductListItem} from '@/types/product';
const props = defineProps<{
products: ProductListItem[];

View File

@ -3,6 +3,9 @@ import { useForm } from '@inertiajs/vue3';
import { Copy, Plus, Save, Trash2 } from '@lucide/vue';
import { ref } from 'vue';
import { toast } from 'vue-sonner';
import { DecimalInput } from '@/components/form/decimal-input';
import { MultipleImageUploadField } from '@/components/form/image-upload-field';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
@ -12,10 +15,7 @@ import {
FieldLabel,
FieldSet,
} from '@/components/ui/field';
import { MultipleImageUploadField } from '@/components/form/image-upload-field';
import { Input } from '@/components/ui/input';
import { DecimalInput } from '@/components/form/decimal-input';
import { RupiahInput } from '@/components/form/rupiah-input';
import {
Select,
SelectContent,

View File

@ -3,8 +3,8 @@ import { Link } from '@inertiajs/vue3';
import { computed } from 'vue';
import DataTableActions from '@/components/admin/master/raw-materials/data-table-actions.vue';
import RawMaterialStatusToggle from '@/components/admin/master/raw-materials/raw-material-status-toggle.vue';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import DataTableToolbar from '@/components/data-table/DataTableToolbar.vue';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {

View File

@ -3,6 +3,7 @@ import { useForm } from '@inertiajs/vue3';
import { Save } from '@lucide/vue';
import { computed } from 'vue';
import { toast } from 'vue-sonner';
import { PhoneNumberInput } from '@/components/form/phone-number-input';
import { Button } from '@/components/ui/button';
import {
Dialog,
@ -19,7 +20,6 @@ import {
FieldSet,
} from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { PhoneNumberInput } from '@/components/form/phone-number-input';
import { Textarea } from '@/components/ui/textarea';
import { useFormDialog } from '@/composables/useFormDialog';
import { FIELD_LIMITS } from '@/lib/field-limits';

View File

@ -1,12 +1,12 @@
<script setup lang="ts">
import { computed } from 'vue';
import { RupiahInput } from '@/components/form/rupiah-input';
import {
Field,
FieldError,
FieldLabel,
} from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { RupiahInput } from '@/components/form/rupiah-input';
import {
Select,
SelectContent,

View File

@ -2,12 +2,12 @@
import { router, useForm } from '@inertiajs/vue3';
import { Save } from '@lucide/vue';
import { toast } from 'vue-sonner';
import { ImageUploadField } from '@/components/form/image-upload-field';
import { PhoneNumberInput } from '@/components/form/phone-number-input';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Field, FieldError, FieldGroup, FieldLabel, FieldSet } from '@/components/ui/field';
import { ImageUploadField } from '@/components/form/image-upload-field';
import { Input } from '@/components/ui/input';
import { PhoneNumberInput } from '@/components/form/phone-number-input';
import { Textarea } from '@/components/ui/textarea';
import { formErrors } from '@/lib/form';
import type { SystemSettingsData } from '@/types/setting';

View File

@ -18,11 +18,17 @@ const emit = defineEmits<{
// Formats JS float string/number (e.g. 1250.75) to ID decimal string (e.g. 1250,75)
function formatDecimal(value: string | number): string {
const str = String(value ?? '').trim();
if (!str) return '';
if (!str) {
return '';
}
// Normalize to dot for processing
const normalized = str.replace(',', '.');
if (isNaN(parseFloat(normalized))) return '';
if (isNaN(parseFloat(normalized))) {
return '';
}
// Split into integer and decimal parts
const parts = normalized.split('.');
@ -31,8 +37,10 @@ function formatDecimal(value: string | number): string {
if (decimalPart !== undefined) {
decimalPart = decimalPart.slice(0, 2);
return `${integerPart},${decimalPart}`;
}
return integerPart;
}
@ -50,6 +58,7 @@ function parseDecimal(value: string): string {
let clean = normalized.replace(/\./g, '').replace(',', '.');
const parts = clean.split('.');
if (parts.length > 2) {
clean = parts[0] + '.' + parts.slice(1).join('');
}
@ -57,6 +66,7 @@ function parseDecimal(value: string): string {
clean = clean.replace(/[^0-9.]/g, '');
const cleanParts = clean.split('.');
if (cleanParts[1] !== undefined) {
clean = `${cleanParts[0]}.${cleanParts[1].slice(0, 2)}`;
}

View File

@ -18,7 +18,11 @@ const emit = defineEmits<{
function formatNumber(value: string | number): string {
const str = String(value ?? '').trim();
const clean = str.replace(/\D/g, '');
if (!clean) return '';
if (!clean) {
return '';
}
return Number(clean).toLocaleString('id-ID');
}

View File

@ -1,6 +1,7 @@
import { router } from '@inertiajs/vue3';
import { useDebounceFn } from '@vueuse/core';
import { ref, watch, type WatchSource } from 'vue';
import { ref, watch } from 'vue';
import type {WatchSource} from 'vue';
import type { DataTableQuery, DataTableSort } from '@/types/data-table';
type UseDataTableQueryOptions = {

View File

@ -8,6 +8,7 @@ export function useGeolocation() {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject(new Error('Perangkat tidak mendukung geolokasi.'));
return;
}

View File

@ -7,6 +7,7 @@ function urlBase64ToUint8Array(base64String: string): Uint8Array {
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = atob(base64);
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
}
@ -26,7 +27,9 @@ export function usePushNotification() {
.vapidPublicKey as string | undefined;
async function checkSubscriptionStatus(): Promise<void> {
if (!isSupported.value) return;
if (!isSupported.value) {
return;
}
try {
const registration = await navigator.serviceWorker.ready;
@ -39,10 +42,15 @@ export function usePushNotification() {
}
async function subscribe(): Promise<void> {
if (!isSupported.value || isLoading.value) return;
if (!isSupported.value || isLoading.value) {
return;
}
const permission = await Notification.requestPermission();
if (permission !== 'granted') return;
if (permission !== 'granted') {
return;
}
isLoading.value = true;
@ -85,7 +93,9 @@ export function usePushNotification() {
}
async function unsubscribe(): Promise<void> {
if (!isSupported.value || isLoading.value) return;
if (!isSupported.value || isLoading.value) {
return;
}
isLoading.value = true;
@ -96,6 +106,7 @@ export function usePushNotification() {
if (!subscription) {
isSubscribed.value = false;
return;
}

View File

@ -1,13 +1,13 @@
<script setup lang="ts">
import { usePage } from '@inertiajs/vue3';
import AppSidebar from '@/components/AppSidebar.vue';
import UserNav from '@/components/UserNav.vue';
import { Separator } from '@/components/ui/separator';
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from '@/components/ui/sidebar';
import UserNav from '@/components/UserNav.vue';
defineProps<{
title?: string;

View File

@ -2,6 +2,7 @@
import { Head, useForm } from '@inertiajs/vue3';
import { Save } from '@lucide/vue';
import { toast } from 'vue-sonner';
import { PhoneNumberInput } from '@/components/form/phone-number-input';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { DatePicker } from '@/components/ui/date-picker';
@ -13,7 +14,6 @@ import {
FieldSet,
} from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { PhoneNumberInput } from '@/components/form/phone-number-input';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Textarea } from '@/components/ui/textarea';
import AccountLayout from '@/layouts/AccountLayout.vue';

View File

@ -2,16 +2,16 @@
import { Head } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import ExpenseFormModal from '@/components/admin/finance/expenses/ExpenseFormModal.vue';
import { createColumns } from '@/components/admin/finance/expenses/columns';
import ExpenseFormModal from '@/components/admin/finance/expenses/ExpenseFormModal.vue';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { ExpenseListItem, PaginatedExpenses } from '@/types/expense';
import type { DataTableSort } from '@/types/data-table';
import type { ExpenseListItem, PaginatedExpenses } from '@/types/expense';
const props = defineProps<{
expenses: PaginatedExpenses;

View File

@ -2,8 +2,8 @@
import { Head, Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import CuttingInProgressSection from '@/components/admin/manage/cuttings/CuttingInProgressSection.vue';
import CuttingGroupedTable from '@/components/admin/manage/cuttings/CuttingGroupedTable.vue';
import CuttingInProgressSection from '@/components/admin/manage/cuttings/CuttingInProgressSection.vue';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/composables/useCan';

View File

@ -3,6 +3,7 @@ import { Head } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import { createColumns } from '@/components/admin/master/customers/columns';
import CustomerFormModal from '@/components/admin/master/customers/CustomerFormModal.vue';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
@ -10,7 +11,6 @@ import { useCan } from '@/composables/useCan';
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { DataTableSort } from '@/types/data-table';
import CustomerFormModal from '@/components/admin/master/customers/CustomerFormModal.vue';
import type { CustomerListItem, PaginatedCustomers } from '@/types/customer';
const props = defineProps<{

View File

@ -1,4 +1,7 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import MasterOutOfStockCatalogSection from '@/components/admin/master/MasterOutOfStockCatalogSection.vue';
import ProductGroupedTable from '@/components/admin/master/products/ProductGroupedTable.vue';
import { Button } from '@/components/ui/button';
@ -15,9 +18,6 @@ import type {
ProductOutOfStockGroup,
ProductStockWarningGroup,
} from '@/types/product';
import { Head, Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
const props = defineProps<{
products: PaginatedProducts;

View File

@ -1,4 +1,7 @@
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import MasterOutOfStockCatalogSection from '@/components/admin/master/MasterOutOfStockCatalogSection.vue';
import RawMaterialGroupedTable from '@/components/admin/master/raw-materials/RawMaterialGroupedTable.vue';
import { Button } from '@/components/ui/button';
@ -12,9 +15,6 @@ import AdminLayout from '@/layouts/AdminLayout.vue';
import type { DataTableFilterDef } from '@/types/data-table';
import type { MasterOutOfStockGroup } from '@/types/master-inventory';
import type { PaginatedRawMaterials } from '@/types/raw-material';
import { Head, Link } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
const props = defineProps<{
rawMaterials: PaginatedRawMaterials;

View File

@ -2,6 +2,8 @@
import { Head } from '@inertiajs/vue3';
import { Plus } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import { createColumns } from '@/components/admin/master/suppliers/columns';
import SupplierFormModal from '@/components/admin/master/suppliers/SupplierFormModal.vue';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
@ -9,8 +11,6 @@ import { useCan } from '@/composables/useCan';
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { DataTableSort } from '@/types/data-table';
import { createColumns } from '@/components/admin/master/suppliers/columns';
import SupplierFormModal from '@/components/admin/master/suppliers/SupplierFormModal.vue';
import type { SupplierListItem, PaginatedSuppliers } from '@/types/supplier';
const props = defineProps<{

View File

@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
const sw = self as unknown as ServiceWorkerGlobalScope;
@ -35,7 +35,9 @@ sw.addEventListener('activate', (event: Event) => {
sw.addEventListener('push', (event: Event) => {
const pushEvent = event as PushEvent;
if (!pushEvent.data) return;
if (!pushEvent.data) {
return;
}
let data: { title?: string; body?: string; icon?: string; url?: string } =
{};
@ -81,6 +83,7 @@ sw.addEventListener('notificationclick', (event: Event) => {
.then((c) => c?.focus());
}
}
return sw.clients.openWindow(targetUrl);
}),
);