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">
|
<script setup lang="ts">
|
||||||
import { useForm } from '@inertiajs/vue3'
|
import { Button } from '@/components/ui/button';
|
||||||
import type { HTMLAttributes } from "vue"
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { toast } from 'vue-sonner'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { Card, CardContent } from '@/components/ui/card'
|
|
||||||
import {
|
import {
|
||||||
Field, FieldError,
|
Field,
|
||||||
|
FieldError,
|
||||||
FieldGroup,
|
FieldGroup,
|
||||||
FieldLabel
|
FieldLabel,
|
||||||
} from '@/components/ui/field'
|
} from '@/components/ui/field';
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input';
|
||||||
import { formErrors } from '@/lib/form'
|
import { formErrors } from '@/lib/form';
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/utils';
|
||||||
import FieldSeparator from './ui/field/FieldSeparator.vue'
|
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<{
|
const props = defineProps<{
|
||||||
class?: HTMLAttributes["class"]
|
class?: HTMLAttributes['class'];
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
|
const showPassword = ref(false);
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
login: '',
|
login: '',
|
||||||
password: '',
|
password: '',
|
||||||
})
|
});
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
form.post('/auth/login', {
|
form.post('/auth/login', {
|
||||||
onError: (errors) => {
|
onError: (errors) => {
|
||||||
const message = errors.login
|
const message =
|
||||||
?? errors.password
|
errors.login ??
|
||||||
?? 'Gagal masuk. Periksa kembali data Anda.';
|
errors.password ??
|
||||||
|
'Gagal masuk. Periksa kembali data Anda.';
|
||||||
|
|
||||||
toast.error(message);
|
toast.error(message);
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -42,33 +47,58 @@ function submit() {
|
|||||||
<CardContent class="grid p-0 md:grid-cols-2">
|
<CardContent class="grid p-0 md:grid-cols-2">
|
||||||
<form class="p-6 md:p-8" @submit.prevent="submit">
|
<form class="p-6 md:p-8" @submit.prevent="submit">
|
||||||
<FieldGroup>
|
<FieldGroup>
|
||||||
<div class="flex flex-col items-center gap-2 text-center">
|
<div
|
||||||
<h1 class="text-2xl font-bold">
|
class="flex flex-col items-center gap-2 text-center"
|
||||||
Selamat Datang
|
>
|
||||||
</h1>
|
<h1 class="text-2xl font-bold">Selamat Datang</h1>
|
||||||
<p class="text-muted-foreground text-balance">
|
<p class="text-balance text-muted-foreground">
|
||||||
Silakan masukkan email dan username untuk akses dasbor.
|
Silakan masukkan email dan username untuk akses
|
||||||
|
dasbor.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Field>
|
<Field>
|
||||||
<FieldLabel for="login" required>
|
<FieldLabel for="login" required>
|
||||||
Email/Username
|
Email/Username
|
||||||
</FieldLabel>
|
</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')" />
|
<FieldError :errors="formErrors(form, 'login')" />
|
||||||
</Field>
|
</Field>
|
||||||
<Field>
|
<Field>
|
||||||
<div class="flex items-center">
|
<FieldLabel for="password" required>
|
||||||
<FieldLabel for="password" required>
|
Kata Sandi
|
||||||
Kata Sandi
|
</FieldLabel>
|
||||||
</FieldLabel>
|
|
||||||
<a href="#" class="ml-auto text-sm underline-offset-2 hover:underline">
|
<div class="relative">
|
||||||
Lupa kata sandi?
|
<Input
|
||||||
</a>
|
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>
|
</div>
|
||||||
<Input id="password" v-model="form.password" type="password" placeholder="********"
|
|
||||||
required />
|
<FieldError
|
||||||
<FieldError :errors="formErrors(form, 'password')" />
|
:errors="formErrors(form, 'password')"
|
||||||
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field>
|
<Field>
|
||||||
<Button type="submit" :disabled="form.processing">
|
<Button type="submit" :disabled="form.processing">
|
||||||
@ -76,24 +106,33 @@ function submit() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<FieldSeparator class="*:data-[slot=field-separator-content]:bg-card">
|
<FieldSeparator
|
||||||
|
class="*:data-[slot=field-separator-content]:bg-card"
|
||||||
|
>
|
||||||
Atau lanjutkan dengan
|
Atau lanjutkan dengan
|
||||||
</FieldSeparator>
|
</FieldSeparator>
|
||||||
<Field class="grid gap-4">
|
<Field class="grid gap-4">
|
||||||
<Button variant="outline" type="button">
|
<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
|
<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"
|
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>
|
</svg>
|
||||||
<span>Google</span>
|
<span>Google</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Field>
|
</Field>
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
</form>
|
</form>
|
||||||
<div class="bg-muted relative hidden md:block">
|
<div class="relative hidden bg-muted md:block">
|
||||||
<img src="https://images.pexels.com/photos/37080685/pexels-photo-37080685.jpeg" alt="Image"
|
<img
|
||||||
class="absolute inset-0 h-full w-full object-cover">
|
src="https://images.pexels.com/photos/37080685/pexels-photo-37080685.jpeg"
|
||||||
|
alt="Image"
|
||||||
|
class="absolute inset-0 h-full w-full object-cover"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { useForm } from '@inertiajs/vue3';
|
|||||||
import { Save } from '@lucide/vue';
|
import { Save } from '@lucide/vue';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
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 { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -18,8 +20,6 @@ import {
|
|||||||
FieldLabel,
|
FieldLabel,
|
||||||
FieldSet,
|
FieldSet,
|
||||||
} from '@/components/ui/field';
|
} 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 { Textarea } from '@/components/ui/textarea';
|
||||||
import { useFormDialog } from '@/composables/useFormDialog';
|
import { useFormDialog } from '@/composables/useFormDialog';
|
||||||
import { FIELD_LIMITS } from '@/lib/field-limits';
|
import { FIELD_LIMITS } from '@/lib/field-limits';
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { ColumnDef } from '@tanstack/vue-table';
|
import type { ColumnDef } from '@tanstack/vue-table';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import DataTableActions from '@/components/admin/finance/cash/data-table-actions.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 { DataTableColumnHeader } from '@/components/data-table';
|
||||||
|
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import type { CashTransactionListItem } from '@/types/cash';
|
import type { CashTransactionListItem } from '@/types/cash';
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { useForm } from '@inertiajs/vue3';
|
|||||||
import { Save } from '@lucide/vue';
|
import { Save } from '@lucide/vue';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
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 { Button } from '@/components/ui/button';
|
||||||
import { DatePicker } from '@/components/ui/date-picker';
|
import { DatePicker } from '@/components/ui/date-picker';
|
||||||
import {
|
import {
|
||||||
@ -19,8 +21,6 @@ import {
|
|||||||
FieldLabel,
|
FieldLabel,
|
||||||
FieldSet,
|
FieldSet,
|
||||||
} from '@/components/ui/field';
|
} 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 { Textarea } from '@/components/ui/textarea';
|
||||||
import { useFormDialog } from '@/composables/useFormDialog';
|
import { useFormDialog } from '@/composables/useFormDialog';
|
||||||
import { FIELD_LIMITS } from '@/lib/field-limits';
|
import { FIELD_LIMITS } from '@/lib/field-limits';
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { ColumnDef } from '@tanstack/vue-table';
|
import type { ColumnDef } from '@tanstack/vue-table';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import DataTableActions from '@/components/admin/finance/employee-advances/data-table-actions.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 { DataTableColumnHeader } from '@/components/data-table';
|
||||||
|
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import type { EmployeeAdvanceListItem } from '@/types/employee-advance';
|
import type { EmployeeAdvanceListItem } from '@/types/employee-advance';
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { useForm } from '@inertiajs/vue3';
|
|||||||
import { Save } from '@lucide/vue';
|
import { Save } from '@lucide/vue';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
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 { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -18,8 +20,6 @@ import {
|
|||||||
FieldLabel,
|
FieldLabel,
|
||||||
FieldSet,
|
FieldSet,
|
||||||
} from '@/components/ui/field';
|
} 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 { Textarea } from '@/components/ui/textarea';
|
||||||
import { useFormDialog } from '@/composables/useFormDialog';
|
import { useFormDialog } from '@/composables/useFormDialog';
|
||||||
import { FIELD_LIMITS } from '@/lib/field-limits';
|
import { FIELD_LIMITS } from '@/lib/field-limits';
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { ColumnDef } from '@tanstack/vue-table';
|
import type { ColumnDef } from '@tanstack/vue-table';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import DataTableActions from '@/components/admin/finance/expenses/data-table-actions.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 { DataTableColumnHeader } from '@/components/data-table';
|
||||||
|
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
||||||
import type { ExpenseListItem } from '@/types/expense';
|
import type { ExpenseListItem } from '@/types/expense';
|
||||||
|
|
||||||
export function createColumns(onEdit: (expense: ExpenseListItem) => void): ColumnDef<ExpenseListItem>[] {
|
export function createColumns(onEdit: (expense: ExpenseListItem) => void): ColumnDef<ExpenseListItem>[] {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useForm } from '@inertiajs/vue3';
|
|||||||
import { Save } from '@lucide/vue';
|
import { Save } from '@lucide/vue';
|
||||||
import { watch } from 'vue';
|
import { watch } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
|
import { RupiahInput } from '@/components/form/rupiah-input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -20,7 +21,6 @@ import {
|
|||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { FIELD_LIMITS } from '@/lib/field-limits';
|
import { FIELD_LIMITS } from '@/lib/field-limits';
|
||||||
import { parseRupiah } from '@/lib/rupiah';
|
import { parseRupiah } from '@/lib/rupiah';
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
import { useForm } from '@inertiajs/vue3';
|
import { useForm } from '@inertiajs/vue3';
|
||||||
import { Save } from '@lucide/vue';
|
import { Save } from '@lucide/vue';
|
||||||
import { toast } from 'vue-sonner';
|
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 { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { DatePicker } from '@/components/ui/date-picker';
|
import { DatePicker } from '@/components/ui/date-picker';
|
||||||
@ -14,9 +16,7 @@ import {
|
|||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { PhoneNumberInput } from '@/components/form/phone-number-input';
|
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
checkInPhotoUrl: string | null;
|
checkInPhotoUrl: string | null;
|
||||||
checkOutPhotoUrl: string | null;
|
checkOutPhotoUrl: string | null;
|
||||||
checkInLocationTag?: string | null;
|
checkInLocationTag?: string | null;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { ColumnDef } from '@tanstack/vue-table';
|
import type { ColumnDef } from '@tanstack/vue-table';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { DataTableColumnHeader } from '@/components/data-table';
|
|
||||||
import DataTableActions from '@/components/admin/hr/employees/data-table-actions.vue';
|
import DataTableActions from '@/components/admin/hr/employees/data-table-actions.vue';
|
||||||
import EmployeeStatusToggle from '@/components/admin/hr/employees/employee-status-toggle.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 { Badge } from '@/components/ui/badge';
|
||||||
import type { EmployeeListItem } from '@/types/employee';
|
import type { EmployeeListItem } from '@/types/employee';
|
||||||
|
|
||||||
|
|||||||
@ -19,11 +19,11 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table';
|
} from '@/components/ui/table';
|
||||||
|
import type { CuttingListItem } from '@/types/cutting';
|
||||||
import type {
|
import type {
|
||||||
DataTablePagination,
|
DataTablePagination,
|
||||||
DataTablePaginationLink,
|
DataTablePaginationLink,
|
||||||
} from '@/types/data-table';
|
} from '@/types/data-table';
|
||||||
import type { CuttingListItem } from '@/types/cutting';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
cuttings: CuttingListItem[];
|
cuttings: CuttingListItem[];
|
||||||
@ -104,6 +104,7 @@ function getGroupedMaterials(materials: any[]): GroupedCuttingMaterials[] {
|
|||||||
items: [],
|
items: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
groups[rawMaterialId].items.push(item);
|
groups[rawMaterialId].items.push(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -125,6 +126,7 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
|
|||||||
items: [],
|
items: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
groups[productId].items.push(item);
|
groups[productId].items.push(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<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 PosCatalogCard from '@/components/admin/manage/PosCatalogCard.vue';
|
||||||
import PosCatalogVariantThumb from '@/components/admin/manage/PosCatalogVariantThumb.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 { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -18,8 +24,6 @@ import {
|
|||||||
FieldSet,
|
FieldSet,
|
||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { Input } from '@/components/ui/input';
|
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 { Separator } from '@/components/ui/separator';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { getFirstCoverImage } from '@/lib/catalog-cover';
|
import { getFirstCoverImage } from '@/lib/catalog-cover';
|
||||||
@ -31,10 +35,6 @@ import type {
|
|||||||
CuttingRawMaterialCatalogItem,
|
CuttingRawMaterialCatalogItem,
|
||||||
CuttingResultCartItem,
|
CuttingResultCartItem,
|
||||||
} from '@/types/cutting';
|
} 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<{
|
const props = defineProps<{
|
||||||
rawMaterialCatalog: CuttingRawMaterialCatalogItem[];
|
rawMaterialCatalog: CuttingRawMaterialCatalogItem[];
|
||||||
@ -124,9 +124,11 @@ function decreaseMaterialQty(priceId: number) {
|
|||||||
const index = materialCart.value.findIndex(
|
const index = materialCart.value.findIndex(
|
||||||
(item) => item.raw_material_price_id === priceId,
|
(item) => item.raw_material_price_id === priceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
const item = materialCart.value[index];
|
const item = materialCart.value[index];
|
||||||
const nextQty = (Number(item.material_usage) || 0) - 1;
|
const nextQty = (Number(item.material_usage) || 0) - 1;
|
||||||
|
|
||||||
if (nextQty <= 0) {
|
if (nextQty <= 0) {
|
||||||
removeMaterial(index);
|
removeMaterial(index);
|
||||||
} else {
|
} else {
|
||||||
@ -147,6 +149,7 @@ function addMaterial(
|
|||||||
existing.material_usage = String(
|
existing.material_usage = String(
|
||||||
(Number(existing.material_usage) || 0) + 1,
|
(Number(existing.material_usage) || 0) + 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,9 +181,11 @@ function decreaseResultQty(variantId: number) {
|
|||||||
const index = resultCart.value.findIndex(
|
const index = resultCart.value.findIndex(
|
||||||
(item) => item.product_variant_id === variantId,
|
(item) => item.product_variant_id === variantId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
const item = resultCart.value[index];
|
const item = resultCart.value[index];
|
||||||
const nextQty = (Number(item.cutting_result) || 0) - 1;
|
const nextQty = (Number(item.cutting_result) || 0) - 1;
|
||||||
|
|
||||||
if (nextQty <= 0) {
|
if (nextQty <= 0) {
|
||||||
removeResult(index);
|
removeResult(index);
|
||||||
} else {
|
} else {
|
||||||
@ -210,6 +215,7 @@ function addResult(
|
|||||||
(Number(existing.warehouse_stock) || 0) + 1,
|
(Number(existing.warehouse_stock) || 0) + 1,
|
||||||
);
|
);
|
||||||
syncResultTotals(existing);
|
syncResultTotals(existing);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<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 ConfirmDialog from '@/components/ConfirmDialog.vue';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -26,10 +30,6 @@ import {
|
|||||||
import { useCan } from '@/composables/useCan';
|
import { useCan } from '@/composables/useCan';
|
||||||
import { FIELD_LIMITS } from '@/lib/field-limits';
|
import { FIELD_LIMITS } from '@/lib/field-limits';
|
||||||
import type { CuttingListItem, CuttingStatusAction } from '@/types/cutting';
|
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<{
|
const props = defineProps<{
|
||||||
cutting: CuttingListItem;
|
cutting: CuttingListItem;
|
||||||
@ -108,6 +108,7 @@ function statusConfirmDescription(action: CuttingStatusAction): string {
|
|||||||
function openStatusConfirm(action: CuttingStatusAction) {
|
function openStatusConfirm(action: CuttingStatusAction) {
|
||||||
if (action.status === 'verified') {
|
if (action.status === 'verified') {
|
||||||
verifyDialogOpen.value = true;
|
verifyDialogOpen.value = true;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { computed, ref, watch } from 'vue';
|
|||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import PosCatalogCard from '@/components/admin/manage/PosCatalogCard.vue';
|
import PosCatalogCard from '@/components/admin/manage/PosCatalogCard.vue';
|
||||||
import PosCatalogVariantThumb from '@/components/admin/manage/PosCatalogVariantThumb.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 { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
@ -21,8 +23,6 @@ import {
|
|||||||
FieldSet,
|
FieldSet,
|
||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { NumberInput } from '@/components/form/number-input';
|
|
||||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@ -185,6 +185,7 @@ function getCartItem(variantId: number): OrderCartItem | undefined {
|
|||||||
|
|
||||||
async function decreaseVariantQty(variantId: number) {
|
async function decreaseVariantQty(variantId: number) {
|
||||||
const index = cart.value.findIndex((item) => item.product_variant_id === variantId);
|
const index = cart.value.findIndex((item) => item.product_variant_id === variantId);
|
||||||
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
await adjustQuantity(index, -1);
|
await adjustQuantity(index, -1);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,6 +83,7 @@ function getGroupedItems(items: any[]): GroupedPurchaseItems[] {
|
|||||||
items: [],
|
items: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
groups[rawMaterialId].items.push(item);
|
groups[rawMaterialId].items.push(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,9 @@ import { computed, ref, watch } from 'vue';
|
|||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import PosCatalogCard from '@/components/admin/manage/PosCatalogCard.vue';
|
import PosCatalogCard from '@/components/admin/manage/PosCatalogCard.vue';
|
||||||
import PosCatalogVariantThumb from '@/components/admin/manage/PosCatalogVariantThumb.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 { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -21,10 +24,7 @@ import {
|
|||||||
FieldLabel,
|
FieldLabel,
|
||||||
FieldSet,
|
FieldSet,
|
||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { ImageUploadField } from '@/components/form/image-upload-field';
|
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { DecimalInput } from '@/components/form/decimal-input';
|
|
||||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@ -162,6 +162,7 @@ function getCartItem(priceId: number): PurchaseCartItem | undefined {
|
|||||||
|
|
||||||
async function decreasePriceQty(priceId: number) {
|
async function decreasePriceQty(priceId: number) {
|
||||||
const index = cart.value.findIndex((item) => item.raw_material_price_id === priceId);
|
const index = cart.value.findIndex((item) => item.raw_material_price_id === priceId);
|
||||||
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
await adjustQuantity(index, -1);
|
await adjustQuantity(index, -1);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useForm } from '@inertiajs/vue3';
|
|||||||
import { Save } from '@lucide/vue';
|
import { Save } from '@lucide/vue';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
|
import { PhoneNumberInput } from '@/components/form/phone-number-input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -19,7 +20,6 @@ import {
|
|||||||
FieldSet,
|
FieldSet,
|
||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { PhoneNumberInput } from '@/components/form/phone-number-input';
|
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { useFormDialog } from '@/composables/useFormDialog';
|
import { useFormDialog } from '@/composables/useFormDialog';
|
||||||
import { FIELD_LIMITS } from '@/lib/field-limits';
|
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 { Copy, Plus, Save, Trash2 } from '@lucide/vue';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
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 { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
@ -12,10 +15,7 @@ import {
|
|||||||
FieldLabel,
|
FieldLabel,
|
||||||
FieldSet,
|
FieldSet,
|
||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { MultipleImageUploadField } from '@/components/form/image-upload-field';
|
|
||||||
import { Input } from '@/components/ui/input';
|
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 { Textarea } from '@/components/ui/textarea';
|
||||||
import { FIELD_LIMITS } from '@/lib/field-limits';
|
import { FIELD_LIMITS } from '@/lib/field-limits';
|
||||||
import { formErrors } from '@/lib/form';
|
import { formErrors } from '@/lib/form';
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import { Link } from '@inertiajs/vue3';
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import DataTableActions from '@/components/admin/master/products/data-table-actions.vue';
|
import DataTableActions from '@/components/admin/master/products/data-table-actions.vue';
|
||||||
import ProductStatusToggle from '@/components/admin/master/products/product-status-toggle.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 DataTableToolbar from '@/components/data-table/DataTableToolbar.vue';
|
||||||
|
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
@ -28,9 +28,10 @@ import type {
|
|||||||
} from '@/types/data-table';
|
} from '@/types/data-table';
|
||||||
import {
|
import {
|
||||||
PRICE_TYPES,
|
PRICE_TYPES,
|
||||||
PRICE_TYPE_LABELS,
|
PRICE_TYPE_LABELS
|
||||||
type ProductListItem,
|
|
||||||
} from '@/types/product';
|
} from '@/types/product';
|
||||||
|
import type {ProductListItem} from '@/types/product';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
products: ProductListItem[];
|
products: ProductListItem[];
|
||||||
|
|||||||
@ -3,6 +3,9 @@ import { useForm } from '@inertiajs/vue3';
|
|||||||
import { Copy, Plus, Save, Trash2 } from '@lucide/vue';
|
import { Copy, Plus, Save, Trash2 } from '@lucide/vue';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
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 { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
@ -12,10 +15,7 @@ import {
|
|||||||
FieldLabel,
|
FieldLabel,
|
||||||
FieldSet,
|
FieldSet,
|
||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { MultipleImageUploadField } from '@/components/form/image-upload-field';
|
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { DecimalInput } from '@/components/form/decimal-input';
|
|
||||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import { Link } from '@inertiajs/vue3';
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import DataTableActions from '@/components/admin/master/raw-materials/data-table-actions.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 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 DataTableToolbar from '@/components/data-table/DataTableToolbar.vue';
|
||||||
|
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useForm } from '@inertiajs/vue3';
|
|||||||
import { Save } from '@lucide/vue';
|
import { Save } from '@lucide/vue';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
|
import { PhoneNumberInput } from '@/components/form/phone-number-input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -19,7 +20,6 @@ import {
|
|||||||
FieldSet,
|
FieldSet,
|
||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { PhoneNumberInput } from '@/components/form/phone-number-input';
|
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { useFormDialog } from '@/composables/useFormDialog';
|
import { useFormDialog } from '@/composables/useFormDialog';
|
||||||
import { FIELD_LIMITS } from '@/lib/field-limits';
|
import { FIELD_LIMITS } from '@/lib/field-limits';
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { RupiahInput } from '@/components/form/rupiah-input';
|
||||||
import {
|
import {
|
||||||
Field,
|
Field,
|
||||||
FieldError,
|
FieldError,
|
||||||
FieldLabel,
|
FieldLabel,
|
||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
import { router, useForm } from '@inertiajs/vue3';
|
import { router, useForm } from '@inertiajs/vue3';
|
||||||
import { Save } from '@lucide/vue';
|
import { Save } from '@lucide/vue';
|
||||||
import { toast } from 'vue-sonner';
|
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 { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Field, FieldError, FieldGroup, FieldLabel, FieldSet } from '@/components/ui/field';
|
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 { Input } from '@/components/ui/input';
|
||||||
import { PhoneNumberInput } from '@/components/form/phone-number-input';
|
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { formErrors } from '@/lib/form';
|
import { formErrors } from '@/lib/form';
|
||||||
import type { SystemSettingsData } from '@/types/setting';
|
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)
|
// Formats JS float string/number (e.g. 1250.75) to ID decimal string (e.g. 1250,75)
|
||||||
function formatDecimal(value: string | number): string {
|
function formatDecimal(value: string | number): string {
|
||||||
const str = String(value ?? '').trim();
|
const str = String(value ?? '').trim();
|
||||||
if (!str) return '';
|
|
||||||
|
if (!str) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Normalize to dot for processing
|
// Normalize to dot for processing
|
||||||
const normalized = str.replace(',', '.');
|
const normalized = str.replace(',', '.');
|
||||||
if (isNaN(parseFloat(normalized))) return '';
|
|
||||||
|
if (isNaN(parseFloat(normalized))) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Split into integer and decimal parts
|
// Split into integer and decimal parts
|
||||||
const parts = normalized.split('.');
|
const parts = normalized.split('.');
|
||||||
@ -31,8 +37,10 @@ function formatDecimal(value: string | number): string {
|
|||||||
|
|
||||||
if (decimalPart !== undefined) {
|
if (decimalPart !== undefined) {
|
||||||
decimalPart = decimalPart.slice(0, 2);
|
decimalPart = decimalPart.slice(0, 2);
|
||||||
|
|
||||||
return `${integerPart},${decimalPart}`;
|
return `${integerPart},${decimalPart}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return integerPart;
|
return integerPart;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +58,7 @@ function parseDecimal(value: string): string {
|
|||||||
let clean = normalized.replace(/\./g, '').replace(',', '.');
|
let clean = normalized.replace(/\./g, '').replace(',', '.');
|
||||||
|
|
||||||
const parts = clean.split('.');
|
const parts = clean.split('.');
|
||||||
|
|
||||||
if (parts.length > 2) {
|
if (parts.length > 2) {
|
||||||
clean = parts[0] + '.' + parts.slice(1).join('');
|
clean = parts[0] + '.' + parts.slice(1).join('');
|
||||||
}
|
}
|
||||||
@ -57,6 +66,7 @@ function parseDecimal(value: string): string {
|
|||||||
clean = clean.replace(/[^0-9.]/g, '');
|
clean = clean.replace(/[^0-9.]/g, '');
|
||||||
|
|
||||||
const cleanParts = clean.split('.');
|
const cleanParts = clean.split('.');
|
||||||
|
|
||||||
if (cleanParts[1] !== undefined) {
|
if (cleanParts[1] !== undefined) {
|
||||||
clean = `${cleanParts[0]}.${cleanParts[1].slice(0, 2)}`;
|
clean = `${cleanParts[0]}.${cleanParts[1].slice(0, 2)}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,11 @@ const emit = defineEmits<{
|
|||||||
function formatNumber(value: string | number): string {
|
function formatNumber(value: string | number): string {
|
||||||
const str = String(value ?? '').trim();
|
const str = String(value ?? '').trim();
|
||||||
const clean = str.replace(/\D/g, '');
|
const clean = str.replace(/\D/g, '');
|
||||||
if (!clean) return '';
|
|
||||||
|
if (!clean) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
return Number(clean).toLocaleString('id-ID');
|
return Number(clean).toLocaleString('id-ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { router } from '@inertiajs/vue3';
|
import { router } from '@inertiajs/vue3';
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
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';
|
import type { DataTableQuery, DataTableSort } from '@/types/data-table';
|
||||||
|
|
||||||
type UseDataTableQueryOptions = {
|
type UseDataTableQueryOptions = {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export function useGeolocation() {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!navigator.geolocation) {
|
if (!navigator.geolocation) {
|
||||||
reject(new Error('Perangkat tidak mendukung geolokasi.'));
|
reject(new Error('Perangkat tidak mendukung geolokasi.'));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ function urlBase64ToUint8Array(base64String: string): Uint8Array {
|
|||||||
.replace(/-/g, '+')
|
.replace(/-/g, '+')
|
||||||
.replace(/_/g, '/');
|
.replace(/_/g, '/');
|
||||||
const rawData = atob(base64);
|
const rawData = atob(base64);
|
||||||
|
|
||||||
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
|
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +27,9 @@ export function usePushNotification() {
|
|||||||
.vapidPublicKey as string | undefined;
|
.vapidPublicKey as string | undefined;
|
||||||
|
|
||||||
async function checkSubscriptionStatus(): Promise<void> {
|
async function checkSubscriptionStatus(): Promise<void> {
|
||||||
if (!isSupported.value) return;
|
if (!isSupported.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const registration = await navigator.serviceWorker.ready;
|
const registration = await navigator.serviceWorker.ready;
|
||||||
@ -39,10 +42,15 @@ export function usePushNotification() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function subscribe(): Promise<void> {
|
async function subscribe(): Promise<void> {
|
||||||
if (!isSupported.value || isLoading.value) return;
|
if (!isSupported.value || isLoading.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const permission = await Notification.requestPermission();
|
const permission = await Notification.requestPermission();
|
||||||
if (permission !== 'granted') return;
|
|
||||||
|
if (permission !== 'granted') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
@ -85,7 +93,9 @@ export function usePushNotification() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function unsubscribe(): Promise<void> {
|
async function unsubscribe(): Promise<void> {
|
||||||
if (!isSupported.value || isLoading.value) return;
|
if (!isSupported.value || isLoading.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
@ -96,6 +106,7 @@ export function usePushNotification() {
|
|||||||
|
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
isSubscribed.value = false;
|
isSubscribed.value = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { usePage } from '@inertiajs/vue3';
|
import { usePage } from '@inertiajs/vue3';
|
||||||
import AppSidebar from '@/components/AppSidebar.vue';
|
import AppSidebar from '@/components/AppSidebar.vue';
|
||||||
import UserNav from '@/components/UserNav.vue';
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import {
|
import {
|
||||||
SidebarInset,
|
SidebarInset,
|
||||||
SidebarProvider,
|
SidebarProvider,
|
||||||
SidebarTrigger,
|
SidebarTrigger,
|
||||||
} from '@/components/ui/sidebar';
|
} from '@/components/ui/sidebar';
|
||||||
|
import UserNav from '@/components/UserNav.vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import { Head, useForm } from '@inertiajs/vue3';
|
import { Head, useForm } from '@inertiajs/vue3';
|
||||||
import { Save } from '@lucide/vue';
|
import { Save } from '@lucide/vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
|
import { PhoneNumberInput } from '@/components/form/phone-number-input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { DatePicker } from '@/components/ui/date-picker';
|
import { DatePicker } from '@/components/ui/date-picker';
|
||||||
@ -13,7 +14,6 @@ import {
|
|||||||
FieldSet,
|
FieldSet,
|
||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { PhoneNumberInput } from '@/components/form/phone-number-input';
|
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import AccountLayout from '@/layouts/AccountLayout.vue';
|
import AccountLayout from '@/layouts/AccountLayout.vue';
|
||||||
|
|||||||
@ -2,16 +2,16 @@
|
|||||||
import { Head } from '@inertiajs/vue3';
|
import { Head } from '@inertiajs/vue3';
|
||||||
import { Plus } from '@lucide/vue';
|
import { Plus } from '@lucide/vue';
|
||||||
import { computed, ref, watch } from '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 { createColumns } from '@/components/admin/finance/expenses/columns';
|
||||||
|
import ExpenseFormModal from '@/components/admin/finance/expenses/ExpenseFormModal.vue';
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { useCan } from '@/composables/useCan';
|
import { useCan } from '@/composables/useCan';
|
||||||
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
||||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||||
import type { ExpenseListItem, PaginatedExpenses } from '@/types/expense';
|
|
||||||
import type { DataTableSort } from '@/types/data-table';
|
import type { DataTableSort } from '@/types/data-table';
|
||||||
|
import type { ExpenseListItem, PaginatedExpenses } from '@/types/expense';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
expenses: PaginatedExpenses;
|
expenses: PaginatedExpenses;
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
import { Head, Link } from '@inertiajs/vue3';
|
import { Head, Link } from '@inertiajs/vue3';
|
||||||
import { Plus } from '@lucide/vue';
|
import { Plus } from '@lucide/vue';
|
||||||
import { computed, ref, watch } from '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 CuttingGroupedTable from '@/components/admin/manage/cuttings/CuttingGroupedTable.vue';
|
||||||
|
import CuttingInProgressSection from '@/components/admin/manage/cuttings/CuttingInProgressSection.vue';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { useCan } from '@/composables/useCan';
|
import { useCan } from '@/composables/useCan';
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Head } from '@inertiajs/vue3';
|
|||||||
import { Plus } from '@lucide/vue';
|
import { Plus } from '@lucide/vue';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { createColumns } from '@/components/admin/master/customers/columns';
|
import { createColumns } from '@/components/admin/master/customers/columns';
|
||||||
|
import CustomerFormModal from '@/components/admin/master/customers/CustomerFormModal.vue';
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
@ -10,7 +11,6 @@ import { useCan } from '@/composables/useCan';
|
|||||||
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
||||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||||
import type { DataTableSort } from '@/types/data-table';
|
import type { DataTableSort } from '@/types/data-table';
|
||||||
import CustomerFormModal from '@/components/admin/master/customers/CustomerFormModal.vue';
|
|
||||||
import type { CustomerListItem, PaginatedCustomers } from '@/types/customer';
|
import type { CustomerListItem, PaginatedCustomers } from '@/types/customer';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<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 MasterOutOfStockCatalogSection from '@/components/admin/master/MasterOutOfStockCatalogSection.vue';
|
||||||
import ProductGroupedTable from '@/components/admin/master/products/ProductGroupedTable.vue';
|
import ProductGroupedTable from '@/components/admin/master/products/ProductGroupedTable.vue';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -15,9 +18,6 @@ import type {
|
|||||||
ProductOutOfStockGroup,
|
ProductOutOfStockGroup,
|
||||||
ProductStockWarningGroup,
|
ProductStockWarningGroup,
|
||||||
} from '@/types/product';
|
} from '@/types/product';
|
||||||
import { Head, Link } from '@inertiajs/vue3';
|
|
||||||
import { Plus } from '@lucide/vue';
|
|
||||||
import { computed, ref, watch } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
products: PaginatedProducts;
|
products: PaginatedProducts;
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<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 MasterOutOfStockCatalogSection from '@/components/admin/master/MasterOutOfStockCatalogSection.vue';
|
||||||
import RawMaterialGroupedTable from '@/components/admin/master/raw-materials/RawMaterialGroupedTable.vue';
|
import RawMaterialGroupedTable from '@/components/admin/master/raw-materials/RawMaterialGroupedTable.vue';
|
||||||
import { Button } from '@/components/ui/button';
|
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 { DataTableFilterDef } from '@/types/data-table';
|
||||||
import type { MasterOutOfStockGroup } from '@/types/master-inventory';
|
import type { MasterOutOfStockGroup } from '@/types/master-inventory';
|
||||||
import type { PaginatedRawMaterials } from '@/types/raw-material';
|
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<{
|
const props = defineProps<{
|
||||||
rawMaterials: PaginatedRawMaterials;
|
rawMaterials: PaginatedRawMaterials;
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
import { Head } from '@inertiajs/vue3';
|
import { Head } from '@inertiajs/vue3';
|
||||||
import { Plus } from '@lucide/vue';
|
import { Plus } from '@lucide/vue';
|
||||||
import { computed, ref, watch } from '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 { DataTable } from '@/components/data-table';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
@ -9,8 +11,6 @@ import { useCan } from '@/composables/useCan';
|
|||||||
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
||||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||||
import type { DataTableSort } from '@/types/data-table';
|
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';
|
import type { SupplierListItem, PaginatedSuppliers } from '@/types/supplier';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
|
|
||||||
const sw = self as unknown as ServiceWorkerGlobalScope;
|
const sw = self as unknown as ServiceWorkerGlobalScope;
|
||||||
|
|
||||||
@ -35,7 +35,9 @@ sw.addEventListener('activate', (event: Event) => {
|
|||||||
sw.addEventListener('push', (event: Event) => {
|
sw.addEventListener('push', (event: Event) => {
|
||||||
const pushEvent = event as PushEvent;
|
const pushEvent = event as PushEvent;
|
||||||
|
|
||||||
if (!pushEvent.data) return;
|
if (!pushEvent.data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let data: { title?: string; body?: string; icon?: string; url?: string } =
|
let data: { title?: string; body?: string; icon?: string; url?: string } =
|
||||||
{};
|
{};
|
||||||
@ -81,6 +83,7 @@ sw.addEventListener('notificationclick', (event: Event) => {
|
|||||||
.then((c) => c?.focus());
|
.then((c) => c?.focus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sw.clients.openWindow(targetUrl);
|
return sw.clients.openWindow(targetUrl);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user