This commit is contained in:
Flatlogic Bot 2025-10-03 14:19:02 +00:00
parent 8d0038be8c
commit 2be5f009ee
22 changed files with 1445 additions and 52 deletions

33
api/add_office.php Normal file
View File

@ -0,0 +1,33 @@
<?php
// api/add_office.php
header('Content-Type: application/json');
require_once '../db/config.php';
session_start();
// Authentication and Authorization check
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'super_admin') {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
// Basic validation
if (empty($_POST['nama_kantor']) || empty($_POST['tipe_kantor'])) {
echo json_encode(['success' => false, 'message' => 'Nama kantor and tipe are required.']);
exit;
}
$nama_kantor = $_POST['nama_kantor'];
$alamat = $_POST['alamat'] ?? null;
$tipe_kantor = $_POST['tipe_kantor'];
try {
$sql = "INSERT INTO kantor (nama_kantor, alamat, tipe_kantor) VALUES (?, ?, ?)";
$stmt = db()->prepare($sql);
$stmt->execute([$nama_kantor, $alamat, $tipe_kantor]);
echo json_encode(['success' => true, 'message' => 'Office added successfully.']);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}
?>

48
api/add_user.php Normal file
View File

@ -0,0 +1,48 @@
<?php
// api/add_user.php
header('Content-Type: application/json');
require_once '../db/config.php';
session_start();
// Authentication and Authorization check
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'super_admin') {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
// Basic validation
if (empty($_POST['nama_lengkap']) || empty($_POST['email']) || empty($_POST['password']) || empty($_POST['role'])) {
echo json_encode(['success' => false, 'message' => 'Please fill all required fields.']);
exit;
}
$nama_lengkap = $_POST['nama_lengkap'];
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
$role = $_POST['role'];
$id_kantor = !empty($_POST['id_kantor']) ? filter_input(INPUT_POST, 'id_kantor', FILTER_SANITIZE_NUMBER_INT) : null;
if (!$email) {
echo json_encode(['success' => false, 'message' => 'Invalid email format.']);
exit;
}
// Check for existing email
try {
$stmt = db()->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$email]);
if ($stmt->fetch()) {
echo json_encode(['success' => false, 'message' => 'Email already exists.']);
exit;
}
$sql = "INSERT INTO users (nama_lengkap, email, password, role, id_kantor) VALUES (?, ?, ?, ?, ?)";
$stmt = db()->prepare($sql);
$stmt->execute([$nama_lengkap, $email, $password, $role, $id_kantor]);
echo json_encode(['success' => true, 'message' => 'User added successfully.']);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}
?>

42
api/delete_asset.php Normal file
View File

@ -0,0 +1,42 @@
<?php
require_once '../db/config.php';
session_start();
// Basic security checks
if (!isset($_SESSION['user_id'])) {
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'Akses ditolak.']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Metode tidak diizinkan.']);
exit;
}
$data = json_decode(file_get_contents('php://input'), true);
$assetId = $data['id'] ?? null;
if (!$assetId) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'ID Aset tidak valid.']);
exit;
}
try {
$pdo = db();
$stmt = $pdo->prepare("DELETE FROM aset WHERE id = ?");
$stmt->execute([$assetId]);
if ($stmt->rowCount() > 0) {
echo json_encode(['success' => true, 'message' => 'Aset berhasil dihapus.']);
} else {
http_response_code(404);
echo json_encode(['success' => false, 'message' => 'Aset tidak ditemukan.']);
}
} catch (PDOException $e) {
http_response_code(500);
// In production, log this error instead of echoing it.
echo json_encode(['success' => false, 'message' => 'Gagal menghapus aset: ' . $e->getMessage()]);
}

46
api/delete_office.php Normal file
View File

@ -0,0 +1,46 @@
<?php
// api/delete_office.php
header('Content-Type: application/json');
require_once '../db/config.php';
session_start();
// Authentication and Authorization check
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'super_admin') {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
if (empty($_POST['id'])) {
echo json_encode(['success' => false, 'message' => 'Office ID is required.']);
exit;
}
$id = filter_input(INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT);
try {
// The foreign key constraints in `users` and `aset` tables are set to
// `ON DELETE SET NULL` and `ON DELETE RESTRICT` respectively.
// We should check for assets before deleting.
$stmt_check = db()->prepare("SELECT COUNT(*) FROM aset WHERE id_kantor_lokasi = ?");
$stmt_check->execute([$id]);
if ($stmt_check->fetchColumn() > 0) {
echo json_encode(['success' => false, 'message' => 'Cannot delete office. It is still associated with existing assets. Please reassign assets first.']);
exit;
}
// If no assets are linked, proceed with deletion.
// Users linked to this office will have their id_kantor set to NULL.
$stmt_delete = db()->prepare("DELETE FROM kantor WHERE id = ?");
$stmt_delete->execute([$id]);
if ($stmt_delete->rowCount() > 0) {
echo json_encode(['success' => true, 'message' => 'Office deleted successfully.']);
} else {
echo json_encode(['success' => false, 'message' => 'Office not found or could not be deleted.']);
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}
?>

42
api/delete_user.php Normal file
View File

@ -0,0 +1,42 @@
<?php
// api/delete_user.php
header('Content-Type: application/json');
require_once '../db/config.php';
session_start();
// Authentication and Authorization check
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'super_admin') {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
if (empty($_POST['id'])) {
echo json_encode(['success' => false, 'message' => 'User ID is required.']);
exit;
}
$id = filter_input(INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT);
// Prevent super admin from deleting themselves
if ($id == $_SESSION['user_id']) {
echo json_encode(['success' => false, 'message' => 'You cannot delete your own account.']);
exit;
}
try {
// Optional: Check if user has related records (e.g., assets) before deleting
// For now, we rely on the database foreign key constraint (ON DELETE SET NULL)
$stmt = db()->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$id]);
if ($stmt->rowCount() > 0) {
echo json_encode(['success' => true, 'message' => 'User deleted successfully.']);
} else {
echo json_encode(['success' => false, 'message' => 'User not found or could not be deleted.']);
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}
?>

35
api/get_asset.php Normal file
View File

@ -0,0 +1,35 @@
<?php
require_once '../db/config.php';
session_start();
if (!isset($_SESSION['user_id'])) {
http_response_code(403);
echo json_encode(['error' => 'Akses ditolak.']);
exit;
}
$assetId = $_GET['id'] ?? null;
if (!$assetId) {
http_response_code(400);
echo json_encode(['error' => 'ID Aset tidak valid.']);
exit;
}
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM aset WHERE id = ?");
$stmt->execute([$assetId]);
$asset = $stmt->fetch(PDO::FETCH_ASSOC);
if ($asset) {
header('Content-Type: application/json');
echo json_encode($asset);
} else {
http_response_code(404);
echo json_encode(['error' => 'Aset tidak ditemukan.']);
}
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Gagal mengambil data aset: ' . $e->getMessage()]);
}

32
api/get_offices.php Normal file
View File

@ -0,0 +1,32 @@
<?php
// api/get_offices.php
header('Content-Type: application/json');
require_once '../db/config.php';
session_start();
// Authentication and Authorization check
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'super_admin') {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
try {
$sql = "SELECT * FROM kantor ORDER BY nama_kantor ASC";
$params = [];
// If an ID is provided, fetch a single office
if (isset($_GET['id']) && !empty($_GET['id'])) {
$sql = "SELECT * FROM kantor WHERE id = ?";
$params[] = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
}
$stmt = db()->prepare($sql);
$stmt->execute($params);
$offices = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'offices' => $offices]);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}
?>

34
api/get_user.php Normal file
View File

@ -0,0 +1,34 @@
<?php
// api/get_user.php
header('Content-Type: application/json');
require_once '../db/config.php';
session_start();
// Authentication and Authorization check
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'super_admin') {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
if (!isset($_GET['id'])) {
echo json_encode(['success' => false, 'message' => 'User ID is required']);
exit;
}
$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
try {
$stmt = db()->prepare("SELECT id, nama_lengkap, email, role, id_kantor FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
echo json_encode(['success' => true, 'user' => $user]);
} else {
echo json_encode(['success' => false, 'message' => 'User not found']);
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}
?>

26
api/get_users.php Normal file
View File

@ -0,0 +1,26 @@
<?php
// api/get_users.php
header('Content-Type: application/json');
require_once '../db/config.php';
session_start();
// Authentication and Authorization check
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'super_admin') {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
try {
$sql = "SELECT u.id, u.nama_lengkap, u.email, u.role, u.created_at, k.nama_kantor
FROM users u
LEFT JOIN kantor k ON u.id_kantor = k.id
ORDER BY u.nama_lengkap ASC";
$stmt = db()->query($sql);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'users' => $users]);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}
?>

73
api/update_asset.php Normal file
View File

@ -0,0 +1,73 @@
<?php
require_once '../db/config.php';
session_start();
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Metode tidak diizinkan.']);
exit;
}
if (!isset($_SESSION['user_id'])) {
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'Akses ditolak.']);
exit;
}
// Get data from POST request
$id = $_POST['id'] ?? null;
$nama_aset = $_POST['nama_aset'] ?? '';
$id_kategori = $_POST['id_kategori'] ?? null;
$id_kantor_lokasi = $_POST['id_kantor_lokasi'] ?? null;
$spesifikasi = $_POST['spesifikasi'] ?? '';
$tanggal_pembelian = $_POST['tanggal_pembelian'] ?? null;
$harga_pembelian = $_POST['harga_pembelian'] ?? null;
$vendor = $_POST['vendor'] ?? '';
$status = $_POST['status'] ?? '';
// Basic validation
if (empty($id) || empty($nama_aset) || empty($id_kategori) || empty($id_kantor_lokasi) || empty($tanggal_pembelian) || empty($harga_pembelian) || empty($status)) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Semua field yang wajib diisi harus diisi.']);
exit;
}
try {
$pdo = db();
$sql = "UPDATE aset SET
nama_aset = :nama_aset,
id_kategori = :id_kategori,
id_kantor_lokasi = :id_kantor_lokasi,
spesifikasi = :spesifikasi,
tanggal_pembelian = :tanggal_pembelian,
harga_pembelian = :harga_pembelian,
vendor = :vendor,
status = :status,
updated_at = NOW()
WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->bindParam(':nama_aset', $nama_aset, PDO::PARAM_STR);
$stmt->bindParam(':id_kategori', $id_kategori, PDO::PARAM_INT);
$stmt->bindParam(':id_kantor_lokasi', $id_kantor_lokasi, PDO::PARAM_INT);
$stmt->bindParam(':spesifikasi', $spesifikasi, PDO::PARAM_STR);
$stmt->bindParam(':tanggal_pembelian', $tanggal_pembelian, PDO::PARAM_STR);
$stmt->bindParam(':harga_pembelian', $harga_pembelian);
$stmt->bindParam(':vendor', $vendor, PDO::PARAM_STR);
$stmt->bindParam(':status', $status, PDO::PARAM_STR);
if ($stmt->execute()) {
echo json_encode(['success' => true, 'message' => 'Aset berhasil diperbarui.']);
} else {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Gagal memperbarui aset.']);
}
} catch (PDOException $e) {
http_response_code(500);
// In production, log this error.
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}

34
api/update_office.php Normal file
View File

@ -0,0 +1,34 @@
<?php
// api/update_office.php
header('Content-Type: application/json');
require_once '../db/config.php';
session_start();
// Authentication and Authorization check
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'super_admin') {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
// Basic validation
if (empty($_POST['id']) || empty($_POST['nama_kantor']) || empty($_POST['tipe_kantor'])) {
echo json_encode(['success' => false, 'message' => 'Incomplete data for update.']);
exit;
}
$id = filter_input(INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT);
$nama_kantor = $_POST['nama_kantor'];
$alamat = $_POST['alamat'] ?? null;
$tipe_kantor = $_POST['tipe_kantor'];
try {
$sql = "UPDATE kantor SET nama_kantor = ?, alamat = ?, tipe_kantor = ? WHERE id = ?";
$stmt = db()->prepare($sql);
$stmt->execute([$nama_kantor, $alamat, $tipe_kantor, $id]);
echo json_encode(['success' => true, 'message' => 'Office updated successfully.']);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}
?>

56
api/update_user.php Normal file
View File

@ -0,0 +1,56 @@
<?php
// api/update_user.php
header('Content-Type: application/json');
require_once '../db/config.php';
session_start();
// Authentication and Authorization check
if (!isset($_SESSION['user_id']) || $_SESSION['user_role'] !== 'super_admin') {
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
exit;
}
// Basic validation
if (empty($_POST['id']) || empty($_POST['nama_lengkap']) || empty($_POST['email']) || empty($_POST['role'])) {
echo json_encode(['success' => false, 'message' => 'Incomplete data for update.']);
exit;
}
$id = filter_input(INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT);
$nama_lengkap = $_POST['nama_lengkap'];
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$role = $_POST['role'];
$id_kantor = !empty($_POST['id_kantor']) ? filter_input(INPUT_POST, 'id_kantor', FILTER_SANITIZE_NUMBER_INT) : null;
if (!$email) {
echo json_encode(['success' => false, 'message' => 'Invalid email format.']);
exit;
}
try {
// Check if email is used by another user
$stmt = db()->prepare("SELECT id FROM users WHERE email = ? AND id != ?");
$stmt->execute([$email, $id]);
if ($stmt->fetch()) {
echo json_encode(['success' => false, 'message' => 'Email is already in use by another account.']);
exit;
}
// Handle password update
if (!empty($_POST['password'])) {
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
$sql = "UPDATE users SET nama_lengkap = ?, email = ?, password = ?, role = ?, id_kantor = ? WHERE id = ?";
$stmt = db()->prepare($sql);
$stmt->execute([$nama_lengkap, $email, $password, $role, $id_kantor, $id]);
} else {
$sql = "UPDATE users SET nama_lengkap = ?, email = ?, role = ?, id_kantor = ? WHERE id = ?";
$stmt = db()->prepare($sql);
$stmt->execute([$nama_lengkap, $email, $role, $id_kantor, $id]);
}
echo json_encode(['success' => true, 'message' => 'User updated successfully.']);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
}
?>

View File

@ -1,13 +1,15 @@
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// --- Constants --- // --- Constants ---
const API_BASE = 'api/'; const API_BASE = 'api/';
const addAssetForm = document.getElementById('addAssetForm'); const addAssetForm = document.getElementById('addAssetForm');
const editAssetForm = document.getElementById('editAssetForm');
const assetsTableBody = document.getElementById('assets-table-body'); const assetsTableBody = document.getElementById('assets-table-body');
const loadingIndicator = document.getElementById('loading-indicator'); const loadingIndicator = document.getElementById('loading-indicator');
const notificationToastEl = document.getElementById('notificationToast'); const notificationToastEl = document.getElementById('notificationToast');
const notificationToast = new bootstrap.Toast(notificationToastEl); const notificationToast = new bootstrap.Toast(notificationToastEl);
const editAssetModalEl = document.getElementById('editAssetModal');
const editAssetModal = new bootstrap.Modal(editAssetModalEl);
// --- Functions --- // --- Functions ---
@ -20,8 +22,7 @@ document.addEventListener('DOMContentLoaded', function () {
function showToast(title, body, type = 'success') { function showToast(title, body, type = 'success') {
const toastHeader = notificationToastEl.querySelector('.toast-header'); const toastHeader = notificationToastEl.querySelector('.toast-header');
// Reset classes toastHeader.classList.remove('bg-success', 'text-white', 'bg-danger', 'text-white');
toastHeader.classList.remove('bg-success', 'text-white', 'bg-danger');
if (type === 'success') { if (type === 'success') {
toastHeader.classList.add('bg-success', 'text-white'); toastHeader.classList.add('bg-success', 'text-white');
@ -37,40 +38,45 @@ document.addEventListener('DOMContentLoaded', function () {
/** /**
* Fetches data from the API. * Fetches data from the API.
* @param {string} endpoint - The API endpoint to fetch from. * @param {string} endpoint - The API endpoint to fetch from.
* @param {object} options - Optional fetch options.
* @returns {Promise<any>} - The JSON response. * @returns {Promise<any>} - The JSON response.
*/ */
async function fetchApi(endpoint) { async function fetchApi(endpoint, options = {}) {
try { try {
const response = await fetch(API_BASE + endpoint); const response = await fetch(API_BASE + endpoint, options);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); const errorData = await response.json().catch(() => ({ message: 'Gagal memuat detail error.' }));
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
} }
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error(`Error fetching ${endpoint}:`, error); console.error(`Error fetching ${endpoint}:`, error);
showToast('Error', `Gagal memuat data dari server: ${error.message}`, 'error'); showToast('Error', `Terjadi kesalahan: ${error.message}`, 'error');
return null; return null;
} }
} }
/** /**
* Populates select dropdowns. * Populates select dropdowns for both add and edit modals.
*/ */
async function populateSelectOptions() { async function populateSelectOptions() {
const data = await fetchApi('get_options.php'); const data = await fetchApi('get_options.php');
if (!data) return; if (!data) return;
const kategoriSelect = document.getElementById('id_kategori'); const selects = [
const kantorSelect = document.getElementById('id_kantor_lokasi'); { el: document.getElementById('id_kategori'), data: data.kategori, placeholder: 'Pilih Kategori...', name: 'nama_kategori' },
{ el: document.getElementById('id_kantor_lokasi'), data: data.kantor, placeholder: 'Pilih Lokasi...', name: 'nama_kantor' },
{ el: document.getElementById('edit_id_kategori'), data: data.kategori, placeholder: 'Pilih Kategori...', name: 'nama_kategori' },
{ el: document.getElementById('edit_id_kantor_lokasi'), data: data.kantor, placeholder: 'Pilih Lokasi...', name: 'nama_kantor' }
];
kategoriSelect.innerHTML = '<option value="">Pilih Kategori...</option>'; selects.forEach(s => {
data.kategori.forEach(item => { if (s.el) {
kategoriSelect.innerHTML += `<option value="${item.id}">${item.nama_kategori}</option>`; s.el.innerHTML = `<option value="">${s.placeholder}</option>`;
}); s.data.forEach(item => {
s.el.innerHTML += `<option value="${item.id}">${item[s.name]}</option>`;
kantorSelect.innerHTML = '<option value="">Pilih Lokasi...</option>'; });
data.kantor.forEach(item => { }
kantorSelect.innerHTML += `<option value="${item.id}">${item.nama_kantor}</option>`;
}); });
} }
@ -93,10 +99,14 @@ document.addEventListener('DOMContentLoaded', function () {
<td>${asset.nama_aset}</td> <td>${asset.nama_aset}</td>
<td>${asset.nama_kategori}</td> <td>${asset.nama_kategori}</td>
<td>${asset.nama_kantor}</td> <td>${asset.nama_kantor}</td>
<td><span class="status-badge status-${asset.status}">${asset.status}</span></td> <td><span class="status-badge status-${asset.status.toLowerCase()}">${asset.status}</span></td>
<td> <td>
<button class="btn btn-sm btn-outline-primary" onclick="alert('Fitur Edit belum tersedia.')"><i data-feather="edit-2" class="feather-sm"></i></button> <button class="btn btn-sm btn-outline-primary btn-edit" data-asset-id="${asset.id}" title="Edit">
<button class="btn btn-sm btn-outline-danger" onclick="alert('Fitur Hapus belum tersedia.')"><i data-feather="trash-2" class="feather-sm"></i></button> <i data-feather="edit-2" class="feather-sm"></i>
</button>
<button class="btn btn-sm btn-outline-danger btn-delete" data-asset-id="${asset.id}" title="Hapus">
<i data-feather="trash-2" class="feather-sm"></i>
</button>
</td> </td>
</tr> </tr>
`; `;
@ -107,12 +117,11 @@ document.addEventListener('DOMContentLoaded', function () {
} else { } else {
assetsTableBody.innerHTML = '<tr><td colspan="6" class="text-center text-danger">Gagal memuat data aset.</td></tr>'; assetsTableBody.innerHTML = '<tr><td colspan="6" class="text-center text-danger">Gagal memuat data aset.</td></tr>';
} }
feather.replace(); // Re-initialize Feather icons feather.replace();
} }
/** /**
* Handles the submission of the 'Add Asset' form. * Handles the submission of the 'Add Asset' form.
* @param {Event} event - The form submission event.
*/ */
async function handleAddAssetSubmit(event) { async function handleAddAssetSubmit(event) {
event.preventDefault(); event.preventDefault();
@ -128,41 +137,122 @@ document.addEventListener('DOMContentLoaded', function () {
submitButton.disabled = true; submitButton.disabled = true;
submitButton.innerHTML = '''<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Menyimpan...'''; submitButton.innerHTML = '''<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Menyimpan...''';
try { const result = await fetchApi('add_asset.php', { method: 'POST', body: formData });
const response = await fetch(API_BASE + 'add_asset.php', {
method: 'POST',
body: formData
});
const result = await response.json(); if (result && result.success) {
showToast('Sukses', 'Aset baru berhasil ditambahkan.');
addAssetForm.reset();
addAssetForm.classList.remove('was-validated');
bootstrap.Modal.getInstance(document.getElementById('addAssetModal')).hide();
loadAssets();
} else if (result) {
showToast('Error', result.message, 'error');
}
if (result.success) { submitButton.disabled = false;
showToast('Sukses', 'Aset baru berhasil ditambahkan.'); submitButton.innerHTML = 'Simpan Aset';
addAssetForm.reset(); }
addAssetForm.classList.remove('was-validated');
bootstrap.Modal.getInstance(document.getElementById('addAssetModal')).hide(); /**
loadAssets(); // Refresh the table * Opens the edit modal and populates it with asset data.
} else { * @param {number} assetId - The ID of the asset to edit.
showToast('Error', result.message || 'Terjadi kesalahan yang tidak diketahui.', 'error'); */
} async function openEditModal(assetId) {
} catch (error) { const asset = await fetchApi(`get_asset.php?id=${assetId}`);
console.error('Error submitting form:', error); if (!asset) return;
showToast('Error', `Gagal mengirim data: ${error.message}`, 'error');
} finally { document.getElementById('edit_id').value = asset.id;
submitButton.disabled = false; document.getElementById('edit_nama_aset').value = asset.nama_aset;
submitButton.innerHTML = 'Simpan Aset'; document.getElementById('edit_id_kategori').value = asset.id_kategori;
document.getElementById('edit_id_kantor_lokasi').value = asset.id_kantor_lokasi;
document.getElementById('edit_spesifikasi').value = asset.spesifikasi;
document.getElementById('edit_tanggal_pembelian').value = asset.tanggal_pembelian;
document.getElementById('edit_harga_pembelian').value = asset.harga_pembelian;
document.getElementById('edit_vendor').value = asset.vendor;
document.getElementById('edit_status').value = asset.status;
editAssetModal.show();
}
/**
* Handles the submission of the 'Edit Asset' form.
*/
async function handleEditAssetSubmit(event) {
event.preventDefault();
event.stopPropagation();
if (!editAssetForm.checkValidity()) {
editAssetForm.classList.add('was-validated');
return;
}
const formData = new FormData(editAssetForm);
const submitButton = editAssetForm.querySelector('button[type="submit"]');
submitButton.disabled = true;
submitButton.innerHTML = '''<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Menyimpan...''';
const result = await fetchApi('update_asset.php', { method: 'POST', body: formData });
if (result && result.success) {
showToast('Sukses', 'Aset berhasil diperbarui.');
editAssetModal.hide();
loadAssets();
} else if (result) {
showToast('Error', result.message, 'error');
}
submitButton.disabled = false;
submitButton.innerHTML = 'Simpan Perubahan';
}
/**
* Handles the deletion of an asset.
* @param {number} assetId - The ID of the asset to delete.
*/
async function handleDeleteAsset(assetId) {
if (!confirm('Anda yakin ingin menghapus aset ini? Tindakan ini tidak dapat dibatalkan.')) {
return;
}
const result = await fetchApi('delete_asset.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: assetId })
});
if (result && result.success) {
showToast('Sukses', 'Aset berhasil dihapus.');
loadAssets();
} else if (result) {
showToast('Error', result.message, 'error');
} }
} }
// --- Initializations --- // --- Initializations ---
feather.replace(); // Initial call for icons feather.replace();
populateSelectOptions(); populateSelectOptions();
loadAssets(); loadAssets();
// --- Event Listeners --- // --- Event Listeners ---
addAssetForm.addEventListener('submit', handleAddAssetSubmit); addAssetForm.addEventListener('submit', handleAddAssetSubmit);
editAssetForm.addEventListener('submit', handleEditAssetSubmit);
assetsTableBody.addEventListener('click', function(event) {
const editButton = event.target.closest('.btn-edit');
const deleteButton = event.target.closest('.btn-delete');
if (editButton) {
const assetId = editButton.dataset.assetId;
openEditModal(assetId);
}
if (deleteButton) {
const assetId = deleteButton.dataset.assetId;
handleDeleteAsset(assetId);
}
});
// Add feather icon class for easier styling // Add feather icon class for easier styling
document.querySelectorAll('i[data-feather]').forEach(el => { document.querySelectorAll('i[data-feather]').forEach(el => {

114
assets/js/offices.js Normal file
View File

@ -0,0 +1,114 @@
document.addEventListener('DOMContentLoaded', function () {
const officeModal = new bootstrap.Modal(document.getElementById('office-modal'));
const officeForm = document.getElementById('office-form');
const officeTable = document.getElementById('offices-table').getElementsByTagName('tbody')[0];
const modalLabel = document.getElementById('office-modal-label');
// Function to fetch and display offices
function loadOffices() {
fetch('api/get_offices.php')
.then(response => response.json())
.then(data => {
officeTable.innerHTML = '';
if(data.success) {
data.offices.forEach(office => {
const row = officeTable.insertRow();
row.innerHTML = `
<td>${office.nama_kantor}</td>
<td>${office.alamat}</td>
<td>${office.tipe_kantor}</td>
<td>
<button class="btn btn-sm btn-outline-secondary edit-btn" data-id="${office.id}"><i data-feather="edit-2" class="feather-sm"></i></button>
<button class="btn btn-sm btn-outline-danger delete-btn" data-id="${office.id}"><i data-feather="trash-2" class="feather-sm"></i></button>
</td>
`;
});
feather.replace();
}
})
.catch(error => console.error('Error loading offices:', error));
}
// Show modal for adding a new office
document.getElementById('btn-add-office').addEventListener('click', function () {
officeForm.reset();
document.getElementById('office-id').value = '';
modalLabel.textContent = 'Tambah Kantor Baru';
officeModal.show();
});
// Handle form submission for both add and edit
officeForm.addEventListener('submit', function (e) {
e.preventDefault();
const formData = new FormData(officeForm);
const officeId = formData.get('id');
const url = officeId ? 'api/update_office.php' : 'api/add_office.php';
fetch(url, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
officeModal.hide();
loadOffices(); // Refresh the table
} else {
alert('Error: ' + data.message);
}
})
.catch(error => console.error('Form submission error:', error));
});
// Handle edit and delete button clicks
officeTable.addEventListener('click', function (e) {
const target = e.target.closest('button');
if (!target) return;
const id = target.dataset.id;
// Edit button
if (target.classList.contains('edit-btn')) {
// Fetch full details to edit
fetch(`api/get_offices.php?id=${id}`) // We can reuse get_offices and filter by id server-side
.then(response => response.json())
.then(data => {
if (data.success && data.offices.length > 0) {
const office = data.offices[0];
document.getElementById('office-id').value = office.id;
document.getElementById('nama_kantor').value = office.nama_kantor;
document.getElementById('alamat').value = office.alamat;
document.getElementById('tipe_kantor').value = office.tipe_kantor;
modalLabel.textContent = 'Edit Kantor';
officeModal.show();
} else {
alert('Error: Could not fetch office details.');
}
});
}
// Delete button
if (target.classList.contains('delete-btn')) {
if (confirm('Apakah Anda yakin ingin menghapus kantor ini? Ini mungkin akan mempengaruhi pengguna dan aset yang terkait.')) {
fetch('api/delete_office.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `id=${id}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
loadOffices(); // Refresh the table
} else {
alert('Error: ' + data.message);
}
})
.catch(error => console.error('Delete error:', error));
}
}
});
// Initial load of offices
loadOffices();
});

128
assets/js/users.js Normal file
View File

@ -0,0 +1,128 @@
document.addEventListener('DOMContentLoaded', function () {
const userModal = new bootstrap.Modal(document.getElementById('user-modal'));
const userForm = document.getElementById('user-form');
const userTable = document.getElementById('users-table').getElementsByTagName('tbody')[0];
const modalLabel = document.getElementById('user-modal-label');
const passwordField = document.getElementById('password');
const passwordHelpText = passwordField.nextElementSibling;
// Function to fetch and display users
function loadUsers() {
fetch('api/get_users.php')
.then(response => response.json())
.then(data => {
userTable.innerHTML = '';
if(data.success) {
data.users.forEach(user => {
const row = userTable.insertRow();
row.innerHTML = `
<td>${user.nama_lengkap}</td>
<td>${user.email}</td>
<td>${user.role.replace('_', ' ')}</td>
<td>${user.nama_kantor || 'N/A'}</td>
<td>${new Date(user.created_at).toLocaleDateString()}</td>
<td>
<button class="btn btn-sm btn-outline-secondary edit-btn" data-id="${user.id}"><i data-feather="edit-2" class="feather-sm"></i></button>
<button class="btn btn-sm btn-outline-danger delete-btn" data-id="${user.id}"><i data-feather="trash-2" class="feather-sm"></i></button>
</td>
`;
});
feather.replace();
}
})
.catch(error => console.error('Error loading users:', error));
}
// Show modal for adding a new user
document.getElementById('btn-add-user').addEventListener('click', function () {
userForm.reset();
document.getElementById('user-id').value = '';
modalLabel.textContent = 'Tambah Pengguna Baru';
passwordField.setAttribute('required', 'required');
passwordHelpText.style.display = 'none';
userModal.show();
});
// Handle form submission for both add and edit
userForm.addEventListener('submit', function (e) {
e.preventDefault();
const formData = new FormData(userForm);
const userId = formData.get('id');
const url = userId ? 'api/update_user.php' : 'api/add_user.php';
// If password is empty on update, remove it from form data
if (userId && !formData.get('password')) {
formData.delete('password');
}
fetch(url, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
userModal.hide();
loadUsers(); // Refresh the table
// You can add a toast notification here for better UX
} else {
alert('Error: ' + data.message);
}
})
.catch(error => console.error('Form submission error:', error));
});
// Handle edit and delete button clicks
userTable.addEventListener('click', function (e) {
const target = e.target.closest('button');
if (!target) return;
const id = target.dataset.id;
// Edit button
if (target.classList.contains('edit-btn')) {
fetch(`api/get_user.php?id=${id}`)
.then(response => response.json())
.then(data => {
if (data.success) {
const user = data.user;
document.getElementById('user-id').value = user.id;
document.getElementById('nama_lengkap').value = user.nama_lengkap;
document.getElementById('email').value = user.email;
document.getElementById('role').value = user.role;
document.getElementById('id_kantor').value = user.id_kantor || '';
modalLabel.textContent = 'Edit Pengguna';
passwordField.removeAttribute('required');
passwordHelpText.style.display = 'block';
userModal.show();
} else {
alert('Error: ' + data.message);
}
});
}
// Delete button
if (target.classList.contains('delete-btn')) {
if (confirm('Apakah Anda yakin ingin menghapus pengguna ini?')) {
fetch('api/delete_user.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `id=${id}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
loadUsers(); // Refresh the table
} else {
alert('Error: ' + data.message);
}
})
.catch(error => console.error('Delete error:', error));
}
}
});
// Initial load of users
loadUsers();
});

46
auth.php Normal file
View File

@ -0,0 +1,46 @@
<?php
session_start();
require_once 'db/config.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
if (empty($email) || empty($password)) {
$_SESSION['login_error'] = 'Email dan password harus diisi.';
header('Location: login.php');
exit();
}
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
// Regenerate session ID to prevent session fixation
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['nama_lengkap'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['user_office_id'] = $user['id_kantor'];
header('Location: index.php');
exit();
} else {
$_SESSION['login_error'] = 'Email atau password salah.';
header('Location: login.php');
exit();
}
} catch (PDOException $e) {
// In a real app, you would log this error
$_SESSION['login_error'] = 'Terjadi kesalahan pada database.';
header('Location: login.php');
exit();
}
} else {
header('Location: login.php');
exit();
}

View File

@ -50,6 +50,21 @@ try {
UNIQUE KEY `kode_kategori` (`kode_kategori`) UNIQUE KEY `kode_kategori` (`kode_kategori`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nama_lengkap` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`role` enum('super_admin','admin_cabang','pegawai') NOT NULL DEFAULT 'pegawai',
`id_kantor` int(11) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
KEY `id_kantor` (`id_kantor`),
CONSTRAINT `users_ibfk_1` FOREIGN KEY (`id_kantor`) REFERENCES `kantor` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `aset` ( CREATE TABLE IF NOT EXISTS `aset` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`kode_aset` varchar(255) NOT NULL, `kode_aset` varchar(255) NOT NULL,
@ -68,13 +83,15 @@ try {
UNIQUE KEY `kode_aset` (`kode_aset`), UNIQUE KEY `kode_aset` (`kode_aset`),
KEY `id_kategori` (`id_kategori`), KEY `id_kategori` (`id_kategori`),
KEY `id_kantor_lokasi` (`id_kantor_lokasi`), KEY `id_kantor_lokasi` (`id_kantor_lokasi`),
KEY `id_pengguna_penanggung_jawab` (`id_pengguna_penanggung_jawab`),
CONSTRAINT `aset_ibfk_1` FOREIGN KEY (`id_kategori`) REFERENCES `kategori_aset` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT `aset_ibfk_1` FOREIGN KEY (`id_kategori`) REFERENCES `kategori_aset` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `aset_ibfk_2` FOREIGN KEY (`id_kantor_lokasi`) REFERENCES `kantor` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE CONSTRAINT `aset_ibfk_2` FOREIGN KEY (`id_kantor_lokasi`) REFERENCES `kantor` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `aset_ibfk_3` FOREIGN KEY (`id_pengguna_penanggung_jawab`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
"; ";
$pdo->exec($sql); $pdo->exec($sql);
echo "Tables `kantor`, `kategori_aset`, and `aset` created or already exist.\n"; echo "Tables `kantor`, `kategori_aset`, `users`, and `aset` created or already exist.\n";
// --- Seed initial data if tables are empty --- // --- Seed initial data if tables are empty ---
$stmt = $pdo->query("SELECT COUNT(*) FROM `kantor`"); $stmt = $pdo->query("SELECT COUNT(*) FROM `kantor`");
@ -100,6 +117,16 @@ try {
echo "Seeded `kategori_aset` table with initial data.\n"; echo "Seeded `kategori_aset` table with initial data.\n";
} }
$stmt = $pdo->query("SELECT COUNT(*) FROM `users`");
if ($stmt->fetchColumn() == 0) {
$hashed_password = password_hash('password', PASSWORD_DEFAULT);
$pdo->exec("
INSERT INTO `users` (`nama_lengkap`, `email`, `password`, `role`, `id_kantor`) VALUES
('Super Admin', 'admin@example.com', '{$hashed_password}', 'super_admin', 1);
");
echo "Seeded `users` table with a super admin user.\n";
}
// Seed a sample asset // Seed a sample asset
$stmt = $pdo->query("SELECT COUNT(*) FROM `aset`"); $stmt = $pdo->query("SELECT COUNT(*) FROM `aset`");
if ($stmt->fetchColumn() == 0) { if ($stmt->fetchColumn() == 0) {

116
index.php
View File

@ -1,3 +1,11 @@
<?php
session_start();
// If the user is not logged in, redirect to the login page.
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit();
}
?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="id"> <html lang="id">
<head> <head>
@ -28,10 +36,35 @@
</head> </head>
<body> <body>
<header class="app-header"> <header class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container"> <div class="container-fluid">
<h1>Data Aset Perusahaan</h1> <a class="navbar-brand" href="index.php">Manajemen Aset</a>
<p class="lead">Selamat datang di sistem manajemen aset terpusat.</p> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<?php if (isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'super_admin'): ?>
<li class="nav-item">
<a class="nav-link active" href="index.php">Aset</a>
</li>
<li class="nav-item">
<a class="nav-link" href="users.php">Pengguna</a>
</li>
<li class="nav-item">
<a class="nav-link" href="offices.php">Kantor</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i data-feather="user" class="me-1"></i> <?php echo htmlspecialchars($_SESSION['user_name']); ?>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
</ul>
</li>
</ul>
</div>
</div> </div>
</header> </header>
@ -147,6 +180,81 @@
</div> </div>
</div> </div>
<!-- Edit Asset Modal -->
<div class="modal fade" id="editAssetModal" tabindex="-1" aria-labelledby="editAssetModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editAssetModalLabel">Edit Aset</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="editAssetForm" class="needs-validation" novalidate>
<input type="hidden" id="edit_id" name="id">
<div class="row">
<div class="col-md-12 mb-3">
<label for="edit_nama_aset" class="form-label">Nama Aset</label>
<input type="text" class="form-control" id="edit_nama_aset" name="nama_aset" required>
<div class="invalid-feedback">Nama aset tidak boleh kosong.</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="edit_id_kategori" class="form-label">Kategori</label>
<select class="form-select" id="edit_id_kategori" name="id_kategori" required>
<!-- Options will be populated by JS -->
</select>
<div class="invalid-feedback">Silakan pilih kategori.</div>
</div>
<div class="col-md-6 mb-3">
<label for="edit_id_kantor_lokasi" class="form-label">Lokasi Kantor</label>
<select class="form-select" id="edit_id_kantor_lokasi" name="id_kantor_lokasi" required>
<!-- Options will be populated by JS -->
</select>
<div class="invalid-feedback">Silakan pilih lokasi.</div>
</div>
</div>
<div class="mb-3">
<label for="edit_spesifikasi" class="form-label">Spesifikasi</label>
<textarea class="form-control" id="edit_spesifikasi" name="spesifikasi" rows="3"></textarea>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="edit_tanggal_pembelian" class="form-label">Tanggal Pembelian</label>
<input type="date" class="form-control" id="edit_tanggal_pembelian" name="tanggal_pembelian" required>
<div class="invalid-feedback">Tanggal pembelian tidak boleh kosong.</div>
</div>
<div class="col-md-6 mb-3">
<label for="edit_harga_pembelian" class="form-label">Harga Pembelian (Rp)</label>
<input type="number" class="form-control" id="edit_harga_pembelian" name="harga_pembelian" step="0.01" required>
<div class="invalid-feedback">Harga pembelian tidak boleh kosong.</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="edit_vendor" class="form-label">Vendor</label>
<input type="text" class="form-control" id="edit_vendor" name="vendor">
</div>
<div class="col-md-6 mb-3">
<label for="edit_status" class="form-label">Status</label>
<select class="form-select" id="edit_status" name="status" required>
<option value="Tersedia">Tersedia</option>
<option value="Digunakan">Digunakan</option>
<option value="Perbaikan">Perbaikan</option>
<option value="Dihapuskan">Dihapuskan</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Simpan Perubahan</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Toast container --> <!-- Toast container -->
<div class="toast-container position-fixed bottom-0 end-0 p-3"> <div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="notificationToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true"> <div id="notificationToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">

64
login.php Normal file
View File

@ -0,0 +1,64 @@
<?php
session_start();
if (isset($_SESSION['user_id'])) {
header('Location: index.php');
exit();
}
$error = '';
if (isset($_SESSION['login_error'])) {
$error = $_SESSION['login_error'];
unset($_SESSION['login_error']);
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Manajemen Aset</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css">
<style>
body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background-color: #F4F7F9;
}
.login-card {
max-width: 400px;
width: 100%;
}
</style>
</head>
<body>
<div class="card login-card shadow-sm">
<div class="card-body p-5">
<h3 class="card-title text-center mb-4">Login Sistem</h3>
<?php if ($error): ?>
<div class="alert alert-danger" role="alert">
<?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<form action="auth.php" method="POST">
<div class="mb-3">
<label for="email" class="form-label">Alamat Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

21
logout.php Normal file
View File

@ -0,0 +1,21 @@
<?php
session_start();
// Unset all of the session variables
$_SESSION = [];
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
// Finally, destroy the session.
session_destroy();
header('Location: login.php');
exit();

129
offices.php Normal file
View File

@ -0,0 +1,129 @@
<?php
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
if ($_SESSION['user_role'] !== 'super_admin') {
header('Location: index.php?error=unauthorized');
exit;
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manajemen Kantor - Sistem Manajemen Aset</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.css" rel="stylesheet">
<link href="assets/css/custom.css" rel="stylesheet">
</head>
<body>
<div class="d-flex">
<div class="main-content flex-grow-1">
<header class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">Manajemen Aset</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<?php if ($_SESSION['user_role'] === 'super_admin'): ?>
<li class="nav-item">
<a class="nav-link" href="index.php">Aset</a>
</li>
<li class="nav-item">
<a class="nav-link" href="users.php">Pengguna</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="offices.php">Kantor</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i data-feather="user" class="me-1"></i> <?php echo htmlspecialchars($_SESSION['user_name']); ?>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
</ul>
</li>
</ul>
</div>
</div>
</header>
<main class="container-fluid mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Manajemen Kantor</h1>
<button class="btn btn-primary" id="btn-add-office"><i data-feather="plus" class="me-1"></i> Tambah Kantor Baru</button>
</div>
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover" id="offices-table">
<thead class="table-light">
<tr>
<th>Nama Kantor</th>
<th>Alamat</th>
<th>Tipe</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
<!-- Office data will be loaded here by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- Add/Edit Office Modal -->
<div class="modal fade" id="office-modal" tabindex="-1" aria-labelledby="office-modal-label" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="office-modal-label">Tambah Kantor Baru</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="office-form">
<input type="hidden" id="office-id" name="id">
<div class="mb-3">
<label for="nama_kantor" class="form-label">Nama Kantor</label>
<input type="text" class="form-control" id="nama_kantor" name="nama_kantor" required>
</div>
<div class="mb-3">
<label for="alamat" class="form-label">Alamat</label>
<textarea class="form-control" id="alamat" name="alamat" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="tipe_kantor" class="form-label">Tipe Kantor</label>
<select class="form-select" id="tipe_kantor" name="tipe_kantor" required>
<option value="pusat">Pusat</option>
<option value="cabang" selected>Cabang</option>
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Simpan</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script>
feather.replace();
</script>
<script src="assets/js/offices.js"></script>
</body>
</html>

165
users.php Normal file
View File

@ -0,0 +1,165 @@
<?php
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
if ($_SESSION['user_role'] !== 'super_admin') {
// Optionally, redirect to a "not authorized" page or back to index
header('Location: index.php?error=unauthorized');
exit;
}
// Include database configuration
require_once 'db/config.php';
// Fetch options for form selects (offices)
try {
$stmt_offices = db()->query("SELECT id, nama_kantor FROM kantor ORDER BY nama_kantor");
$offices = $stmt_offices->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
// Handle error, maybe log it and show a generic message
$offices = [];
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manajemen Pengguna - Sistem Manajemen Aset</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.css" rel="stylesheet">
<link href="assets/css/custom.css" rel="stylesheet">
</head>
<body>
<div class="d-flex">
<!-- Sidebar can be added here if needed -->
<div class="main-content flex-grow-1">
<header class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="index.php">Manajemen Aset</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<?php if ($_SESSION['user_role'] === 'super_admin'): ?>
<li class="nav-item">
<a class="nav-link" href="index.php">Aset</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="users.php">Pengguna</a>
</li>
<li class="nav-item">
<a class="nav-link" href="offices.php">Kantor</a>
</li>
<?php endif; ?>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i data-feather="user" class="me-1"></i> <?php echo htmlspecialchars($_SESSION['user_name']); ?>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
</ul>
</li>
</ul>
</div>
</div>
</header>
<main class="container-fluid mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Manajemen Pengguna</h1>
<button class="btn btn-primary" id="btn-add-user"><i data-feather="plus" class="me-1"></i> Tambah Pengguna Baru</button>
</div>
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover" id="users-table">
<thead class="table-light">
<tr>
<th>Nama Lengkap</th>
<th>Email</th>
<th>Role</th>
<th>Kantor</th>
<th>Tgl Dibuat</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
<!-- User data will be loaded here by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- Add/Edit User Modal -->
<div class="modal fade" id="user-modal" tabindex="-1" aria-labelledby="user-modal-label" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="user-modal-label">Tambah Pengguna Baru</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="user-form">
<input type="hidden" id="user-id" name="id">
<div class="row">
<div class="col-md-6 mb-3">
<label for="nama_lengkap" class="form-label">Nama Lengkap</label>
<input type="text" class="form-control" id="nama_lengkap" name="nama_lengkap" required>
</div>
<div class="col-md-6 mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password">
<small class="form-text text-muted">Kosongkan jika tidak ingin mengubah password.</small>
</div>
<div class="col-md-6 mb-3">
<label for="role" class="form-label">Role</label>
<select class="form-select" id="role" name="role" required>
<option value="super_admin">Super Admin</option>
<option value="admin_cabang">Admin Cabang</option>
<option value="pegawai" selected>Pegawai</option>
</select>
</div>
</div>
<div class="mb-3">
<label for="id_kantor" class="form-label">Kantor</label>
<select class="form-select" id="id_kantor" name="id_kantor">
<option value="">Tidak Ditugaskan</option>
<?php foreach ($offices as $office): ?>
<option value="<?php echo $office['id']; ?>"><?php echo htmlspecialchars($office['nama_kantor']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary">Simpan</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script>
feather.replace();
</script>
<script src="assets/js/users.js"></script>
</body>
</html>