refactor: reorder component imports and optimize code structure across UI forms and composables
This commit is contained in:
parent
c22252f44c
commit
8609c68a6d
@ -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>
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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>[] {
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
checkInPhotoUrl: string | null;
|
||||
checkOutPhotoUrl: string | null;
|
||||
checkInLocationTag?: string | null;
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -83,6 +83,7 @@ function getGroupedItems(items: any[]): GroupedPurchaseItems[] {
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
||||
groups[rawMaterialId].items.push(item);
|
||||
});
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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[];
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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)}`;
|
||||
}
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -8,6 +8,7 @@ export function useGeolocation() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!navigator.geolocation) {
|
||||
reject(new Error('Perangkat tidak mendukung geolokasi.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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<{
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<{
|
||||
|
||||
@ -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);
|
||||
}),
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user