243 lines
8.3 KiB
Vue
243 lines
8.3 KiB
Vue
<script setup lang="ts">
|
|
import { Save } from '@lucide/vue';
|
|
import { computed, reactive, ref, watch } from 'vue';
|
|
import { toast } from 'vue-sonner';
|
|
import { NumberInput } from '@/components/form/number-input';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import {
|
|
Field,
|
|
FieldError,
|
|
FieldGroup,
|
|
FieldLabel,
|
|
FieldSet,
|
|
} from '@/components/ui/field';
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectGroup,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { apiFetch } from '@/lib/api';
|
|
import { transfer } from '@/routes/admin/manage/retail-stock';
|
|
|
|
interface VariantOption {
|
|
id: number;
|
|
name: string;
|
|
stock: number;
|
|
retail_stock: number;
|
|
}
|
|
|
|
const open = defineModel<boolean>('open', { default: false });
|
|
|
|
const props = defineProps<{
|
|
variantId: number;
|
|
variantName: string;
|
|
productName: string;
|
|
goodStock: number;
|
|
retailStock: number;
|
|
variants?: VariantOption[];
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
submitted: [];
|
|
}>();
|
|
|
|
const processing = ref(false);
|
|
const errors = reactive<Record<string, string>>({});
|
|
|
|
const selectedVariantId = ref(0);
|
|
|
|
const hasMultipleVariants = computed(() => (props.variants?.length ?? 0) > 1);
|
|
|
|
const activeVariant = computed(() => {
|
|
if (hasMultipleVariants.value && selectedVariantId.value > 0) {
|
|
return props.variants?.find((v) => v.id === selectedVariantId.value);
|
|
}
|
|
|
|
return undefined;
|
|
});
|
|
|
|
const displayVariantName = computed(() => activeVariant.value?.name ?? props.variantName);
|
|
const displayGoodStock = computed(() => activeVariant.value?.stock ?? props.goodStock);
|
|
const displayRetailStock = computed(() => activeVariant.value?.retail_stock ?? props.retailStock);
|
|
|
|
const form = reactive({
|
|
quantity: '',
|
|
notes: '',
|
|
});
|
|
|
|
function resetForm() {
|
|
form.quantity = '';
|
|
form.notes = '';
|
|
Object.keys(errors).forEach((key) => delete errors[key]);
|
|
|
|
if (hasMultipleVariants.value) {
|
|
selectedVariantId.value = 0;
|
|
}
|
|
}
|
|
|
|
watch(open, (isOpen) => {
|
|
if (isOpen) {
|
|
resetForm();
|
|
|
|
if (!hasMultipleVariants.value && props.variantId > 0) {
|
|
selectedVariantId.value = props.variantId;
|
|
}
|
|
}
|
|
});
|
|
|
|
const submitVariantId = computed(() => {
|
|
if (hasMultipleVariants.value) {
|
|
return selectedVariantId.value;
|
|
}
|
|
|
|
return props.variantId;
|
|
});
|
|
|
|
async function submit() {
|
|
Object.keys(errors).forEach((key) => delete errors[key]);
|
|
processing.value = true;
|
|
|
|
try {
|
|
await apiFetch(transfer.url(), {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
product_variant_id: submitVariantId.value,
|
|
quantity: Number(form.quantity),
|
|
notes: form.notes || null,
|
|
}),
|
|
});
|
|
|
|
emit('submitted');
|
|
open.value = false;
|
|
toast.success('Pengajuan transfer stok ecer berhasil dikirim. Menunggu verifikasi owner.');
|
|
} catch (error) {
|
|
const message =
|
|
error instanceof Error ? error.message : 'Gagal menyimpan data.';
|
|
|
|
if (message.includes('jumlah') || message.includes('Jumlah')) {
|
|
errors.quantity = message;
|
|
} else if (message.includes('Stok')) {
|
|
errors.quantity = message;
|
|
} else if (message.includes('pengajuan') || message.includes('menunggu')) {
|
|
errors.quantity = message;
|
|
} else {
|
|
toast.error(message);
|
|
}
|
|
} finally {
|
|
processing.value = false;
|
|
}
|
|
}
|
|
|
|
function formatNumber(value: number): string {
|
|
return value.toLocaleString('id-ID');
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Dialog v-model:open="open">
|
|
<DialogContent class="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle class="flex items-center gap-2">
|
|
Transfer Stok Ecer
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<div class="space-y-4">
|
|
<div class="rounded-lg border p-3 space-y-1.5">
|
|
<p class="text-sm font-medium">{{ productName }}</p>
|
|
|
|
<Field v-if="hasMultipleVariants">
|
|
<FieldLabel for="transfer-variant" required>Pilih Varian</FieldLabel>
|
|
<Select :model-value="selectedVariantId ? String(selectedVariantId) : undefined" @update:model-value="selectedVariantId = Number($event)">
|
|
<SelectTrigger id="transfer-variant" class="w-full">
|
|
<SelectValue placeholder="Pilih varian" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectGroup>
|
|
<SelectItem
|
|
v-for="variant in variants"
|
|
:key="variant.id"
|
|
:value="String(variant.id)"
|
|
>
|
|
{{ variant.name }}
|
|
</SelectItem>
|
|
</SelectGroup>
|
|
</SelectContent>
|
|
</Select>
|
|
</Field>
|
|
|
|
<p v-if="!hasMultipleVariants" class="text-xs text-muted-foreground">Varian: {{ displayVariantName }}</p>
|
|
<div class="flex justify-between text-xs pt-1">
|
|
<span class="text-muted-foreground">Stok Bagus</span>
|
|
<span class="font-medium tabular-nums">{{ formatNumber(displayGoodStock) }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-xs">
|
|
<span class="text-muted-foreground">Stok Ecer</span>
|
|
<span class="font-medium tabular-nums text-blue-600">{{ formatNumber(displayRetailStock) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-xs text-amber-600 bg-amber-50 border border-amber-200 rounded-md px-3 py-2">
|
|
Transfer stok memerlukan persetujuan owner. Stok akan berubah setelah disetujui.
|
|
</p>
|
|
|
|
<form @submit.prevent="submit">
|
|
<FieldGroup>
|
|
<FieldSet class="grid gap-4">
|
|
<Field>
|
|
<FieldLabel for="retail-quantity" required>Jumlah Transfer</FieldLabel>
|
|
<NumberInput
|
|
id="retail-quantity"
|
|
v-model="form.quantity"
|
|
:max="displayGoodStock"
|
|
placeholder="Masukkan jumlah"
|
|
/>
|
|
<FieldError
|
|
v-if="errors.quantity"
|
|
:errors="[errors.quantity]"
|
|
/>
|
|
</Field>
|
|
|
|
<Field>
|
|
<FieldLabel for="retail-notes">Catatan</FieldLabel>
|
|
<Textarea
|
|
id="retail-notes"
|
|
v-model="form.notes"
|
|
rows="2"
|
|
placeholder="Contoh: Transfer untuk kebutuhan kasir"
|
|
/>
|
|
</Field>
|
|
</FieldSet>
|
|
</FieldGroup>
|
|
|
|
<DialogFooter class="mt-6">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
:disabled="processing"
|
|
@click="open = false"
|
|
>
|
|
Batal
|
|
</Button>
|
|
<Button type="submit" :disabled="processing || !form.quantity || Number(form.quantity) < 1 || (hasMultipleVariants && !selectedVariantId)">
|
|
<Save class="size-4" />
|
|
{{ processing ? 'Mengirim...' : 'Ajukan Transfer' }}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</template>
|