300 lines
9.4 KiB
Vue
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>
|