diff --git a/.gitignore b/.gitignore index e427ff3..3128c3d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,12 @@ -node_modules/ -*/node_modules/ -*/build/ +# Composer dependencies +/vendor/ + +# Environment files +.env + +# Log files +*.log + +# IDE settings +.idea/ +.vscode/ \ No newline at end of file diff --git a/api/add_asset.php b/api/add_asset.php new file mode 100644 index 0000000..83f9718 --- /dev/null +++ b/api/add_asset.php @@ -0,0 +1,71 @@ + 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()]); +} + +?> \ No newline at end of file diff --git a/api/get_assets.php b/api/get_assets.php new file mode 100644 index 0000000..1e45ea2 --- /dev/null +++ b/api/get_assets.php @@ -0,0 +1,34 @@ +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()]); +} +?> \ No newline at end of file diff --git a/api/get_options.php b/api/get_options.php new file mode 100644 index 0000000..ab87c49 --- /dev/null +++ b/api/get_options.php @@ -0,0 +1,26 @@ +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()]); +} +?> \ No newline at end of file diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..8a09fb5 --- /dev/null +++ b/assets/css/custom.css @@ -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; } diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..4567512 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,174 @@ + +document.addEventListener('DOMContentLoaded', function () { + + // --- Constants --- + const API_BASE = 'api/'; + const addAssetForm = document.getElementById('addAssetForm'); + const assetsTableBody = document.getElementById('assets-table-body'); + const loadingIndicator = document.getElementById('loading-indicator'); + const notificationToastEl = document.getElementById('notificationToast'); + const notificationToast = new bootstrap.Toast(notificationToastEl); + + // --- 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'); + + // Reset classes + toastHeader.classList.remove('bg-success', 'text-white', 'bg-danger'); + + 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. + * @returns {Promise} - The JSON response. + */ + async function fetchApi(endpoint) { + try { + const response = await fetch(API_BASE + endpoint); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error(`Error fetching ${endpoint}:`, error); + showToast('Error', `Gagal memuat data dari server: ${error.message}`, 'error'); + return null; + } + } + + /** + * Populates select dropdowns. + */ + async function populateSelectOptions() { + const data = await fetchApi('get_options.php'); + if (!data) return; + + const kategoriSelect = document.getElementById('id_kategori'); + const kantorSelect = document.getElementById('id_kantor_lokasi'); + + kategoriSelect.innerHTML = ''; + data.kategori.forEach(item => { + kategoriSelect.innerHTML += ``; + }); + + kantorSelect.innerHTML = ''; + data.kantor.forEach(item => { + kantorSelect.innerHTML += ``; + }); + } + + /** + * 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 = ` + + ${asset.kode_aset} + ${asset.nama_aset} + ${asset.nama_kategori} + ${asset.nama_kantor} + ${asset.status} + + + + + + `; + assetsTableBody.innerHTML += row; + }); + } else if (assets) { + assetsTableBody.innerHTML = 'Belum ada data aset.'; + } else { + assetsTableBody.innerHTML = 'Gagal memuat data aset.'; + } + feather.replace(); // Re-initialize Feather icons + } + + /** + * Handles the submission of the 'Add Asset' form. + * @param {Event} event - The form submission event. + */ + 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 = ''' Menyimpan...'''; + + try { + const response = await fetch(API_BASE + 'add_asset.php', { + method: 'POST', + body: formData + }); + + const result = await response.json(); + + if (result.success) { + showToast('Sukses', 'Aset baru berhasil ditambahkan.'); + addAssetForm.reset(); + addAssetForm.classList.remove('was-validated'); + bootstrap.Modal.getInstance(document.getElementById('addAssetModal')).hide(); + loadAssets(); // Refresh the table + } else { + showToast('Error', result.message || 'Terjadi kesalahan yang tidak diketahui.', 'error'); + } + } catch (error) { + console.error('Error submitting form:', error); + showToast('Error', `Gagal mengirim data: ${error.message}`, 'error'); + } finally { + submitButton.disabled = false; + submitButton.innerHTML = 'Simpan Aset'; + } + } + + + // --- Initializations --- + + feather.replace(); // Initial call for icons + populateSelectOptions(); + loadAssets(); + + // --- Event Listeners --- + addAssetForm.addEventListener('submit', handleAddAssetSubmit); + + // 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}`); + }); + +}); diff --git a/db/config.php b/db/config.php index 89075af..3bc440f 100644 --- a/db/config.php +++ b/db/config.php @@ -1,17 +1,38 @@ PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ]); - } - return $pdo; +// --- Database Credentials --- +// In a real environment, use environment variables. +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'); + +/** + * 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; } +?> \ No newline at end of file diff --git a/db/setup.php b/db/setup.php new file mode 100644 index 0000000..e03f484 --- /dev/null +++ b/db/setup.php @@ -0,0 +1,119 @@ +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 `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`), + 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 + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + "; + + $pdo->exec($sql); + echo "Tables `kantor`, `kategori_aset`, 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"; + } + + // 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"); +} +?> \ No newline at end of file diff --git a/index.php b/index.php index 7205f3d..874001b 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,168 @@ - - - + + - - - New Style - - - - - - - - - - - - - - - - - - - + + + + Data Aset Andi Offset + + + + + + + + + + + + + + + + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

+ +
+
+

Data Aset Perusahaan

+

Selamat datang di sistem manajemen aset terpusat.

+
+
+ +
+
+
+
+

Daftar Aset

+ +
+ + + +
+ + + + + + + + + + + + + + +
Kode AsetNama AsetKategoriLokasiStatusAksi
+
+
+
+ Loading... +
+
+
+
+
+ + + -
- + + +
+ +
+ + + + + + - + \ No newline at end of file