Merge branch 'production' of https://git.abbauf.com/Magang-2025/Kasir into production

This commit is contained in:
Baghaztra 2025-09-03 13:53:09 +07:00
commit bb487a4c09
17 changed files with 652 additions and 98 deletions

View File

@ -19,14 +19,14 @@ class UserController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
$request->validate([ $request->validate([
'nama' => 'required|nama|unique:users', 'nama' => 'required|string|unique:users',
'password' => 'required|min:6', 'password' => 'required|min:6',
'role' => 'required|in:owner, kasir', 'role' => 'required|in:owner,kasir',
]); ]);
User::create([ User::create([
'nama' => $request->nama, 'nama' => $request->nama,
'password' => bcrypt($request->password), 'password' => $request->password,
'role' => $request->role, 'role' => $request->role,
]); ]);
@ -43,7 +43,7 @@ class UserController extends Controller
$request->validate([ $request->validate([
'nama' => 'required|nama|unique:users,nama,' . $id, 'nama' => 'required|nama|unique:users,nama,' . $id,
'password' => 'required|min:6', 'password' => 'required|min:6',
'role' => 'required|in:owner, kasir', 'role' => 'required|in:owner,kasir',
]); ]);
$user->update([ $user->update([

View File

@ -0,0 +1,93 @@
<template>
<div
class="fixed inset-0 flex items-center justify-center bg-black/65 z-50"
>
<div class="bg-white rounded-lg p-6 w-96 shadow-lg">
<h2 class="text-lg font-bold mb-4">Tambah Akun</h2>
<form @submit.prevent="createAkun" class="space-y-3">
<!-- Nama -->
<label for="nama">Nama</label>
<InputField
v-model="form.nama"
id="nama"
type="text"
:required="true"
/>
<div>
<label for="password">Password</label>
<InputField
v-model="form.password"
id="password"
type="password"
:required="true"
/>
</div>
<label for="peran">Peran</label>
<InputSelect
v-model="form.role"
:options="[
{ value: 'owner', label: 'Owner' },
{ value: 'kasir', label: 'Kasir' },
]"
placeholder="-- Pilih Peran --"
/>
<!-- Tombol -->
<div class="flex justify-end gap-2 mt-4">
<button
type="button"
@click="$emit('close')"
class="bg-gray-300 hover:bg-gray-400 px-4 py-2 rounded"
>
Batal
</button>
<button
type="submit"
class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded"
>
Simpan
</button>
</div>
</form>
<!-- Error -->
<p v-if="errorMessage" class="text-red-500 text-sm mt-3">
{{ errorMessage }}
</p>
</div>
</div>
</template>
<script>
import axios from "axios";
import InputField from "@/components/InputField.vue";
import InputSelect from "@/components/InputSelect.vue";
export default {
name: "CreateAkun",
components: { InputField, InputSelect },
data() {
return {
form: { nama: "", password: "", role: "" },
errorMessage: "",
};
},
methods: {
async createAkun() {
try {
await axios.post("api/user", this.form);
this.form = { nama: "", password: "", role: "" };
this.$emit("refresh");
this.$emit("close");
} catch (err) {
this.errorMessage =
err.response?.data?.message || "Gagal menambah akun.";
console.error("Gagal tambah akun:", err);
}
},
},
};
</script>

View File

@ -1,5 +1,6 @@
<template> <template>
<div v-if="isOpen" class="fixed inset-0 flex items-center justify-center bg-black/75 z-50">
<div v-if="isOpen" class="fixed inset-0 flex items-center justify-center bg-black/65 z-50">
<div class="bg-white rounded-lg shadow-lg w-96 p-6 relative"> <div class="bg-white rounded-lg shadow-lg w-96 p-6 relative">
<!-- Header --> <!-- Header -->
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
@ -10,18 +11,17 @@
</div> </div>
<!-- Form --> <!-- Form -->
<div class="mb-4"> <div>
<label class="block text-sm font-medium text-gray-700">Nama Kategori</label> <label class="block text-sm font-medium text-gray-700">Nama Kategori</label>
<input <InputField
v-model="form.nama" v-model="form.nama"
type="text" type="text"
placeholder="Masukkan nama kategori" placeholder="Masukkan nama kategori"
class="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 focus:ring focus:ring-C"
/> />
</div> </div>
<!-- Buttons --> <!-- Buttons -->
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2 mt-4">
<button <button
@click="emit('close')" @click="emit('close')"
class="px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300" class="px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300"
@ -31,7 +31,7 @@
<button <button
@click="saveKategori" @click="saveKategori"
:disabled="!form.nama" :disabled="!form.nama"
class="px-4 py-2 bg-C text-white rounded hover:bg-B disabled:opacity-50" class="px-4 py-2 bg-C text-black rounded hover:bg-B"
> >
Simpan Simpan
</button> </button>
@ -43,6 +43,7 @@
<script setup> <script setup>
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import axios from 'axios' import axios from 'axios'
import InputField from './InputField.vue'
const props = defineProps({ const props = defineProps({
isOpen: Boolean, isOpen: Boolean,

View File

@ -1,30 +1,47 @@
<template> <template>
<div <div
v-if="isOpen" v-if="isOpen"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" class="fixed inset-0 bg-black/65 flex items-center justify-center z-50"
> >
<div class="bg-white rounded-lg shadow-lg w-full max-w-lg p-6 relative"> <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> <h2 class="text-xl font-bold mb-4">Tambah Sales</h2>
<form @submit.prevent="handleSubmit" class="space-y-4"> <form @submit.prevent="handleSubmit" class="space-y-4">
<!-- Nama -->
<div> <div>
<label class="block text-sm font-medium text-gray-700">Nama Sales</label> <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 /> <InputField v-model="form.nama" type="text" placeholder="Masukkan nama sales" required />
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700">No HP</label> <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 /> <InputField v-model="form.no_hp" type="text" placeholder="Masukkan nomor HP" required />
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700">Alamat</label> <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> <textarea
v-model="form.alamat"
placeholder="Masukkan alamat"
class="mt-1 block w-full rounded-md shadow-sm sm:text-sm bg-A text-D border-B focus:border-C focus:ring focus:ring-D focus:ring-opacity-50 p-2"
required
></textarea>
</div> </div>
<div class="flex justify-end gap-2 mt-6"> <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
<button type="submit" class="px-4 py-2 bg-[#c6a77d] text-white rounded hover:bg-[#b09065]">Simpan</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-C text-D rounded hover:bg-C/80"
>
Simpan
</button>
</div> </div>
</form> </form>
</div> </div>
@ -32,27 +49,34 @@
</template> </template>
<script setup> <script setup>
import { ref } from "vue"; import { ref } from "vue"
import axios from "axios"; import axios from "axios"
import InputField from "./InputField.vue"
const props = defineProps({ const props = defineProps({
isOpen: Boolean, isOpen: Boolean,
}); })
const emit = defineEmits(["close"]); const emit = defineEmits(["close", "saved"])
const form = ref({ const form = ref({
nama: "", nama: "",
no_hp: "", no_hp: "",
alamat: "", alamat: "",
}); })
const resetForm = () => {
form.value = { nama: "", no_hp: "", alamat: "" }
}
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
await axios.post("/api/sales", form.value); await axios.post("/api/sales", form.value)
emit("close"); resetForm()
emit("saved")
emit("close")
} catch (error) { } catch (error) {
console.error("Error creating sales:", error); console.error("Error creating sales:", error)
} }
}; }
</script> </script>

View File

@ -0,0 +1,114 @@
<template>
<div class="fixed inset-0 flex items-center justify-center bg-black/65">
<div class="bg-white rounded-lg p-6 w-96">
<h2 class="text-lg font-bold mb-4">Edit Akun</h2>
<form @submit.prevent="updateAkun" class="space-y-3">
<label for="nama">Nama</label>
<InputField
v-model="form.nama"
label="nama"
type="text"
:required="true"
class="mb-3"
/>
<div>
<label for="password">Password</label>
<InputField
v-model="form.password"
label="password"
type="password"
:required="false"
class="mb-1"
/>
<p class="text-sm">Kosongkan jika tidak ingin ubah password</p>
</div>
<label for="peran">Peran</label>
<InputSelect
v-model="form.role"
label="peran"
:options="[
{ value: 'owner', label: 'Owner' },
{ value: 'kasir', label: 'Kasir' }
]"
:required="true"
class="mb-3"
/>
<div class="flex justify-end gap-2 mt-4">
<button
type="button"
@click="$emit('close')"
class="bg-gray-300 px-4 py-2 rounded"
>
Batal
</button>
<button
type="submit"
class="bg-blue-500 text-white px-4 py-2 rounded"
>
Ubah
</button>
</div>
</form>
</div>
</div>
</template>
<script>
import axios from "axios";
import InputField from "@/components/InputField.vue";
import InputSelect from "@/components/InputSelect.vue";
export default {
props: {
akun: {
type: Object,
required: true,
},
},
components: { InputField, InputSelect },
data() {
return {
form: {
nama: this.akun.nama || "",
password: "",
role: this.akun.role || "",
},
};
},
watch: {
akun: {
handler(newVal) {
if (newVal) {
this.form = {
nama: newVal.nama || "",
password: "",
role: newVal.role || "",
};
}
},
deep: true,
immediate: true,
},
},
methods: {
async updateAkun() {
try {
const payload = { ...this.form };
if (!payload.password) delete payload.password;
await axios.put(`api/user/${this.akun.id}`, payload);
this.$emit("refresh");
this.$emit("close");
} catch (err) {
console.error("Gagal update akun:", err.response?.data || err.message);
}
},
},
};
</script>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-40 z-50"> <div class="fixed inset-0 flex items-center justify-center bg-black/65 z-50">
<div class="bg-white rounded-lg shadow-lg w-[400px] p-6 relative"> <div class="bg-white rounded-lg shadow-lg w-[400px] p-6 relative">
<!-- Tombol close --> <!-- Tombol close -->
@ -11,14 +11,13 @@
<h2 class="text-xl font-bold text-center text-D mb-4">Edit Kategori</h2> <h2 class="text-xl font-bold text-center text-D mb-4">Edit Kategori</h2>
<!-- Input Nama Kategori --> <!-- Input Nama Kategori -->
<div class="mb-4"> <div>
<label for="editKategori" class="block text-sm font-medium text-D mb-1">Nama Kategori</label> <label for="editKategori" class="block text-sm font-medium text-D mb-1">Nama Kategori</label>
<input <InputField
v-model="editNamaKategori" v-model="editNamaKategori"
type="text" type="text"
id="editKategori" id="editKategori"
placeholder="Masukkan nama kategori" placeholder="Masukkan nama kategori"
class="w-full p-2 border rounded-md bg-Focus outline-none"
/> />
</div> </div>
@ -27,8 +26,8 @@
<button @click="$emit('close')" class="px-4 py-2 bg-gray-300 rounded-md hover:bg-gray-400"> <button @click="$emit('close')" class="px-4 py-2 bg-gray-300 rounded-md hover:bg-gray-400">
Batal Batal
</button> </button>
<button @click="updateKategori" class="px-4 py-2 bg-B text-white rounded-md hover:bg-A"> <button @click="updateKategori" class="px-4 py-2 bg-B text-D rounded-md hover:bg-A">
Update Ubah
</button> </button>
</div> </div>
</div> </div>
@ -37,6 +36,7 @@
<script setup> <script setup>
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import InputField from "./InputField.vue";
const props = defineProps({ const props = defineProps({
kategori: { type: Object, required: true }, kategori: { type: Object, required: true },

View File

@ -1,7 +1,7 @@
<template> <template>
<div <div
v-if="isOpen" v-if="isOpen"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" class="fixed inset-0 bg-black/65 flex items-center justify-center z-50"
> >
<div class="bg-white rounded-lg shadow-lg w-full max-w-lg p-6 relative"> <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> <h2 class="text-xl font-bold mb-4">Ubah Sales</h2>
@ -9,22 +9,26 @@
<form @submit.prevent="handleSubmit" class="space-y-4"> <form @submit.prevent="handleSubmit" class="space-y-4">
<div> <div>
<label class="block text-sm font-medium text-gray-700">Nama Sales</label> <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 /> <InputField 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>
<div> <div>
<label class="block text-sm font-medium text-gray-700">No HP</label> <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 /> <InputField 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>
<div> <div>
<label class="block text-sm font-medium text-gray-700">Alamat</label> <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> <textarea
v-model="form.alamat"
class="mt-1 block w-full rounded-md shadow-sm sm:text-sm bg-A text-D border-B focus:border-C focus:ring focus:ring-D focus:ring-opacity-50 p-2"
required
></textarea>
</div> </div>
<div class="flex justify-end gap-2 mt-6"> <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="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> <button type="submit" class="px-4 py-2 bg-C text-D rounded hover:bg-C">Ubah</button>
</div> </div>
</form> </form>
</div> </div>
@ -34,10 +38,11 @@
<script setup> <script setup>
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import axios from "axios"; import axios from "axios";
import InputField from "./InputField.vue";
const props = defineProps({ const props = defineProps({
isOpen: Boolean, isOpen: Boolean,
sales: Object, // data sales yang akan di-edit sales: Object,
}); });
const emit = defineEmits(["close"]); const emit = defineEmits(["close"]);
@ -48,7 +53,6 @@
alamat: "", alamat: "",
}); });
// isi form dengan data props.sales
watch( watch(
() => props.sales, () => props.sales,
(val) => { (val) => {
@ -62,11 +66,9 @@
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
await axios.put(`/api/sales/${props.sales.id}`, form.value); await axios.put(`/api/sales/${props.sales.id}`, form.value);
alert("Sales berhasil diubah!");
emit("close"); emit("close");
} catch (error) { } catch (error) {
console.error("Error updating sales:", error); console.error("Error updating sales:", error);
alert("Gagal mengubah sales");
} }
}; };
</script> </script>

View File

@ -0,0 +1,46 @@
<template>
<div class="relative mb-8">
<input
:type="showPassword ? 'text' : 'password'"
:value="modelValue"
:placeholder="placeholder"
@input="$emit('update:modelValue', $event.target.value)"
class="mt-1 block w-full rounded-md shadow-sm sm:text-sm
bg-A text-D border-B focus:border-C
focus:ring focus:ring-D focus:ring-opacity-50 p-2 pr-10"
/>
<!-- Tombol show/hide password -->
<button
type="button"
@click="togglePassword"
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-gray-700 focus:outline-none"
>
<i v-if="showPassword" class="fas fa-eye"></i>
<i v-else class="fas fa-eye-slash"></i>
</button>
</div>
</template>
<script setup>
import { ref } from "vue";
const props = defineProps({
modelValue: {
type: String,
default: "",
},
placeholder: {
type: String,
default: "Password",
},
});
const emit = defineEmits(["update:modelValue"]);
const showPassword = ref(false);
const togglePassword = () => {
showPassword.value = !showPassword.value;
};
</script>

View File

@ -10,7 +10,7 @@
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"> <div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<div v-for="tray in filteredTrays" :key="tray.id" <div v-for="tray in filteredTrays" :key="tray.id"
class="border rounded-lg p-4 shadow-sm hover:shadow-md transition"> class="border border-C rounded-lg p-4 shadow-sm hover:shadow-md transition">
<div class="flex justify-between items-center mb-3"> <div class="flex justify-between items-center mb-3">
<h2 class="font-bold text-lg" style="color: #102C57;">{{ tray.nama }}</h2> <h2 class="font-bold text-lg" style="color: #102C57;">{{ tray.nama }}</h2>
<div class="flex gap-2"> <div class="flex gap-2">
@ -24,7 +24,7 @@
</div> </div>
<div v-if="tray.items && tray.items.length > 0" class="space-y-2 max-h-64 overflow-y-auto pr-2"> <div v-if="tray.items && tray.items.length > 0" class="space-y-2 max-h-64 overflow-y-auto pr-2">
<div v-for="item in tray.items" :key="item.id" class="flex justify-between items-center border rounded-lg p-2" <div v-for="item in tray.items" :key="item.id" class="flex justify-between items-center border border-C rounded-lg p-2"
@click="openMovePopup(item)"> @click="openMovePopup(item)">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">

211
resources/js/pages/Akun.vue Normal file
View File

@ -0,0 +1,211 @@
<template>
<mainLayout>
<!-- Modal Create/Edit Akun -->
<CreateAkun
v-if="creatingAkun"
:isOpen="creatingAkun"
:akun="detail"
@close="closeAkun"
/>
<EditAkun
v-if="editingAkun"
:isOpen="editingAkun"
:akun="detail"
@close="closeEditAkun"
/>
<!-- Modal Delete -->
<ConfirmDeleteModal
:isOpen="confirmDeleteOpen"
title="Hapus User"
message="Apakah Anda yakin ingin menghapus user ini?"
@confirm="confirmDelete"
@cancel="closeDeleteModal"
/>
<div class="p-6 min-h-[75vh]">
<!-- Header Section -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-D">Manajemen Akun</h1>
<button
@click="tambahAkun"
class="px-4 py-2 bg-C text-D rounded-md hover:bg-C/80 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 User
</button>
</div>
<!-- Table Section -->
<div
class="bg-white rounded-lg shadow-md border border-C overflow-hidden"
>
<table class="w-full">
<thead>
<tr class="bg-C text-white">
<th class="px-6 py-4 text-center text-D border-r border-C">No</th>
<th class="px-6 py-4 text-center text-D border-r border-C">Nama</th>
<th class="px-6 py-4 text-center text-D border-r border-C">Peran</th>
<th class="px-6 py-4 text-center text-D">Aksi</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in akun"
:key="item.id"
class="border-b border-C hover:bg-gray-50 transition duration-150"
:class="{ 'bg-gray-50': index % 2 === 1 }"
>
<td class="px-6 py-4 border-r border-C text-center font-medium text-gray-900">
{{ index + 1 }}
</td>
<td class="px-6 py-4 border-r border-C text-D">
{{ item.nama }}
</td>
<td class="px-6 py-4 border-r border-C text-gray-800">
{{ item.role }}
</td>
<td class="px-6 py-4 text-center">
<div class="flex justify-center gap-2">
<button
@click="ubahAkun(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="hapusAkun(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="akun.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 user</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 ConfirmDeleteModal from "../components/ConfirmDeleteModal.vue";
import CreateAkun from "../components/CreateAkun.vue";
import EditAkun from "../components/EditAkun.vue";
// State
const akun = ref([]);
const loading = ref(false);
const creatingAkun = ref(false);
const detail = ref(null);
const editingAkun = ref(false);
const confirmDeleteOpen = ref(false);
const akunToDelete = ref(null);
// Fetch data dari API
const fetchAkun = async () => {
loading.value = true;
try {
const response = await axios.get("/api/user");
akun.value = response.data;
} catch (error) {
console.error("Error fetching akun:", error);
} finally {
loading.value = false;
}
};
// Tambah
const tambahAkun = () => {
detail.value = null;
creatingAkun.value = true;
};
// Ubah
const ubahAkun = (item) => {
detail.value = item;
editingAkun.value = true;
};
// Hapus
const hapusAkun = (item) => {
akunToDelete.value = item;
confirmDeleteOpen.value = true;
};
const confirmDelete = async () => {
try {
await axios.delete(`/api/user/${akunToDelete.value.id}`);
fetchAkun();
confirmDeleteOpen.value = false;
} catch (error) {
console.error("Error deleting akun:", error);
}
};
const closeDeleteModal = () => {
confirmDeleteOpen.value = false;
akunToDelete.value = null;
};
// Tutup modal Create/Edit
const closeAkun = () => {
creatingAkun.value = false;
fetchAkun();
};
const closeEditAkun = () => {
editingAkun.value = false;
fetchAkun();
};
// Lifecycle
onMounted(() => {
fetchAkun();
});
</script>

View File

@ -6,7 +6,6 @@
:product="createdProduct" :product="createdProduct"
@close="closeItemModal" @close="closeItemModal"
/> />
<div class="p-6"> <div class="p-6">
<p class="font-serif italic text-[25px] text-D">Produk Baru</p> <p class="font-serif italic text-[25px] text-D">Produk Baru</p>
@ -130,6 +129,7 @@ import mainLayout from "../layouts/mainLayout.vue";
import InputField from "../components/InputField.vue"; import InputField from "../components/InputField.vue";
import InputSelect from "../components/InputSelect.vue"; import InputSelect from "../components/InputSelect.vue";
import CreateItemModal from "../components/CreateItemModal.vue"; import CreateItemModal from "../components/CreateItemModal.vue";
import Struk from "../components/Struk.vue";
const router = useRouter(); const router = useRouter();

View File

@ -4,7 +4,7 @@
<ConfirmDeleteModal :isOpen="confirmDeleteOpen" :item="kategoriToDelete" title="Hapus Kategori" <ConfirmDeleteModal :isOpen="confirmDeleteOpen" :item="kategoriToDelete" title="Hapus Kategori"
message="Apakah Anda yakin ingin menghapus kategori ini?" @confirm="confirmDelete" @cancel="closeDeleteModal" /> message="Apakah Anda yakin ingin menghapus kategori ini?" @confirm="confirmDelete" @cancel="closeDeleteModal" />
<div class="p-6"> <div class="p-6 min-h-[75vh]" >
<!-- Header Section --> <!-- Header Section -->
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">Kategori</h1> <h1 class="text-2xl font-bold text-gray-800">Kategori</h1>
@ -18,14 +18,14 @@
</div> </div>
<!-- Table Section --> <!-- Table Section -->
<div class="bg-white rounded-lg shadow-md border border-gray-200 overflow-hidden"> <div class="bg-white rounded-lg shadow-md border border-C overflow-hidden">
<table class="w-full"> <table class="w-full">
<thead> <thead>
<tr class="bg-C text-black"> <tr class="bg-C text-black">
<th class="px-6 py-4 text-left font-semibold border-r border-C"> <th class="px-6 py-4 text-center font-semibold border-r border-C">
No No
</th> </th>
<th class="px-6 py-4 text-left font-semibold border-r border-C"> <th class="px-6 py-4 text-center font-semibold border-r border-C">
Nama Kategori Nama Kategori
</th> </th>
<th class="px-6 py-4 text-center font-semibold"> <th class="px-6 py-4 text-center font-semibold">
@ -35,12 +35,12 @@
</thead> </thead>
<tbody> <tbody>
<tr v-for="(item, index) in kategori" :key="item.id" <tr v-for="(item, index) in kategori" :key="item.id"
class="border-b border-gray-200 hover:bg-A transition duration-150" class="border-b border-C hover:bg-A transition duration-150"
:class="{ 'bg-gray-50': index % 2 === 1 }"> :class="{ 'bg-gray-50': index % 2 === 1 }">
<td class="px-6 py-4 border-r border-gray-200 font-medium text-gray-900"> <td class="px-6 py-4 border-r border-C font-medium text-center text-gray-900">
{{ index + 1 }} {{ index + 1 }}
</td> </td>
<td class="px-6 py-4 border-r border-gray-200 text-gray-800"> <td class="px-6 py-4 border-r border-C text-center text-gray-800">
{{ item.nama }} {{ item.nama }}
</td> </td>
<td class="px-6 py-4 text-center"> <td class="px-6 py-4 text-center">

View File

@ -0,0 +1,48 @@
<template>
<div class="flex items-center justify-center min-h-screen bg-[#0c4b66]">
<div class="bg-white p-8 rounded-2xl shadow-xl w-80 text-center">
<!-- Logo + Title -->
<div class="mb-6">
<img :src="logo" alt="Logo" class="mx-auto w-34 py-5">
</div>
<!-- Input -->
<div>
<InputField v-model="username" type="text" placeholder="Username"class="mb-4"/>
<PasswordInput v-model="password" placeholder="Password" />
</div>
<!-- Button -->
<button
@click="handleLogin"
class="w-full py-2 bg-sky-400 hover:bg-sky-500 rounded font-bold text-gray-800 transition"
>
Login
</button>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import logo from '@/../images/logo.png'
import InputField from "@/components/InputField.vue";
const username = ref("");
const password = ref("");
import PasswordInput from "@/components/InputPassword.vue";
// import { ref } from "vue";
// const username = ref("");
// const password = ref("");
// const handleLogin = () => {
// if (!username.value || !password.value) {
// alert("Harap isi username dan password!");
// return;
// }
// // Contoh: panggil API login
// console.log("Login dengan:", username.value, password.value);
// };
</script>

View File

@ -57,11 +57,11 @@
@click.self="closeOverlay" @click.self="closeOverlay"
> >
<div <div
class="bg-white rounded-lg shadow-lg p-6 w-[450px] border-2 border-[#e6d3b3] relative flex flex-col items-center" class="bg-white rounded-lg shadow-lg p-6 w-[450px] border-2 border-B relative flex flex-col items-center"
> >
<!-- Foto Produk --> <!-- Foto Produk -->
<div <div
class="relative w-72 h-72 border border-[#e6d3b3] flex items-center justify-center mb-3 overflow-hidden rounded" class="relative w-72 h-72 border border-B flex items-center justify-center mb-3 overflow-hidden rounded"
> >
<img <img
v-if="detail.foto && detail.foto.length > 0" v-if="detail.foto && detail.foto.length > 0"

View File

@ -12,7 +12,7 @@
v-if="editingSales" v-if="editingSales"
:isOpen="editingSales" :isOpen="editingSales"
:sales="detail" :sales="detail"
@close="closeSales" @close="closeEditSales"
/> />
<!-- Modal Delete --> <!-- Modal Delete -->
@ -21,16 +21,16 @@
title="Hapus Sales" title="Hapus Sales"
message="Apakah Anda yakin ingin menghapus sales ini?" message="Apakah Anda yakin ingin menghapus sales ini?"
@confirm="confirmDelete" @confirm="confirmDelete"
@close="closeDeleteModal" @cancel="closeDeleteModal"
/> />
<div class="p-6"> <div class="p-6 min-h-[75vh]">
<!-- Header Section --> <!-- Header Section -->
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">Sales</h1> <h1 class="text-2xl font-bold text-D">Sales</h1>
<button <button
@click="tambahSales" @click="tambahSales"
class="px-4 py-2 bg-[#c6a77d] text-white rounded-md hover:bg-[#b09065] transition duration-200 flex items-center gap-2" class="px-4 py-2 bg-C text-D rounded-md hover:bg-C/80 transition duration-200 flex items-center gap-2"
> >
<svg <svg
class="w-4 h-4" class="w-4 h-4"
@ -51,32 +51,32 @@
<!-- Table Section --> <!-- Table Section -->
<div <div
class="bg-white rounded-lg shadow-md border border-gray-200 overflow-hidden" class="bg-white rounded-lg shadow-md border border-C overflow-hidden"
> >
<table class="w-full"> <table class="w-full">
<thead> <thead class="">
<tr class="bg-[#c6a77d] text-white"> <tr class="bg-C text-white">
<th <th
class="px-6 py-4 text-left font-semibold border-r border-[#b09065]" class="px-6 py-4 text-center text-D border-r border-[#b09065]"
> >
No No
</th> </th>
<th <th
class="px-6 py-4 text-left font-semibold border-r border-[#b09065]" class="px-6 py-4 text-center text-D border-r border-[#b09065]"
> >
Nama Sales Nama Sales
</th> </th>
<th <th
class="px-6 py-4 text-left font-semibold border-r border-[#b09065]" class="px-6 py-4 text-center text-D border-r border-[#b09065]"
> >
No HP No HP
</th> </th>
<th <th
class="px-6 py-4 text-left font-semibold border-r border-[#b09065]" class="px-6 py-4 text-center text-D border-r border-[#b09065]"
> >
Alamat Alamat
</th> </th>
<th class="px-6 py-4 text-center font-semibold"> <th class="px-6 py-4 text-center text-D">
Aksi Aksi
</th> </th>
</tr> </tr>
@ -85,26 +85,26 @@
<tr <tr
v-for="(item, index) in sales" v-for="(item, index) in sales"
:key="item.id" :key="item.id"
class="border-b border-gray-200 hover:bg-gray-50 transition duration-150" class="border-b border-C hover:bg-gray-50 transition duration-150"
:class="{ 'bg-gray-50': index % 2 === 1 }" :class="{ 'bg-gray-50': index % 2 === 1 }"
> >
<td <td
class="px-6 py-4 border-r border-gray-200 font-medium text-gray-900" class="px-6 py-4 border-r border-C text-center font-medium text-gray-900"
> >
{{ index + 1 }} {{ index + 1 }}
</td> </td>
<td <td
class="px-6 py-4 border-r border-gray-200 text-gray-800" class="px-6 py-4 border-r border-C text-D"
> >
{{ item.nama }} {{ item.nama }}
</td> </td>
<td <td
class="px-6 py-4 border-r border-gray-200 text-gray-800" class="px-6 py-4 border-r border-C text-gray-800"
> >
{{ item.no_hp }} {{ item.no_hp }}
</td> </td>
<td <td
class="px-6 py-4 border-r border-gray-200 text-gray-800" class="px-6 py-4 border-r border-C text-gray-800"
> >
{{ item.alamat }} {{ item.alamat }}
</td> </td>
@ -178,7 +178,7 @@ const sales = ref([]);
const loading = ref(false); const loading = ref(false);
const creatingSales = ref(false); const creatingSales = ref(false);
const detail = ref(null); const detail = ref(null);
const editingSales = ref(false);
const confirmDeleteOpen = ref(false); const confirmDeleteOpen = ref(false);
const salesToDelete = ref(null); const salesToDelete = ref(null);
@ -204,7 +204,7 @@ const tambahSales = () => {
// Ubah // Ubah
const ubahSales = (item) => { const ubahSales = (item) => {
detail.value = item; detail.value = item;
creatingSales.value = true; editingSales.value = true;
}; };
// Hapus // Hapus
@ -234,6 +234,11 @@ const closeSales = () => {
fetchSales(); fetchSales();
}; };
const closeEditSales = () => {
editingSales.value = false;
fetchSales();
};
// Lifecycle // Lifecycle
onMounted(() => { onMounted(() => {
fetchSales(); fetchSales();

View File

@ -42,7 +42,7 @@
<!-- Modal Tambah/Edit Nampan --> <!-- Modal Tambah/Edit Nampan -->
<div <div
v-if="showModal" v-if="showModal"
class="fixed inset-0 bg-black bg-opacity-40 flex justify-center items-center z-50" class="fixed inset-0 bg-black/75 flex justify-center items-center z-50"
> >
<div class="bg-white rounded-lg shadow-lg p-6 w-96"> <div class="bg-white rounded-lg shadow-lg p-6 w-96">
<h2 class="text-lg font-semibold mb-4" style="color: #102C57;">Tambah Nampan</h2> <h2 class="text-lg font-semibold mb-4" style="color: #102C57;">Tambah Nampan</h2>

View File

@ -10,12 +10,17 @@ import Sales from '../pages/Sales.vue'
import EditProduk from '../pages/EditProduk.vue' import EditProduk from '../pages/EditProduk.vue'
import Laporan from '../pages/Laporan.vue' import Laporan from '../pages/Laporan.vue'
import Login from '../pages/Login.vue'
import Akun from '../pages/Akun.vue'
const routes = [ const routes = [
{ {
path: '/', path: '/',
name: 'Home', name: 'Login',
component: Home component: Login
}, },
{ {
path: '/produk', path: '/produk',
@ -52,6 +57,11 @@ const routes = [
name: 'Sales', name: 'Sales',
component: Sales component: Sales
}, },
{
path: '/akun',
name: 'Akun',
component: Akun
},
{ {
path: '/produk/:id/edit', // :id = parameter dinamis path: '/produk/:id/edit', // :id = parameter dinamis
name: 'EditProduk', name: 'EditProduk',