[UPDATE] FIX BUG,

This commit is contained in:
timotiabbauftech 2025-09-12 16:59:38 +07:00
parent 380ede5ca8
commit c65f0a857b
5 changed files with 278 additions and 225 deletions

View File

@ -22,7 +22,7 @@
<!-- Password -->
<div>
<label for="password" class="block text-sm font-medium">Password</label>
<InputField
<InputPassword
v-model="form.password"
id="password"
type="password"
@ -77,10 +77,11 @@
import axios from "axios";
import InputField from "@/components/InputField.vue";
import InputSelect from "@/components/InputSelect.vue";
import InputPassword from "./InputPassword.vue";
export default {
name: "CreateAkun",
components: { InputField, InputSelect },
components: { InputField, InputSelect, InputPassword },
data() {
return {
form: { nama: "", password: "", role: "" },

View File

@ -1,174 +1,210 @@
<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">Edit Akun</h2>
<form @submit.prevent="updateAkun" class="space-y-3">
<!-- Nama -->
<div>
<label for="nama" class="block text-sm font-medium">Nama</label>
<InputField
v-model="form.nama"
id="nama"
type="text"
:required="true"
@input="clearError('nama')"
/>
<p v-if="errors.nama" class="text-red-500 text-sm">{{ errors.nama }}</p>
</div>
<!-- Password -->
<div>
<label for="password" class="block text-sm font-medium">Password</label>
<InputField
v-model="form.password"
id="password"
type="password"
:required="false"
@input="clearError('password')"
/>
<p class="text-sm">Kosongkan jika tidak ingin ubah password</p>
<p v-if="errors.password" class="text-red-500 text-sm">{{ errors.password }}</p>
</div>
<!-- Role -->
<div>
<label for="role" class="block text-sm font-medium">Peran</label>
<InputSelect
v-model="form.role"
:options="[
{ value: 'owner', label: 'Owner' },
{ value: 'kasir', label: 'Kasir' }
]"
placeholder="-- Pilih Peran --"
@change="clearError('role')"
/>
<p v-if="errors.role" class="text-red-500 text-sm">{{ errors.role }}</p>
</div>
<!-- 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-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
>
Ubah
</button>
</div>
</form>
<!-- Error global -->
<p v-if="errorMessage" class="text-red-500 text-sm mt-3">
{{ errorMessage }}
</p>
<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">Edit Akun</h2>
<form @submit.prevent="updateAkun" class="space-y-3">
<!-- Nama -->
<div>
<label for="nama" class="block text-sm font-medium">Nama</label>
<InputField
v-model="form.nama"
id="nama"
type="text"
:required="true"
@input="clearError('nama')"
/>
<p v-if="errors.nama" class="text-red-500 text-sm">{{ errors.nama }}</p>
</div>
<!-- Password -->
<div>
<label for="password" class="block text-sm font-medium">Password</label>
<InputPassword
v-model="form.password"
id="password"
type="password"
:required="false"
@input="clearError('password')"
/>
<p class="text-sm">Kosongkan jika tidak ingin ubah password</p>
<p v-if="errors.password" class="text-red-500 text-sm">{{ errors.password }}</p>
</div>
<!-- Confirm Password -->
<div v-if="form.password">
<label for="confirmPassword" class="block text-sm font-medium">Konfirmasi Password</label>
<InputPassword
v-model="form.confirmPassword"
id="confirmPassword"
type="password"
:required="false"
@input="clearError('confirmPassword')"
/>
<p v-if="errors.confirmPassword" class="text-red-500 text-sm">
{{ errors.confirmPassword }}
</p>
</div>
<!-- Role -->
<div>
<label for="role" class="block text-sm font-medium">Peran</label>
<!-- 🔒 Kalau akun sendiri tampil readonly -->
<template v-if="isEditingSelf">
<p class="mt-1 px-3 py-2 border rounded bg-gray-100 text-gray-700">
{{ form.role === 'owner' ? 'Owner' : 'Kasir' }}
</p>
</template>
<!-- 🔓 Kalau akun lain bisa diubah -->
<template v-else>
<InputSelect
v-model="form.role"
:options="[
{ value: 'owner', label: 'Owner' },
{ value: 'kasir', label: 'Kasir' }
]"
placeholder="-- Pilih Peran --"
@change="clearError('role')"
/>
<p v-if="errors.role" class="text-red-500 text-sm">{{ errors.role }}</p>
</template>
</div>
<!-- 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-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="!isFormValid"
>
Ubah
</button>
</div>
</form>
<!-- Error global -->
<p v-if="errorMessage" class="text-red-500 text-sm mt-3">
{{ errorMessage }}
</p>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import InputField from "@/components/InputField.vue";
import InputSelect from "@/components/InputSelect.vue";
export default {
name: "EditAkun",
props: {
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import axios from "axios";
import InputField from "@/components/InputField.vue";
import InputSelect from "@/components/InputSelect.vue";
import InputPassword from "./InputPassword.vue";
const props = defineProps({
akun: {
type: Object,
required: true,
},
},
components: { InputField, InputSelect },
data() {
return {
form: {
nama: this.akun?.nama || "",
password: "",
role: this.akun?.role || "",
},
errors: { nama: "", password: "", role: "" },
errorMessage: "",
};
},
watch: {
akun: {
handler(newVal) {
if (newVal) {
this.form = {
nama: newVal.nama || "",
password: "",
role: newVal.role || "",
};
this.errors = { nama: "", password: "", role: "" };
this.errorMessage = "";
}
},
deep: true,
immediate: true,
},
},
methods: {
clearError(field) {
this.errors[field] = "";
this.errorMessage = "";
},
validateForm() {
let valid = true;
this.errors = { nama: "", password: "", role: "" };
if (!this.form.nama) {
this.errors.nama = "Nama wajib diisi";
valid = false;
}
if (this.form.password && this.form.password.length < 6) {
this.errors.password = "Password minimal 6 karakter";
valid = false;
}
if (!this.form.role) {
this.errors.role = "Role wajib dipilih";
valid = false;
} else if (!["owner", "kasir"].includes(this.form.role)) {
this.errors.role = "Role harus owner atau kasir";
valid = false;
}
return valid;
},
async updateAkun() {
if (!this.validateForm()) return;
try {
const payload = { ...this.form };
if (!payload.password) delete payload.password;
await axios.put(`/api/user/${this.akun.id}`, payload, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
const emit = defineEmits(["refresh", "close"]);
const form = ref({
nama: props.akun?.nama || "",
password: "",
confirmPassword: "",
role: props.akun?.role || "",
});
const errors = ref({ nama: "", password: "", confirmPassword: "", role: "" });
const errorMessage = ref("");
const loggedInId = ref(localStorage.getItem("userId")); // 🔥 ambil dari localStorage
const isFormValid = computed(() => {
if (form.value.password && form.value.password !== form.value.confirmPassword) {
return false;
}
return (
form.value.nama.trim() &&
form.value.role &&
!errors.value.nama &&
!errors.value.password &&
!errors.value.confirmPassword &&
!errors.value.role
);
});
// 🔥 ini cek apakah akun yang diedit adalah akun sendiri
const isEditingSelf = computed(() => {
return String(props.akun.id) === String(loggedInId.value);
});
const clearError = (field) => {
errors.value[field] = "";
errorMessage.value = "";
};
const validateForm = () => {
let valid = true;
errors.value = { nama: "", password: "", confirmPassword: "", role: "" };
if (!form.value.nama) {
errors.value.nama = "Nama wajib diisi";
valid = false;
}
if (form.value.password && form.value.password.length < 6) {
errors.value.password = "Password minimal 6 karakter";
valid = false;
}
if (form.value.password && form.value.password !== form.value.confirmPassword) {
errors.value.confirmPassword = "Konfirmasi password tidak cocok";
valid = false;
}
if (!form.value.role) {
errors.value.role = "Role wajib dipilih";
valid = false;
}
return valid;
};
const updateAkun = async () => {
if (!validateForm()) return;
try {
const payload = { ...form.value };
if (!payload.password) delete payload.password;
delete payload.confirmPassword;
await axios.put(`/api/user/${props.akun.id}`, payload, {
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
});
emit("refresh");
emit("close");
} catch (err) {
if (err.response?.status === 422 && err.response.data.errors) {
const backendErrors = err.response.data.errors;
Object.keys(backendErrors).forEach((key) => {
errors.value[key] = backendErrors[key][0];
});
this.$emit("refresh");
this.$emit("close");
} catch (err) {
if (err.response?.status === 422 && err.response.data.errors) {
const backendErrors = err.response.data.errors;
Object.keys(backendErrors).forEach((key) => {
this.errors[key] = backendErrors[key][0];
});
} else {
this.errorMessage =
err.response?.data?.message || "Gagal update akun.";
}
console.error("Gagal update akun:", err);
} else {
errorMessage.value = err.response?.data?.message || "Gagal update akun.";
}
},
},
};
</script>
console.error("Gagal update akun:", err);
}
};
onMounted(() => {
console.log("Akun.id:", props.akun.id);
console.log("LoggedInId:", loggedInId.value);
console.log("isEditingSelf:", isEditingSelf.value);
});
</script>

View File

@ -1,12 +1,12 @@
<template>
<div class="relative mb-8">
<div class="relative mb-1">
<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
bg-A text-D border-B focus:border-C
focus:ring focus:ring-D focus:ring-opacity-50 p-2 pr-10"
/>

View File

@ -51,14 +51,14 @@
<!-- Table Section -->
<div
class="bg-white rounded-lg shadow-md border border-C overflow-hidden"
class="bg-white rounded-lg shadow-md border border-D 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 border-r border-D">No</th>
<th class="px-6 py-4 text-center text-D border-r border-D">Nama</th>
<th class="px-6 py-4 text-center text-D border-r border-D">Peran</th>
<th class="px-6 py-4 text-center text-D">Aksi</th>
</tr>
</thead>
@ -66,16 +66,16 @@
<tr
v-for="(item, index) in akun"
:key="item.id"
class="border-b border-C hover:bg-gray-50 transition duration-150"
class="border-b border-D 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">
<td class="px-6 py-4 border-r border-D text-center font-medium text-gray-900">
{{ index + 1 }}
</td>
<td class="px-6 py-4 border-r border-C text-D">
<td class="px-6 py-4 border-r border-D text-D">
{{ item.nama }}
</td>
<td class="px-6 py-4 border-r border-C text-gray-800">
<td class="px-6 py-4 border-r border-D text-gray-800">
{{ item.role }}
</td>
<td class="px-6 py-4 text-center">

View File

@ -1,70 +1,86 @@
<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"
:disabled="loading"
class="w-full py-2 bg-sky-400 hover:bg-sky-500 rounded font-bold text-gray-800 transition disabled:opacity-50"
>
{{ loading ? "Loading..." : "Login" }}
</button>
<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"
/>
<InputPassword v-model="password" placeholder="Password" />
<div
v-if="errorMessage"
class="mt-2 text-red-500 text-xs font-medium text-left"
>
{{ errorMessage }}
</div>
</div>
<!-- Button -->
<button
@click="handleLogin"
:disabled="loading"
class="w-full mt-6 py-2 bg-sky-400 hover:bg-sky-500 rounded font-bold text-gray-800 transition disabled:opacity-50"
>
{{ loading ? "Loading..." : "Login" }}
</button>
</div>
</div>
</template>
</template>
<script setup>
import { ref } from "vue";
import logo from '@/../images/logo.png'
import logo from "@/../images/logo.png";
import InputField from "@/components/InputField.vue";
import PasswordInput from "@/components/InputPassword.vue";
import InputPassword from "@/components/InputPassword.vue";
import axios from "axios";
const username = ref("");
const password = ref("");
const loading = ref(false);
const errorMessage = ref("");
const handleLogin = async () => {
if (!username.value || !password.value) {
alert("Harap isi username dan password!");
return;
}
if (!username.value || !password.value) {
errorMessage.value = "Harap isi username dan password!";
return;
}
loading.value = true;
try {
const res = await axios.post("/api/login", {
nama: username.value,
password: password.value,
});
loading.value = true;
errorMessage.value = "";
try {
const res = await axios.post("/api/login", {
nama: username.value,
password: password.value,
});
const data = res.data;
const data = res.data;
// Simpan token & role
localStorage.setItem("token", data.token);
localStorage.setItem("role", data.role);
// Simpan token & role
localStorage.setItem("token", data.token);
localStorage.setItem("role", data.role);
localStorage.setItem("userId", data.user.id);
localStorage.setItem("nama", data.user.nama);
localStorage.setItem("role", data.user.role);
// Redirect sesuai role
window.location.href = data.redirect;
} catch (error) {
console.error(error);
alert("Login gagal. Periksa username atau password.");
// Redirect sesuai role
window.location.href = data.redirect;
} catch (error) {
if (error.response?.data?.message) {
errorMessage.value = error.response.data.message;
} else {
errorMessage.value = "Login gagal. Periksa username atau password.";
}
} finally {
loading.value = false;
}