From eebabfd9192085f459e2f4964e6a6cdf1101dbc0 Mon Sep 17 00:00:00 2001 From: Baghaztra Date: Thu, 28 Aug 2025 15:13:19 +0700 Subject: [PATCH] [update] Input produk --- .env.example | 65 +++++++ resources/js/components/DropdownNav.vue | 48 ----- resources/js/components/Header.vue | 2 +- resources/js/pages/InputProduk.vue | 245 ++++++++++-------------- resources/js/pages/Produk.vue | 8 +- routes/web.php | 2 +- 6 files changed, 169 insertions(+), 201 deletions(-) create mode 100644 .env.example delete mode 100644 resources/js/components/DropdownNav.vue diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b7edfb1 --- /dev/null +++ b/.env.example @@ -0,0 +1,65 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +PHP_CLI_SERVER_WORKERS=4 + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=sqlite +# DB_HOST=127.0.0.1 +# DB_PORT=3306 +# DB_DATABASE=laravel +# DB_USERNAME=root +# DB_PASSWORD= + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=database +# CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=log +MAIL_SCHEME=null +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +VITE_APP_NAME="${APP_NAME}" \ No newline at end of file diff --git a/resources/js/components/DropdownNav.vue b/resources/js/components/DropdownNav.vue deleted file mode 100644 index 629ff4a..0000000 --- a/resources/js/components/DropdownNav.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - diff --git a/resources/js/components/Header.vue b/resources/js/components/Header.vue index 174bc14..d8b59ae 100644 --- a/resources/js/components/Header.vue +++ b/resources/js/components/Header.vue @@ -45,7 +45,7 @@ const toggleDropdown = () => {
  • {{ sub.label }} diff --git a/resources/js/pages/InputProduk.vue b/resources/js/pages/InputProduk.vue index 8ecfacd..f92a062 100644 --- a/resources/js/pages/InputProduk.vue +++ b/resources/js/pages/InputProduk.vue @@ -2,162 +2,115 @@

    Produk Baru

    - +
    - +
    - +
    - +
    - +
    - +
    - -
    -
    - -
    -
    - - -
    -
    - - +
    -
    - +
    +
    + + +
    +
    + + +
    - +
    - +
    -
    +
    - + -
    - + -
    + 'cursor-not-allowed opacity-50': uploadLoading + }">
    - -
    +
    - +
    - + +
    -

    +

    - - - - - + + +

    Format: JPG, JPEG, PNG (Max: 2MB per file, Max: 6 foto)

    - - +
    {{ uploadError }}
    + +
    + + + +
    @@ -191,15 +144,17 @@ const uploadedImages = ref([]); const isDragging = ref(false); const uploadError = ref(''); const fileInput = ref(null); -const userId = ref(1); // Sesuaikan dengan user yang login +// TODO: Logika autentikasi user +const userId = ref(1); const isFormValid = computed(() => { - return form.value.nama && - form.value.kategori && - form.value.berat > 0 && - form.value.kadar > 0 && - form.value.harga_per_gram > 0 && - form.value.harga_jual > 0; + return form.value.nama && + form.value.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 = () => { @@ -220,7 +175,6 @@ const loadExistingPhotos = async () => { if (error.response?.status !== 404) { console.error('Error loading existing photos:', error); } - // 404 is expected when no photos exist yet } }; @@ -238,64 +192,61 @@ const handleFileSelect = (event) => { const handleDrop = (event) => { event.preventDefault(); isDragging.value = false; - + if (uploadLoading.value || uploadedImages.value.length >= 6) return; - + const files = Array.from(event.dataTransfer.files); uploadFiles(files); }; const uploadFiles = async (files) => { uploadError.value = ''; - - // Validate file count + if (uploadedImages.value.length + files.length > 6) { uploadError.value = 'Maksimal 6 foto yang dapat diupload'; return; } - - // Validate file types and sizes + const validFiles = files.filter(file => { const isValidType = ['image/jpeg', 'image/jpg', 'image/png'].includes(file.type); - const isValidSize = file.size <= 2 * 1024 * 1024; // 2MB - + const isValidSize = file.size <= 2 * 1024 * 1024; + if (!isValidType) { uploadError.value = 'Format file harus JPG, JPEG, atau PNG'; return false; } - + if (!isValidSize) { uploadError.value = 'Ukuran file maksimal 2MB'; return false; } - + return true; }); - + if (validFiles.length === 0) return; - + uploadLoading.value = true; - + try { for (const file of validFiles) { const formData = new FormData(); formData.append('foto', file); formData.append('id_user', userId.value); - + const response = await axios.post('/api/foto/upload', formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); - + uploadedImages.value.push(response.data); } - - // Clear file input + if (fileInput.value) { fileInput.value.value = ''; } - + } catch (error) { console.error('Upload error:', error); uploadError.value = error.response?.data?.message || 'Gagal mengupload foto'; @@ -315,21 +266,20 @@ const removeImage = async (imageId) => { } }; -const submitForm = async () => { +const submitForm = async (addItem) => { if (!isFormValid.value) { alert('Mohon lengkapi semua field yang diperlukan'); return; } - + loading.value = true; - + try { const response = await axios.post('/api/produk', { ...form.value, id_user: userId.value }); - - // Reset form + form.value = { nama: '', kategori: '', @@ -338,19 +288,22 @@ const submitForm = async () => { harga_per_gram: 0, harga_jual: 0, }; - + uploadedImages.value = []; uploadError.value = ''; - + if (fileInput.value) { fileInput.value.value = ''; } - - alert('Produk berhasil disimpan!'); - + + if (addItem) { + alert('Produk berhasil ditambahkan. Silakan tambahkan produk lainnya.'); + } else { + window.location.href = '/produk?message=Produk berhasil disimpan'; + } } catch (error) { console.error('Submit error:', error); - + if (error.response?.data?.errors) { const errors = Object.values(error.response.data.errors).flat(); alert('Error: ' + errors.join(', ')); @@ -371,14 +324,12 @@ const resetPhotos = async () => { } }; -// Load existing photos on component mount +const back = () => { + resetPhotos(); + window.history.back(); +}; + onMounted(() => { loadExistingPhotos(); }); - -// Clean up photos if user leaves without saving -onUnmounted(() => { - // Optional: You might want to clean up temporary photos here - // resetPhotos(); -}); diff --git a/resources/js/pages/Produk.vue b/resources/js/pages/Produk.vue index 566dce7..7104636 100644 --- a/resources/js/pages/Produk.vue +++ b/resources/js/pages/Produk.vue @@ -24,9 +24,9 @@
    - +
    @@ -141,7 +141,7 @@ const currentFotoIndex = ref(0); // Fetch data awal onMounted(async () => { try { - const res = await axios.get("http://127.0.0.1:8000/api/produk"); + const res = await axios.get("/api/produk"); products.value = res.data; } catch (error) { console.error("Gagal ambil data produk:", error); @@ -172,7 +172,7 @@ const filteredProducts = computed(() => { // buka overlay async function openOverlay(id) { try { - const res = await axios.get(`http://127.0.0.1:8000/api/produk/${id}`); + const res = await axios.get(`/api/produk/${id}`); detail.value = res.data; currentFotoIndex.value = 0; // reset ke foto pertama showOverlay.value = true; diff --git a/routes/web.php b/routes/web.php index fcdea34..ac3aef3 100644 --- a/routes/web.php +++ b/routes/web.php @@ -30,4 +30,4 @@ Route::prefix('api')->group(function () { // Frontend SPA Route::get('/{any}', function () { return view('app'); -})->where('any', '.*'); +})->where('any', '^(?!storage|api).*$'); \ No newline at end of file