Compare commits

...

2 Commits

Author SHA1 Message Date
Timoti313
d51b73c347 Merge branch 'production' of https://git.abbauf.com/Magang-2025/Kasir into production 2025-09-01 17:07:51 +07:00
Timoti313
cca9aeaaf0 [feat Sales, CreateSales, EditSales] 2025-09-01 17:05:34 +07:00
5 changed files with 379 additions and 2 deletions

View File

@ -102,7 +102,7 @@ const createdItem = ref(null);
// Computed
const selectedNampanName = computed(() => {
if (!selectedNampan.value) return 'Brankas';
console.log("Selected nampan ID:", selectedNampan.value);
const nampan = nampanList.value.find(n => n.id === Number(selectedNampan.value));
console.log("All nampan:", nampanList.value);
@ -147,7 +147,7 @@ const createItem = async () => {
success.value = true;
createdItem.value = response.data.data
console.log('Item created:', createdItem);
} catch (error) {
console.error('Error creating item:', error);
alert('Gagal membuat item: ' + (error.response?.data?.message || error.message));

View File

@ -0,0 +1,58 @@
<template>
<div
v-if="isOpen"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<div class="bg-white rounded-lg shadow-lg w-full max-w-lg p-6 relative">
<h2 class="text-xl font-bold mb-4">Tambah Sales</h2>
<form @submit.prevent="handleSubmit" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700">Nama Sales</label>
<input v-model="form.nama" type="text" class="w-full px-3 py-2 border rounded-md focus:ring-2 focus:ring-[#c6a77d] focus:outline-none" required />
</div>
<div>
<label class="block text-sm font-medium text-gray-700">No HP</label>
<input v-model="form.no_hp" type="text" class="w-full px-3 py-2 border rounded-md focus:ring-2 focus:ring-[#c6a77d] focus:outline-none" required />
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Alamat</label>
<textarea v-model="form.alamat" class="w-full px-3 py-2 border rounded-md focus:ring-2 focus:ring-[#c6a77d] focus:outline-none" required></textarea>
</div>
<div class="flex justify-end gap-2 mt-6">
<button type="button" @click="$emit('close')" class="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400">Batal</button>
<button type="submit" class="px-4 py-2 bg-[#c6a77d] text-white rounded hover:bg-[#b09065]">Simpan</button>
</div>
</form>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import axios from "axios";
const props = defineProps({
isOpen: Boolean,
});
const emit = defineEmits(["close"]);
const form = ref({
nama: "",
no_hp: "",
alamat: "",
});
const handleSubmit = async () => {
try {
await axios.post("/api/sales", form.value);
emit("close");
} catch (error) {
console.error("Error creating sales:", error);
}
};
</script>

View File

@ -0,0 +1,72 @@
<template>
<div
v-if="isOpen"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<div class="bg-white rounded-lg shadow-lg w-full max-w-lg p-6 relative">
<h2 class="text-xl font-bold mb-4">Ubah Sales</h2>
<form @submit.prevent="handleSubmit" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700">Nama Sales</label>
<input v-model="form.nama" type="text" class="w-full px-3 py-2 border rounded-md focus:ring-2 focus:ring-[#c6a77d] focus:outline-none" required />
</div>
<div>
<label class="block text-sm font-medium text-gray-700">No HP</label>
<input v-model="form.no_hp" type="text" class="w-full px-3 py-2 border rounded-md focus:ring-2 focus:ring-[#c6a77d] focus:outline-none" required />
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Alamat</label>
<textarea v-model="form.alamat" class="w-full px-3 py-2 border rounded-md focus:ring-2 focus:ring-[#c6a77d] focus:outline-none" required></textarea>
</div>
<div class="flex justify-end gap-2 mt-6">
<button type="button" @click="$emit('close')" class="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400">Batal</button>
<button type="submit" class="px-4 py-2 bg-[#c6a77d] text-white rounded hover:bg-[#b09065]">Update</button>
</div>
</form>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import axios from "axios";
const props = defineProps({
isOpen: Boolean,
sales: Object, // data sales yang akan di-edit
});
const emit = defineEmits(["close"]);
const form = ref({
nama: "",
no_hp: "",
alamat: "",
});
// isi form dengan data props.sales
watch(
() => props.sales,
(val) => {
if (val) {
form.value = { ...val };
}
},
{ immediate: true }
);
const handleSubmit = async () => {
try {
await axios.put(`/api/sales/${props.sales.id}`, form.value);
alert("Sales berhasil diubah!");
emit("close");
} catch (error) {
console.error("Error updating sales:", error);
alert("Gagal mengubah sales");
}
};
</script>

View File

@ -0,0 +1,241 @@
<template>
<mainLayout>
<!-- Modal Create/Edit Sales -->
<CreateSales
v-if="creatingSales"
:isOpen="creatingSales"
:sales="detail"
@close="closeSales"
/>
<EditSales
v-if="editingSales"
:isOpen="editingSales"
:sales="detail"
@close="closeSales"
/>
<!-- Modal Delete -->
<ConfirmDeleteModal
:isOpen="confirmDeleteOpen"
title="Hapus Sales"
message="Apakah Anda yakin ingin menghapus sales ini?"
@confirm="confirmDelete"
@close="closeDeleteModal"
/>
<div class="p-6">
<!-- Header Section -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">Sales</h1>
<button
@click="tambahSales"
class="px-4 py-2 bg-[#c6a77d] text-white rounded-md hover:bg-[#b09065] transition duration-200 flex items-center gap-2"
>
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4v16m8-8H4"
/>
</svg>
Tambah Sales
</button>
</div>
<!-- Table Section -->
<div
class="bg-white rounded-lg shadow-md border border-gray-200 overflow-hidden"
>
<table class="w-full">
<thead>
<tr class="bg-[#c6a77d] text-white">
<th
class="px-6 py-4 text-left font-semibold border-r border-[#b09065]"
>
No
</th>
<th
class="px-6 py-4 text-left font-semibold border-r border-[#b09065]"
>
Nama Sales
</th>
<th
class="px-6 py-4 text-left font-semibold border-r border-[#b09065]"
>
No HP
</th>
<th
class="px-6 py-4 text-left font-semibold border-r border-[#b09065]"
>
Alamat
</th>
<th class="px-6 py-4 text-center font-semibold">
Aksi
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in sales"
:key="item.id"
class="border-b border-gray-200 hover:bg-gray-50 transition duration-150"
:class="{ 'bg-gray-50': index % 2 === 1 }"
>
<td
class="px-6 py-4 border-r border-gray-200 font-medium text-gray-900"
>
{{ index + 1 }}
</td>
<td
class="px-6 py-4 border-r border-gray-200 text-gray-800"
>
{{ item.nama }}
</td>
<td
class="px-6 py-4 border-r border-gray-200 text-gray-800"
>
{{ item.no_hp }}
</td>
<td
class="px-6 py-4 border-r border-gray-200 text-gray-800"
>
{{ item.alamat }}
</td>
<td class="px-6 py-4 text-center">
<div class="flex justify-center gap-2">
<button
@click="ubahSales(item)"
class="px-3 py-1 bg-yellow-500 text-white text-sm rounded hover:bg-yellow-600 transition duration-200"
>
Ubah
</button>
<button
@click="hapusSales(item)"
class="px-3 py-1 bg-red-500 text-white text-sm rounded hover:bg-red-600 transition duration-200"
>
Hapus
</button>
</div>
</td>
</tr>
<!-- Empty State -->
<tr v-if="sales.length === 0 && !loading">
<td
colspan="5"
class="px-6 py-8 text-center text-gray-500"
>
<div class="flex flex-col items-center">
<svg
class="w-12 h-12 text-gray-400 mb-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2 2v-5m16 0h-2M4 13h2"
/>
</svg>
<p>Tidak ada data sales</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Loading State -->
<div v-if="loading" class="flex justify-center items-center py-8">
<div
class="animate-spin rounded-full h-8 w-8 border-b-2 border-[#c6a77d]"
></div>
<span class="ml-2 text-gray-600">Memuat data...</span>
</div>
</div>
</mainLayout>
</template>
<script setup>
import { ref, onMounted } from "vue";
import axios from "axios";
import mainLayout from "../layouts/mainLayout.vue";
import CreateSales from "../components/CreateSales.vue";
import ConfirmDeleteModal from "../components/ConfirmDeleteModal.vue";
import EditSales from "../components/EditSales.vue";
// State
const sales = ref([]);
const loading = ref(false);
const creatingSales = ref(false);
const detail = ref(null);
const confirmDeleteOpen = ref(false);
const salesToDelete = ref(null);
// Fetch data dari API
const fetchSales = async () => {
loading.value = true;
try {
const response = await axios.get("/api/sales");
sales.value = response.data;
} catch (error) {
console.error("Error fetching sales:", error);
} finally {
loading.value = false;
}
};
// Tambah
const tambahSales = () => {
detail.value = null;
creatingSales.value = true;
};
// Ubah
const ubahSales = (item) => {
detail.value = item;
creatingSales.value = true;
};
// Hapus
const hapusSales = (item) => {
salesToDelete.value = item;
confirmDeleteOpen.value = true;
};
const confirmDelete = async () => {
try {
await axios.delete(`/api/sales/${salesToDelete.value.id}`);
fetchSales();
confirmDeleteOpen.value = false;
} catch (error) {
console.error("Error deleting sales:", error);
}
};
const closeDeleteModal = () => {
confirmDeleteOpen.value = false;
salesToDelete.value = null;
};
// Tutup modal Create/Edit
const closeSales = () => {
creatingSales.value = false;
fetchSales();
};
// Lifecycle
onMounted(() => {
fetchSales();
});
</script>

View File

@ -6,6 +6,7 @@ import Tray from '../pages/Tray.vue'
import Kasir from '../pages/Kasir.vue'
import InputProduk from '../pages/InputProduk.vue'
import Kategori from '../pages/Kategori.vue'
import Sales from '../pages/Sales.vue'
import EditProduk from '../pages/EditProduk.vue'
@ -45,6 +46,11 @@ const routes = [
name: 'Kategori',
component: Kategori
},
{
path: '/sales',
name: 'Sales',
component: Sales
},
{
path: '/produk/:id/edit', // :id = parameter dinamis
name: 'EditProduk',