Kasir/resources/js/pages/InputProduk.vue
2025-10-27 20:02:02 +07:00

300 lines
9.4 KiB
Vue

<template>
<mainLayout>
<!-- Modal Buat Item -->
<CreateItemModal :isOpen="openItemModal" :product="createdProduct" @close="closeItemModal" />
<div class="p-6">
<p class="font-serif italic text-[25px] text-D">Produk Baru</p>
<div class="flex flex-col md:flex-row mt-5 gap-6">
<!-- Form Section -->
<div class="flex-1">
<div class="mb-3">
<label class="block text-D mb-1">Nama Produk</label>
<InputField v-model="form.nama" type="text" placeholder="Masukkan nama produk"
@input="errors.nama = null" />
<p v-if="errors.nama" class="text-sm text-red-500 mt-1">{{ errors.nama[0] }}</p>
</div>
<div class="mb-3">
<label class="block text-D mb-1">Kategori</label>
<InputSelect v-model="form.id_kategori" :options="category" placeholder="Pilih kategori" />
</div>
<div class="mb-3 flex flex-row w-full gap-3">
<div class="flex-1">
<label class="block text-D mb-1">Berat (g)</label>
<InputField v-model="form.berat" type="number" step="0.01" placeholder="Masukkan berat"
@input="calculateHargaJual" />
</div>
<div class="flex-1">
<label class="block text-D mb-1">Kadar (K)</label>
<InputField v-model="form.kadar" type="number" placeholder="Masukkan kadar" />
<p v-if="errors.kadar" class="text-sm text-red-500 mt-1">{{ errors.kadar }}</p>
</div>
</div>
<div class="mb-3 flex flex-row w-full gap-3">
<div class="flex-1">
<label class="block text-D mb-1">Harga per Gram</label>
<InputField v-model="hargaPerGramFormatted" type="text" placeholder="Masukkan harga per gram"
@input="formatHargaPerGramInput" @keypress="onlyNumbers" />
</div>
<div class="flex-1">
<label class="block text-D mb-1">Harga Jual</label>
<InputField v-model="hargaJualFormatted" type="text" placeholder="Masukkan harga jual"
@input="formatHargaJualInput" @keypress="onlyNumbers" />
</div>
</div>
</div>
<!-- Image Upload Section -->
<div class="flex-1">
<PhotoUploader v-model="uploadedImages" :maxPhotos="6" @error="handleUploadError" />
</div>
</div>
<div class="mt-6 flex justify-end flex-row gap-3">
<button @click="back" class="px-6 py-2 rounded-md bg-gray-400 hover:bg-gray-500 text-white">Batal</button>
<button @click="submitForm(true)" :disabled="loading || !isFormValid"
class="bg-C text-D px-6 py-2 rounded-md hover:bg-B disabled:bg-B disabled:text-white disabled:cursor-not-allowed">
{{ loading ? 'Menyimpan...' : 'Tambah Item' }}
</button>
<button @click="submitForm(false)" :disabled="loading || !isFormValid"
class="bg-green-400 text-D px-6 py-2 rounded-md hover:bg-green-300 disabled:bg-green-300 disabled:text-white disabled:cursor-not-allowed">
{{ loading ? 'Menyimpan...' : 'Simpan' }}
</button>
</div>
</div>
<!-- Camera Modal -->
<div v-if="showCamera" class="fixed inset-0 bg-black/75 flex items-center justify-center z-[9999]">
<div class="bg-white w-[480px] rounded-lg shadow-lg p-4 relative">
<video ref="video" autoplay playsinline class="w-full h-64 bg-black rounded"></video>
<canvas ref="canvas" class="hidden"></canvas>
<div class="mt-3 flex justify-between">
<button @click="closeCamera" class="px-4 py-2 bg-gray-400 text-white rounded">Batal</button>
<button @click="capturePhoto" class="px-4 py-2 bg-blue-500 text-white rounded">Ambil Foto</button>
</div>
</div>
</div>
</mainLayout>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import axios from "axios";
import mainLayout from "../layouts/mainLayout.vue";
import InputField from "../components/InputField.vue";
import InputSelect from "../components/InputSelect.vue";
import CreateItemModal from "../components/CreateItemModal.vue";
import PhotoUploader from "../components/PhotoUploader.vue";
const router = useRouter();
const form = ref({
nama: '',
id_kategori: null,
berat: null,
kadar: null,
harga_per_gram: null,
harga_jual: null,
});
const category = ref([]);
const loading = ref(false);
const uploadedImages = ref([]);
const errors = ref({});
const openItemModal = ref(false);
const createdProduct = ref(null);
// Formatted values for harga_per_gram and harga_jual
const hargaPerGramFormatted = ref("");
const hargaJualFormatted = ref("");
// Format angka dengan pemisah ribuan
const formatNumber = (num) => {
if (!num) return "";
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
};
// Menghapus format dan mengambil angka asli
const unformatNumber = (str) => {
if (!str) return null;
const cleaned = str.replace(/\./g, "");
const number = parseFloat(cleaned);
return isNaN(number) ? null : number;
};
// Handler untuk format input harga per gram
const formatHargaPerGramInput = (event) => {
const value = event.target.value;
const cleanValue = value.replace(/\D/g, "");
if (cleanValue) {
const formatted = formatNumber(cleanValue);
hargaPerGramFormatted.value = formatted;
form.value.harga_per_gram = parseFloat(cleanValue);
calculateHargaJual();
} else {
hargaPerGramFormatted.value = "";
form.value.harga_per_gram = null;
calculateHargaJual();
}
};
// Handler untuk format input harga jual
const formatHargaJualInput = (event) => {
const value = event.target.value;
const cleanValue = value.replace(/\D/g, "");
if (cleanValue) {
const formatted = formatNumber(cleanValue);
hargaJualFormatted.value = formatted;
form.value.harga_jual = parseFloat(cleanValue);
} else {
hargaJualFormatted.value = "";
form.value.harga_jual = null;
}
};
// Hanya izinkan angka saat mengetik
const onlyNumbers = (event) => {
const char = String.fromCharCode(event.which);
if (!/[0-9]/.test(char)) {
event.preventDefault();
}
};
const isFormValid = computed(() => {
return (
form.value.nama &&
form.value.id_kategori &&
form.value.berat > 0 &&
form.value.kadar > 0 &&
form.value.harga_per_gram > 0 &&
form.value.harga_jual > 0 &&
uploadedImages.value.length > 0
);
});
const calculateHargaJual = () => {
const berat = parseFloat(form.value.berat) || 0;
const hargaPerGram = parseFloat(form.value.harga_per_gram) || 0;
if (berat > 0 && hargaPerGram > 0) {
const hargaJual = berat * hargaPerGram;
form.value.harga_jual = hargaJual;
hargaJualFormatted.value = formatNumber(hargaJual.toFixed(0));
} else {
form.value.harga_jual = null;
hargaJualFormatted.value = "";
}
};
const loadKategori = async () => {
try {
const response = await axios.get('/api/kategori', {
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
});
if (response.data && Array.isArray(response.data)) {
category.value = response.data.map((cat) => ({ value: cat.id, label: cat.nama }));
}
} catch (error) {
console.error('Error loading categories:', error);
}
};
const loadFoto = async () => {
try {
const response = await axios.get(`/api/foto`, {
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
});
uploadedImages.value = response.data;
} catch (e) {
console.error(e);
}
};
const handleUploadError = (error) => {
console.error('Upload error:', error);
};
const submitForm = async (addItem) => {
if (!isFormValid.value) {
alert('Mohon lengkapi semua field yang diperlukan');
return;
}
loading.value = true;
try {
if (form.value.kadar % 1 != 0) {
errors.value = { kadar: "Masukkan bilangan bulat" };
return;
}
form.value.berat = Number(form.value.berat)
const response = await axios.post('/api/produk', form.value, {
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
});
const createdProductData = response.data.data;
form.value = {
nama: '',
id_kategori: '',
berat: 0,
kadar: 0,
harga_per_gram: 0,
harga_jual: 0,
};
hargaPerGramFormatted.value = "";
hargaJualFormatted.value = "";
uploadedImages.value = [];
if (addItem) {
openCreateItemModal(createdProductData);
} else {
window.location.href = '/produk?message=Produk berhasil disimpan';
}
} catch (error) {
console.error('Submit error:', error);
if (error.response?.status === 422) {
errors.value = error.response.data.errors || {};
} else {
errors.value = { general: "Terjadi kesalahan saat menyimpan produk" };
}
} finally {
loading.value = false;
}
};
const back = async () => {
loading.value = true;
try {
console.log(localStorage.getItem("token"));
await axios.delete('/api/all/foto', {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
router.push('/produk');
} catch (e) {
console.error("Error image ", e);
} finally {
loading.value = false;
}
};
const openCreateItemModal = (product) => {
createdProduct.value = product;
openItemModal.value = true;
};
const closeItemModal = () => {
openItemModal.value = false;
createdProduct.value = null;
router.push('/produk');
};
onMounted(() => {
loadFoto();
loadKategori();
});
</script>