Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2be5f009ee | ||
|
|
8d0038be8c |
15
.gitignore
vendored
15
.gitignore
vendored
@ -1,3 +1,12 @@
|
|||||||
node_modules/
|
# Composer dependencies
|
||||||
*/node_modules/
|
/vendor/
|
||||||
*/build/
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# IDE settings
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
71
api/add_asset.php
Normal file
71
api/add_asset.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
// api/add_asset.php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// --- Validation ---
|
||||||
|
$errors = [];
|
||||||
|
if (empty($_POST['nama_aset'])) {
|
||||||
|
$errors[] = 'Nama aset tidak boleh kosong.';
|
||||||
|
}
|
||||||
|
if (empty($_POST['id_kategori'])) {
|
||||||
|
$errors[] = 'Kategori harus dipilih.';
|
||||||
|
}
|
||||||
|
if (empty($_POST['id_kantor_lokasi'])) {
|
||||||
|
$errors[] = 'Lokasi kantor harus dipilih.';
|
||||||
|
}
|
||||||
|
if (empty($_POST['tanggal_pembelian'])) {
|
||||||
|
$errors[] = 'Tanggal pembelian tidak boleh kosong.';
|
||||||
|
}
|
||||||
|
if (empty($_POST['harga_pembelian'])) {
|
||||||
|
$errors[] = 'Harga pembelian tidak boleh kosong.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($errors)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'message' => implode(' ', $errors)]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = getDbConnection();
|
||||||
|
|
||||||
|
// --- Generate Asset Code ---
|
||||||
|
$stmt = $pdo->prepare("SELECT kode_kategori FROM kategori_aset WHERE id = ?");
|
||||||
|
$stmt->execute([$_POST['id_kategori']]);
|
||||||
|
$kode_kategori = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
$tahun = date('Y');
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM aset WHERE kode_aset LIKE ?");
|
||||||
|
$stmt->execute([$kode_kategori . '-' . $tahun . '%']);
|
||||||
|
$nomor_urut = $stmt->fetchColumn() + 1;
|
||||||
|
|
||||||
|
$kode_aset = $kode_kategori . '-' . $tahun . '-' . str_pad($nomor_urut, 4, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
// --- Insert into Database ---
|
||||||
|
$sql = "INSERT INTO aset (kode_aset, nama_aset, id_kategori, id_kantor_lokasi, spesifikasi, tanggal_pembelian, harga_pembelian, vendor, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$kode_aset,
|
||||||
|
$_POST['nama_aset'],
|
||||||
|
$_POST['id_kategori'],
|
||||||
|
$_POST['id_kantor_lokasi'],
|
||||||
|
$_POST['spesifikasi'] ?? null,
|
||||||
|
$_POST['tanggal_pembelian'],
|
||||||
|
$_POST['harga_pembelian'],
|
||||||
|
$_POST['vendor'] ?? null,
|
||||||
|
$_POST['status'] ?? 'Tersedia'
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Aset berhasil ditambahkan.', 'new_asset_id' => $pdo->lastInsertId()]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
33
api/add_office.php
Normal file
33
api/add_office.php
Normal 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
48
api/add_user.php
Normal 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
42
api/delete_asset.php
Normal 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
46
api/delete_office.php
Normal 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
42
api/delete_user.php
Normal 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
35
api/get_asset.php
Normal 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()]);
|
||||||
|
}
|
||||||
34
api/get_assets.php
Normal file
34
api/get_assets.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
// api/get_assets.php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = getDbConnection();
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
a.id,
|
||||||
|
a.kode_aset,
|
||||||
|
a.nama_aset,
|
||||||
|
kc.nama_kategori,
|
||||||
|
k.nama_kantor,
|
||||||
|
a.status
|
||||||
|
FROM aset a
|
||||||
|
JOIN kategori_aset kc ON a.id_kategori = kc.id
|
||||||
|
JOIN kantor k ON a.id_kantor_lokasi = k.id
|
||||||
|
ORDER BY a.created_at DESC
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $pdo->query($sql);
|
||||||
|
$assets = $stmt->fetchAll();
|
||||||
|
|
||||||
|
echo json_encode($assets);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Gagal mengambil data aset: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
32
api/get_offices.php
Normal file
32
api/get_offices.php
Normal 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()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
26
api/get_options.php
Normal file
26
api/get_options.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
// api/get_options.php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../db/config.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = getDbConnection();
|
||||||
|
|
||||||
|
$kategori_stmt = $pdo->query("SELECT id, nama_kategori FROM kategori_aset ORDER BY nama_kategori ASC");
|
||||||
|
$kategori = $kategori_stmt->fetchAll();
|
||||||
|
|
||||||
|
$kantor_stmt = $pdo->query("SELECT id, nama_kantor FROM kantor ORDER BY nama_kantor ASC");
|
||||||
|
$kantor = $kantor_stmt->fetchAll();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'kategori' => $kategori,
|
||||||
|
'kantor' => $kantor
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Gagal mengambil data options: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
34
api/get_user.php
Normal file
34
api/get_user.php
Normal 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
26
api/get_users.php
Normal 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
73
api/update_asset.php
Normal 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
34
api/update_office.php
Normal 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
56
api/update_user.php
Normal 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()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
95
assets/css/custom.css
Normal file
95
assets/css/custom.css
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/* assets/css/custom.css */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary-color: #4A90E2;
|
||||||
|
--secondary-color: #50E3C2;
|
||||||
|
--background-color: #F4F7F9;
|
||||||
|
--surface-color: #FFFFFF;
|
||||||
|
--text-color: #333333;
|
||||||
|
--heading-font: 'Georgia', serif;
|
||||||
|
--body-font: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-family: var(--body-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: var(--heading-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 3rem 0;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header h1 {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.surface-card {
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(74, 144, 226, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control, .form-select {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus, .form-select:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(74, 144, 226, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
--bs-table-hover-bg: var(--background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.35em 0.65em;
|
||||||
|
font-size: .75em;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: baseline;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-Tersedia { background-color: #28a745; }
|
||||||
|
.status-Digunakan { background-color: #17a2b8; }
|
||||||
|
.status-Perbaikan { background-color: #ffc107; color: #333;}
|
||||||
|
.status-Dihapuskan { background-color: #dc3545; }
|
||||||
264
assets/js/main.js
Normal file
264
assets/js/main.js
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
|
||||||
|
// --- Constants ---
|
||||||
|
const API_BASE = 'api/';
|
||||||
|
const addAssetForm = document.getElementById('addAssetForm');
|
||||||
|
const editAssetForm = document.getElementById('editAssetForm');
|
||||||
|
const assetsTableBody = document.getElementById('assets-table-body');
|
||||||
|
const loadingIndicator = document.getElementById('loading-indicator');
|
||||||
|
const notificationToastEl = document.getElementById('notificationToast');
|
||||||
|
const notificationToast = new bootstrap.Toast(notificationToastEl);
|
||||||
|
const editAssetModalEl = document.getElementById('editAssetModal');
|
||||||
|
const editAssetModal = new bootstrap.Modal(editAssetModalEl);
|
||||||
|
|
||||||
|
// --- Functions ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a toast notification.
|
||||||
|
* @param {string} title - The title of the toast.
|
||||||
|
* @param {string} body - The body message of the toast.
|
||||||
|
* @param {string} type - 'success' or 'error'.
|
||||||
|
*/
|
||||||
|
function showToast(title, body, type = 'success') {
|
||||||
|
const toastHeader = notificationToastEl.querySelector('.toast-header');
|
||||||
|
|
||||||
|
toastHeader.classList.remove('bg-success', 'text-white', 'bg-danger', 'text-white');
|
||||||
|
|
||||||
|
if (type === 'success') {
|
||||||
|
toastHeader.classList.add('bg-success', 'text-white');
|
||||||
|
} else if (type === 'error') {
|
||||||
|
toastHeader.classList.add('bg-danger', 'text-white');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('toast-title').textContent = title;
|
||||||
|
document.getElementById('toast-body').textContent = body;
|
||||||
|
notificationToast.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches data from the API.
|
||||||
|
* @param {string} endpoint - The API endpoint to fetch from.
|
||||||
|
* @param {object} options - Optional fetch options.
|
||||||
|
* @returns {Promise<any>} - The JSON response.
|
||||||
|
*/
|
||||||
|
async function fetchApi(endpoint, options = {}) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(API_BASE + endpoint, options);
|
||||||
|
if (!response.ok) {
|
||||||
|
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();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching ${endpoint}:`, error);
|
||||||
|
showToast('Error', `Terjadi kesalahan: ${error.message}`, 'error');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates select dropdowns for both add and edit modals.
|
||||||
|
*/
|
||||||
|
async function populateSelectOptions() {
|
||||||
|
const data = await fetchApi('get_options.php');
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const selects = [
|
||||||
|
{ 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' }
|
||||||
|
];
|
||||||
|
|
||||||
|
selects.forEach(s => {
|
||||||
|
if (s.el) {
|
||||||
|
s.el.innerHTML = `<option value="">${s.placeholder}</option>`;
|
||||||
|
s.data.forEach(item => {
|
||||||
|
s.el.innerHTML += `<option value="${item.id}">${item[s.name]}</option>`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches assets and renders them in the table.
|
||||||
|
*/
|
||||||
|
async function loadAssets() {
|
||||||
|
loadingIndicator.style.display = 'block';
|
||||||
|
assetsTableBody.innerHTML = '';
|
||||||
|
|
||||||
|
const assets = await fetchApi('get_assets.php');
|
||||||
|
|
||||||
|
loadingIndicator.style.display = 'none';
|
||||||
|
|
||||||
|
if (assets && assets.length > 0) {
|
||||||
|
assets.forEach(asset => {
|
||||||
|
const row = `
|
||||||
|
<tr>
|
||||||
|
<td>${asset.kode_aset}</td>
|
||||||
|
<td>${asset.nama_aset}</td>
|
||||||
|
<td>${asset.nama_kategori}</td>
|
||||||
|
<td>${asset.nama_kantor}</td>
|
||||||
|
<td><span class="status-badge status-${asset.status.toLowerCase()}">${asset.status}</span></td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-primary btn-edit" data-asset-id="${asset.id}" title="Edit">
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
assetsTableBody.innerHTML += row;
|
||||||
|
});
|
||||||
|
} else if (assets) {
|
||||||
|
assetsTableBody.innerHTML = '<tr><td colspan="6" class="text-center">Belum ada data aset.</td></tr>';
|
||||||
|
} else {
|
||||||
|
assetsTableBody.innerHTML = '<tr><td colspan="6" class="text-center text-danger">Gagal memuat data aset.</td></tr>';
|
||||||
|
}
|
||||||
|
feather.replace();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the submission of the 'Add Asset' form.
|
||||||
|
*/
|
||||||
|
async function handleAddAssetSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (!addAssetForm.checkValidity()) {
|
||||||
|
addAssetForm.classList.add('was-validated');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData(addAssetForm);
|
||||||
|
const submitButton = addAssetForm.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('add_asset.php', { method: 'POST', body: formData });
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
submitButton.disabled = false;
|
||||||
|
submitButton.innerHTML = 'Simpan Aset';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the edit modal and populates it with asset data.
|
||||||
|
* @param {number} assetId - The ID of the asset to edit.
|
||||||
|
*/
|
||||||
|
async function openEditModal(assetId) {
|
||||||
|
const asset = await fetchApi(`get_asset.php?id=${assetId}`);
|
||||||
|
if (!asset) return;
|
||||||
|
|
||||||
|
document.getElementById('edit_id').value = asset.id;
|
||||||
|
document.getElementById('edit_nama_aset').value = asset.nama_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 ---
|
||||||
|
|
||||||
|
feather.replace();
|
||||||
|
populateSelectOptions();
|
||||||
|
loadAssets();
|
||||||
|
|
||||||
|
// --- Event Listeners ---
|
||||||
|
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
|
||||||
|
document.querySelectorAll('i[data-feather]').forEach(el => {
|
||||||
|
if(el.classList.contains('feather-sm')) return;
|
||||||
|
const iconName = el.getAttribute('data-feather');
|
||||||
|
el.classList.add(`icon-${iconName}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
114
assets/js/offices.js
Normal file
114
assets/js/offices.js
Normal 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
128
assets/js/users.js
Normal 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
46
auth.php
Normal 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();
|
||||||
|
}
|
||||||
@ -1,17 +1,38 @@
|
|||||||
<?php
|
<?php
|
||||||
// Generated by setup_mariadb_project.sh — edit as needed.
|
// db/config.php
|
||||||
define('DB_HOST', '127.0.0.1');
|
|
||||||
define('DB_NAME', 'app_30908');
|
|
||||||
define('DB_USER', 'app_30908');
|
|
||||||
define('DB_PASS', '98b730aa-be6c-479d-a47d-e5e7abc49229');
|
|
||||||
|
|
||||||
function db() {
|
// --- Database Credentials ---
|
||||||
static $pdo;
|
// In a real environment, use environment variables.
|
||||||
if (!$pdo) {
|
define('DB_HOST', '127.0.0.1');
|
||||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
define('DB_NAME', 'asset_management');
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
define('DB_USER', 'asset_admin');
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
define('DB_PASS', 'password');
|
||||||
]);
|
define('DB_CHARSET', 'utf8mb4');
|
||||||
}
|
|
||||||
return $pdo;
|
/**
|
||||||
|
* Establishes a PDO database connection.
|
||||||
|
*
|
||||||
|
* @return PDO The PDO database connection object.
|
||||||
|
* @throws PDOException if the connection fails.
|
||||||
|
*/
|
||||||
|
function getDbConnection() {
|
||||||
|
static $pdo = null;
|
||||||
|
|
||||||
|
if ($pdo === null) {
|
||||||
|
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
|
||||||
|
$options = [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
];
|
||||||
|
try {
|
||||||
|
$pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
// For development, it's okay to see the error.
|
||||||
|
// In production, log this and show a generic error message.
|
||||||
|
throw new \PDOException($e->getMessage(), (int)$e->getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $pdo;
|
||||||
}
|
}
|
||||||
|
?>
|
||||||
146
db/setup.php
Normal file
146
db/setup.php
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
// db/setup.php
|
||||||
|
// This script initializes the database and tables.
|
||||||
|
// Run it once from the command line: php db/setup.php
|
||||||
|
|
||||||
|
// --- Database Credentials (temporary for setup) ---
|
||||||
|
define('DB_HOST', '127.0.0.1');
|
||||||
|
define('DB_NAME', 'asset_management');
|
||||||
|
define('DB_USER', 'asset_admin');
|
||||||
|
define('DB_PASS', 'password');
|
||||||
|
define('DB_CHARSET', 'utf8mb4');
|
||||||
|
|
||||||
|
header('Content-Type: text/plain');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Connect without specifying a database to create it
|
||||||
|
$dsn_init = "mysql:host=" . DB_HOST . ";charset=" . DB_CHARSET;
|
||||||
|
$pdo_init = new PDO($dsn_init, DB_USER, DB_PASS);
|
||||||
|
$pdo_init->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
||||||
|
// Create the database if it doesn't exist
|
||||||
|
$pdo_init->exec("CREATE DATABASE IF NOT EXISTS `" . DB_NAME . "` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;");
|
||||||
|
echo "Database '" . DB_NAME . "' created or already exists.\n";
|
||||||
|
|
||||||
|
// Now connect to the newly created database
|
||||||
|
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
|
||||||
|
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
||||||
|
]);
|
||||||
|
|
||||||
|
// SQL to create tables
|
||||||
|
$sql = "
|
||||||
|
CREATE TABLE IF NOT EXISTS `kantor` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_kantor` varchar(255) NOT NULL,
|
||||||
|
`alamat` text DEFAULT NULL,
|
||||||
|
`tipe_kantor` enum('pusat','cabang') NOT NULL DEFAULT 'cabang',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `kategori_aset` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_kategori` varchar(255) NOT NULL,
|
||||||
|
`kode_kategori` varchar(10) NOT 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 `kode_kategori` (`kode_kategori`)
|
||||||
|
) 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` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`kode_aset` varchar(255) NOT NULL,
|
||||||
|
`nama_aset` varchar(255) NOT NULL,
|
||||||
|
`id_kategori` int(11) NOT NULL,
|
||||||
|
`id_kantor_lokasi` int(11) NOT NULL,
|
||||||
|
`spesifikasi` text DEFAULT NULL,
|
||||||
|
`tanggal_pembelian` date NOT NULL,
|
||||||
|
`harga_pembelian` decimal(15,2) NOT NULL,
|
||||||
|
`vendor` varchar(255) DEFAULT NULL,
|
||||||
|
`status` enum('Tersedia','Digunakan','Perbaikan','Dihapuskan') NOT NULL DEFAULT 'Tersedia',
|
||||||
|
`id_pengguna_penanggung_jawab` 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 `kode_aset` (`kode_aset`),
|
||||||
|
KEY `id_kategori` (`id_kategori`),
|
||||||
|
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_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;
|
||||||
|
";
|
||||||
|
|
||||||
|
$pdo->exec($sql);
|
||||||
|
echo "Tables `kantor`, `kategori_aset`, `users`, and `aset` created or already exist.\n";
|
||||||
|
|
||||||
|
// --- Seed initial data if tables are empty ---
|
||||||
|
$stmt = $pdo->query("SELECT COUNT(*) FROM `kantor`");
|
||||||
|
if ($stmt->fetchColumn() == 0) {
|
||||||
|
$pdo->exec("
|
||||||
|
INSERT INTO `kantor` (`nama_kantor`, `alamat`, `tipe_kantor`) VALUES
|
||||||
|
('Kantor Pusat Jakarta', 'Jl. Jenderal Sudirman Kav. 52-53, Jakarta Selatan', 'pusat'),
|
||||||
|
('Cabang Surabaya', 'Jl. Basuki Rahmat No. 129, Surabaya', 'cabang'),
|
||||||
|
('Cabang Bandung', 'Jl. Asia Afrika No. 1, Bandung', 'cabang');
|
||||||
|
");
|
||||||
|
echo "Seeded `kantor` table with initial data.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->query("SELECT COUNT(*) FROM `kategori_aset`");
|
||||||
|
if ($stmt->fetchColumn() == 0) {
|
||||||
|
$pdo->exec("
|
||||||
|
INSERT INTO `kategori_aset` (`nama_kategori`, `kode_kategori`) VALUES
|
||||||
|
('Elektronik', 'ELK'),
|
||||||
|
('Furnitur', 'FNT'),
|
||||||
|
('Kendaraan', 'KDR'),
|
||||||
|
('Peralatan Kantor', 'PKR');
|
||||||
|
");
|
||||||
|
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
|
||||||
|
$stmt = $pdo->query("SELECT COUNT(*) FROM `aset`");
|
||||||
|
if ($stmt->fetchColumn() == 0) {
|
||||||
|
$pdo->exec("
|
||||||
|
INSERT INTO `aset` (`kode_aset`, `nama_aset`, `id_kategori`, `id_kantor_lokasi`, `spesifikasi`, `tanggal_pembelian`, `harga_pembelian`, `vendor`, `status`) VALUES
|
||||||
|
('ELK-2025-0001', 'Laptop Dell XPS 15', 1, 1, 'CPU i9, 32GB RAM, 1TB SSD', '2025-01-15', 45000000.00, 'Dell Indonesia', 'Digunakan');
|
||||||
|
");
|
||||||
|
echo "Seeded `aset` table with a sample asset.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
echo "\nDatabase setup completed successfully!\n";
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Database setup failed: " . $e->getMessage() . "\n");
|
||||||
|
}
|
||||||
|
?>
|
||||||
412
index.php
412
index.php
@ -1,150 +1,276 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
session_start();
|
||||||
@ini_set('display_errors', '1');
|
// If the user is not logged in, redirect to the login page.
|
||||||
@error_reporting(E_ALL);
|
if (!isset($_SESSION['user_id'])) {
|
||||||
@date_default_timezone_set('UTC');
|
header('Location: login.php');
|
||||||
|
exit();
|
||||||
$phpVersion = PHP_VERSION;
|
}
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
?>
|
?>
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="id">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>New Style</title>
|
|
||||||
<?php
|
<title>Data Aset Andi Offset</title>
|
||||||
// Read project preview data from environment
|
<meta name="description" content="Aplikasi Web Pendataan Aset Perusahaan: Kelola dan lacak aset perusahaan secara terpusat di semua kantor.">
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
<meta name="keywords" content="manajemen aset, pendataan aset, inventaris perusahaan, software aset, aplikasi aset, tracking aset, kelola aset kantor, sistem informasi aset, database aset, andi offset">
|
||||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|
||||||
?>
|
<!-- Open Graph / Facebook -->
|
||||||
<?php if ($projectDescription): ?>
|
<meta property="og:type" content="website">
|
||||||
<!-- Meta description -->
|
<meta property="og:title" content="Data Aset Andi Offset">
|
||||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
<meta property="og:description" content="Aplikasi Web Pendataan Aset Perusahaan: Kelola dan lacak aset perusahaan secara terpusat di semua kantor.">
|
||||||
<!-- Open Graph meta tags -->
|
<meta property="og:image" content="https://project-screens.s3.amazonaws.com/screenshots/34627/app-hero-20251003-135016.png">
|
||||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<!-- Twitter meta tags -->
|
<!-- Twitter -->
|
||||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
<?php endif; ?>
|
<meta name="twitter:image" content="https://project-screens.s3.amazonaws.com/screenshots/34627/app-hero-20251003-135016.png">
|
||||||
<?php if ($projectImageUrl): ?>
|
|
||||||
<!-- Open Graph image -->
|
<!-- Styles -->
|
||||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<!-- Twitter image -->
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<?php endif; ?>
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Georgia:wght@700&display=swap" rel="stylesheet">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-color-start: #6a11cb;
|
|
||||||
--bg-color-end: #2575fc;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
|
||||||
color: var(--text-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
|
||||||
animation: bg-pan 20s linear infinite;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
@keyframes bg-pan {
|
|
||||||
0% { background-position: 0% 0%; }
|
|
||||||
100% { background-position: 100% 100%; }
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg-color);
|
|
||||||
border: 1px solid var(--card-border-color);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.loader {
|
|
||||||
margin: 1.25rem auto 1.25rem;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px; height: 1px;
|
|
||||||
padding: 0; margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap; border: 0;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
background: rgba(0,0,0,0.2);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
|
||||||
<div class="card">
|
<header class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
<div class="container-fluid">
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
<a class="navbar-brand" href="index.php">Manajemen Aset</a>
|
||||||
<span class="sr-only">Loading…</span>
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
</div>
|
<span class="navbar-toggler-icon"></span>
|
||||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
</button>
|
||||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
<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>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container my-5">
|
||||||
|
<div class="card surface-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="card-title mb-0">Daftar Aset</h2>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addAssetModal">
|
||||||
|
<i data-feather="plus" class="me-2"></i>Tambah Aset Baru
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TODO: Filter/Search UI -->
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Kode Aset</th>
|
||||||
|
<th>Nama Aset</th>
|
||||||
|
<th>Kategori</th>
|
||||||
|
<th>Lokasi</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="assets-table-body">
|
||||||
|
<!-- Asset rows will be injected here by JavaScript -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="loading-indicator" class="text-center my-4">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Add Asset Modal -->
|
||||||
|
<div class="modal fade" id="addAssetModal" tabindex="-1" aria-labelledby="addAssetModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="addAssetModalLabel">Tambah Aset Baru</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="addAssetForm" class="needs-validation" novalidate>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 mb-3">
|
||||||
|
<label for="nama_aset" class="form-label">Nama Aset</label>
|
||||||
|
<input type="text" class="form-control" id="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="id_kategori" class="form-label">Kategori</label>
|
||||||
|
<select class="form-select" id="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="id_kantor_lokasi" class="form-label">Lokasi Kantor</label>
|
||||||
|
<select class="form-select" id="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="spesifikasi" class="form-label">Spesifikasi</label>
|
||||||
|
<textarea class="form-control" id="spesifikasi" name="spesifikasi" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="tanggal_pembelian" class="form-label">Tanggal Pembelian</label>
|
||||||
|
<input type="date" class="form-control" id="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="harga_pembelian" class="form-label">Harga Pembelian (Rp)</label>
|
||||||
|
<input type="number" class="form-control" id="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="vendor" class="form-label">Vendor</label>
|
||||||
|
<input type="text" class="form-control" id="vendor" name="vendor">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="status" class="form-label">Status</label>
|
||||||
|
<select class="form-select" id="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 Aset</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
|
||||||
<footer>
|
<!-- Edit Asset Modal -->
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
<div class="modal fade" id="editAssetModal" tabindex="-1" aria-labelledby="editAssetModalLabel" aria-hidden="true">
|
||||||
</footer>
|
<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 -->
|
||||||
|
<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 class="toast-header">
|
||||||
|
<strong class="me-auto" id="toast-title"></strong>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body" id="toast-body">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<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 src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
64
login.php
Normal file
64
login.php
Normal 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
21
logout.php
Normal 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
129
offices.php
Normal 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
165
users.php
Normal 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>
|
||||||
Loading…
x
Reference in New Issue
Block a user