This commit is contained in:
Flatlogic Bot 2025-10-03 13:56:08 +00:00
parent 808a8a006e
commit 8d0038be8c
9 changed files with 729 additions and 162 deletions

15
.gitignore vendored
View File

@ -1,3 +1,12 @@
node_modules/
*/node_modules/
*/build/
# Composer dependencies
/vendor/
# Environment files
.env
# Log files
*.log
# IDE settings
.idea/
.vscode/

71
api/add_asset.php Normal file
View 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()]);
}
?>

34
api/get_assets.php Normal file
View 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()]);
}
?>

26
api/get_options.php Normal file
View 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()]);
}
?>

95
assets/css/custom.css Normal file
View 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; }

174
assets/js/main.js Normal file
View File

@ -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<any>} - 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 = '<option value="">Pilih Kategori...</option>';
data.kategori.forEach(item => {
kategoriSelect.innerHTML += `<option value="${item.id}">${item.nama_kategori}</option>`;
});
kantorSelect.innerHTML = '<option value="">Pilih Lokasi...</option>';
data.kantor.forEach(item => {
kantorSelect.innerHTML += `<option value="${item.id}">${item.nama_kantor}</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}">${asset.status}</span></td>
<td>
<button class="btn btn-sm btn-outline-primary" onclick="alert('Fitur Edit belum tersedia.')"><i data-feather="edit-2" class="feather-sm"></i></button>
<button class="btn btn-sm btn-outline-danger" onclick="alert('Fitur Hapus belum tersedia.')"><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(); // 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 = '''<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 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}`);
});
});

View File

@ -1,17 +1,38 @@
<?php
// Generated by setup_mariadb_project.sh — edit as needed.
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');
// db/config.php
function db() {
static $pdo;
if (!$pdo) {
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => 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;
}
?>

119
db/setup.php Normal file
View File

@ -0,0 +1,119 @@
<?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 `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");
}
?>

308
index.php
View File

@ -1,150 +1,168 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?>
<!doctype html>
<html lang="en">
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<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>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data Aset Andi Offset</title>
<meta name="description" content="Aplikasi Web Pendataan Aset Perusahaan: Kelola dan lacak aset perusahaan secara terpusat di semua kantor.">
<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">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:title" content="Data Aset Andi Offset">
<meta property="og:description" content="Aplikasi Web Pendataan Aset Perusahaan: Kelola dan lacak aset perusahaan secara terpusat di semua kantor.">
<meta property="og:image" content="https://project-screens.s3.amazonaws.com/screenshots/34627/app-hero-20251003-135016.png">
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://project-screens.s3.amazonaws.com/screenshots/34627/app-hero-20251003-135016.png">
<!-- Styles -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Georgia:wght@700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
<header class="app-header">
<div class="container">
<h1>Data Aset Perusahaan</h1>
<p class="lead">Selamat datang di sistem manajemen aset terpusat.</p>
</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>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
<!-- 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>
</html>
</html>