diff --git a/assets/css/custom.css b/assets/css/custom.css index 789132e..94129a3 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,403 +1,611 @@ -body { - background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); - background-size: 400% 400%; - animation: gradient 15s ease infinite; - color: #212529; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - margin: 0; - min-height: 100vh; +:root { + --app-bg: #f4f5f7; + --app-surface: #ffffff; + --app-surface-muted: #f8f9fb; + --app-border: #d8dde6; + --app-border-strong: #c6ced8; + --app-text: #111827; + --app-muted: #5f6b7a; + --app-primary: #204f96; + --app-primary-dark: #183d73; + --app-success-bg: #eef6f0; + --app-success-text: #1f5132; + --app-warning-bg: #fff7e6; + --app-warning-text: #7a5316; + --app-danger-bg: #fff1f0; + --app-danger-text: #8e2f2b; + --radius-sm: 0.5rem; + --radius-md: 0.75rem; + --radius-lg: 1rem; + --shadow-sm: 0 1px 2px rgba(17, 24, 39, 0.05); + --shadow-md: 0 10px 24px rgba(17, 24, 39, 0.05); + --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } -.main-wrapper { - display: flex; +html { + scroll-behavior: smooth; +} + +body.app-body { + margin: 0; + background: var(--app-bg); + color: var(--app-text); + font-family: var(--font-sans); + line-height: 1.5; +} + +a { + color: var(--app-primary); + text-decoration: none; +} + +a:hover { + color: var(--app-primary-dark); +} + +.app-container { + width: min(1180px, calc(100% - 2rem)); +} + +.app-navbar { + background: rgba(255, 255, 255, 0.92); + backdrop-filter: blur(12px); + border-bottom: 1px solid rgba(17, 24, 39, 0.08); +} + +.navbar-brand { + display: inline-flex; + align-items: center; + gap: 0.85rem; + color: var(--app-text); +} + +.navbar-brand:hover { + color: var(--app-text); +} + +.brand-mark { + display: inline-flex; align-items: center; justify-content: center; - min-height: 100vh; - width: 100%; - padding: 20px; - box-sizing: border-box; - position: relative; - z-index: 1; -} - -@keyframes gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } -} - -.chat-container { - width: 100%; - max-width: 600px; - background: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 20px; - display: flex; - flex-direction: column; - height: 85vh; - box-shadow: 0 20px 40px rgba(0,0,0,0.2); - backdrop-filter: blur(15px); - -webkit-backdrop-filter: blur(15px); - overflow: hidden; -} - -.chat-header { - padding: 1.5rem; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - background: rgba(255, 255, 255, 0.5); - font-weight: 700; - font-size: 1.1rem; - display: flex; - justify-content: space-between; - align-items: center; -} - -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 1.25rem; -} - -/* Custom Scrollbar */ -::-webkit-scrollbar { - width: 6px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 10px; -} - -::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.5); -} - -.message { - max-width: 85%; - padding: 0.85rem 1.1rem; - border-radius: 16px; - line-height: 1.5; + width: 2.4rem; + height: 2.4rem; + border-radius: 0.85rem; + border: 1px solid var(--app-border); + background: var(--app-surface-muted); font-size: 0.95rem; - box-shadow: 0 4px 15px rgba(0,0,0,0.05); - animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); + font-weight: 700; + letter-spacing: 0.04em; } -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px) scale(0.95); } - to { opacity: 1; transform: translateY(0) scale(1); } +.navbar-brand strong { + font-size: 0.98rem; + font-weight: 700; + letter-spacing: -0.02em; } -.message.visitor { - align-self: flex-end; - background: linear-gradient(135deg, #212529 0%, #343a40 100%); - color: #fff; - border-bottom-right-radius: 4px; +.navbar-brand small { + font-size: 0.76rem; } -.message.bot { - align-self: flex-start; - background: #ffffff; - color: #212529; - border-bottom-left-radius: 4px; -} - -.chat-input-area { +.section-card { + background: var(--app-surface); + border: 1px solid rgba(17, 24, 39, 0.08); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); padding: 1.25rem; - background: rgba(255, 255, 255, 0.5); - border-top: 1px solid rgba(0, 0, 0, 0.05); } -.chat-input-area form { +.hero-card { + padding: 1.5rem; +} + +.hero-title { + font-size: clamp(1.9rem, 3vw, 2.75rem); + line-height: 1.08; + letter-spacing: -0.04em; + margin: 0; +} + +.hero-copy { + color: var(--app-muted); + font-size: 1rem; + max-width: 56rem; +} + +.eyebrow { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.32rem 0.65rem; + border-radius: 999px; + background: var(--app-surface-muted); + border: 1px solid var(--app-border); + color: var(--app-muted); + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.02em; + text-transform: uppercase; +} + +.hero-summary-list { + display: grid; + grid-template-columns: 1fr; + gap: 0.85rem; +} + +.hero-summary-list > div { + background: var(--app-surface-muted); + border: 1px solid var(--app-border); + border-radius: var(--radius-md); + padding: 0.95rem 1rem; +} + +.summary-label { + display: block; + color: var(--app-muted); + font-size: 0.76rem; + text-transform: uppercase; + letter-spacing: 0.03em; +} + +.hero-summary-list strong { + display: block; + margin-top: 0.25rem; + font-size: 1.2rem; + letter-spacing: -0.03em; +} + +.app-alert { + border: 1px solid rgba(17, 24, 39, 0.08); + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); +} + +.alert-success.app-alert { + background: var(--app-success-bg); + color: var(--app-success-text); +} + +.alert-warning.app-alert { + background: var(--app-warning-bg); + color: var(--app-warning-text); +} + +.alert-secondary.app-alert { + background: var(--app-surface-muted); + color: var(--app-text); +} + +.section-header { display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1rem; +} + +.section-header h2 { + margin: 0; + font-size: 1.08rem; + letter-spacing: -0.03em; +} + +.section-header p { + margin: 0.3rem 0 0; + color: var(--app-muted); + font-size: 0.92rem; +} + +.section-chip { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 2rem; + padding: 0.35rem 0.7rem; + border-radius: 999px; + border: 1px solid var(--app-border); + background: var(--app-surface-muted); + color: var(--app-muted); + font-size: 0.75rem; + font-weight: 600; + white-space: nowrap; +} + +.form-label { + font-size: 0.84rem; + font-weight: 600; + margin-bottom: 0.35rem; +} + +.form-control, +.form-select { + min-height: 2.85rem; + border-radius: 0.8rem; + border: 1px solid var(--app-border); + background: #fff; + color: var(--app-text); + padding: 0.7rem 0.85rem; + box-shadow: none; +} + +textarea.form-control { + min-height: 7.25rem; + resize: vertical; +} + +.form-control::placeholder, +.form-select::placeholder { + color: #8a95a4; +} + +.form-control:focus, +.form-select:focus, +.filter-pill:focus, +.btn-app:focus, +.search-form .form-control:focus { + border-color: rgba(32, 79, 150, 0.48); + box-shadow: 0 0 0 0.22rem rgba(32, 79, 150, 0.12); +} + +.form-helper { + color: var(--app-muted); + font-size: 0.88rem; +} + +.btn-app { + border-radius: 0.8rem; + padding: 0.7rem 1rem; + font-size: 0.9rem; + font-weight: 600; + letter-spacing: -0.01em; + box-shadow: none; +} + +.btn-primary.btn-app { + background: var(--app-primary); + border-color: var(--app-primary); +} + +.btn-primary.btn-app:hover, +.btn-primary.btn-app:focus { + background: var(--app-primary-dark); + border-color: var(--app-primary-dark); +} + +.btn-outline-secondary.btn-app { + border-color: var(--app-border-strong); + color: var(--app-text); + background: #fff; +} + +.btn-outline-secondary.btn-app:hover, +.btn-outline-secondary.btn-app:focus { + background: var(--app-surface-muted); + border-color: var(--app-border-strong); + color: var(--app-text); +} + +.priority-card { + border: 1px solid var(--app-border); + border-radius: var(--radius-md); + background: var(--app-surface-muted); + padding: 1rem; +} + +.priority-card-overdue { + border-color: rgba(142, 47, 43, 0.22); + background: var(--app-danger-bg); +} + +.priority-card h3 { + margin: 0.25rem 0 0.35rem; + font-size: 1rem; + letter-spacing: -0.02em; +} + +.priority-card p { + color: var(--app-muted); +} + +.priority-actions, +.table-actions { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.9rem; +} + +.empty-state { + padding: 2.25rem 1.2rem; + text-align: center; + border: 1px dashed var(--app-border); + border-radius: var(--radius-md); + background: var(--app-surface-muted); +} + +.compact-empty-state { + padding: 1.6rem 1rem; +} + +.empty-state h3 { + margin: 0 0 0.45rem; + font-size: 1rem; + letter-spacing: -0.02em; +} + +.empty-state p { + margin: 0; + color: var(--app-muted); +} + +.search-form { + display: flex; + gap: 0.65rem; + width: min(100%, 28rem); +} + +.filter-pills { + display: flex; + flex-wrap: wrap; gap: 0.75rem; } -.chat-input-area input { - flex: 1; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - padding: 0.75rem 1rem; - outline: none; - background: rgba(255, 255, 255, 0.9); - transition: all 0.3s ease; -} - -.chat-input-area input:focus { - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2); -} - -.chat-input-area button { - background: #212529; - color: #fff; - border: none; - padding: 0.75rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - transition: all 0.3s ease; -} - -.chat-input-area button:hover { - background: #000; - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(0,0,0,0.2); -} - -/* Background Animations */ -.bg-animations { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - overflow: hidden; - pointer-events: none; -} - -.blob { - position: absolute; - width: 500px; - height: 500px; - background: rgba(255, 255, 255, 0.2); - border-radius: 50%; - filter: blur(80px); - animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1); -} - -.blob-1 { - top: -10%; - left: -10%; - background: rgba(238, 119, 82, 0.4); -} - -.blob-2 { - bottom: -10%; - right: -10%; - background: rgba(35, 166, 213, 0.4); - animation-delay: -7s; - width: 600px; - height: 600px; -} - -.blob-3 { - top: 40%; - left: 30%; - background: rgba(231, 60, 126, 0.3); - animation-delay: -14s; - width: 450px; - height: 450px; -} - -@keyframes move { - 0% { transform: translate(0, 0) rotate(0deg) scale(1); } - 33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); } - 66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); } - 100% { transform: translate(0, 0) rotate(360deg) scale(1); } -} - -.header-link { - font-size: 14px; - color: #fff; - text-decoration: none; - background: rgba(0, 0, 0, 0.2); - padding: 0.5rem 1rem; - border-radius: 8px; - transition: all 0.3s ease; -} - -.header-link:hover { - background: rgba(0, 0, 0, 0.4); - text-decoration: none; -} - -/* Admin Styles */ -.admin-container { - max-width: 900px; - margin: 3rem auto; - padding: 2.5rem; - background: rgba(255, 255, 255, 0.85); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border-radius: 24px; - box-shadow: 0 20px 50px rgba(0,0,0,0.15); - border: 1px solid rgba(255, 255, 255, 0.4); - position: relative; - z-index: 1; -} - -.admin-container h1 { - margin-top: 0; - color: #212529; - font-weight: 800; -} - -.table { - width: 100%; - border-collapse: separate; - border-spacing: 0 8px; - margin-top: 1.5rem; -} - -.table th { - background: transparent; - border: none; - padding: 1rem; - color: #6c757d; - font-weight: 600; - text-transform: uppercase; - font-size: 0.75rem; - letter-spacing: 1px; -} - -.table td { - background: #fff; - padding: 1rem; - border: none; -} - -.table tr td:first-child { border-radius: 12px 0 0 12px; } -.table tr td:last-child { border-radius: 0 12px 12px 0; } - -.form-group { - margin-bottom: 1.25rem; -} - -.form-group label { - display: block; - margin-bottom: 0.5rem; - font-weight: 600; - font-size: 0.9rem; -} - -.form-control { - width: 100%; - padding: 0.75rem 1rem; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - background: #fff; - transition: all 0.3s ease; - box-sizing: border-box; -} - -.form-control:focus { - outline: none; - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1); -} - -.header-container { - display: flex; - justify-content: space-between; +.filter-pill { + display: inline-flex; align-items: center; + justify-content: space-between; + gap: 0.75rem; + padding: 0.72rem 0.9rem; + min-width: 8.75rem; + border-radius: 0.85rem; + border: 1px solid var(--app-border); + color: var(--app-text); + background: var(--app-surface-muted); + transition: border-color 0.2s ease, transform 0.2s ease, background 0.2s ease; } -.header-links { - display: flex; +.filter-pill:hover { + border-color: var(--app-border-strong); + transform: translateY(-1px); +} + +.filter-pill strong { + font-size: 0.95rem; + letter-spacing: -0.03em; +} + +.filter-pill.is-active { + background: #eef3fb; + border-color: rgba(32, 79, 150, 0.2); + color: var(--app-primary-dark); +} + +.app-table { + --bs-table-bg: transparent; + --bs-table-striped-bg: transparent; + --bs-table-hover-bg: rgba(17, 24, 39, 0.03); + margin-top: 0.25rem; +} + +.app-table thead th { + color: var(--app-muted); + border-bottom: 1px solid var(--app-border); + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.03em; + text-transform: uppercase; + padding: 0.9rem 0.8rem; + white-space: nowrap; +} + +.app-table tbody td { + border-bottom: 1px solid rgba(17, 24, 39, 0.06); + padding: 0.95rem 0.8rem; + vertical-align: middle; +} + +.loan-row-overdue { + background: rgba(255, 241, 240, 0.7); +} + +.loan-reference { + color: var(--app-muted); + font-size: 0.74rem; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.status-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 1.9rem; + padding: 0.35rem 0.65rem; + border-radius: 999px; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.01em; + border: 1px solid transparent; +} + +.badge-status-active { + background: #eef4ff; + color: var(--app-primary-dark); + border-color: rgba(32, 79, 150, 0.16); +} + +.badge-status-overdue { + background: var(--app-danger-bg); + color: var(--app-danger-text); + border-color: rgba(142, 47, 43, 0.18); +} + +.badge-status-returned { + background: var(--app-success-bg); + color: var(--app-success-text); + border-color: rgba(31, 81, 50, 0.16); +} + +.badge-status-neutral { + background: var(--app-surface-muted); + color: var(--app-muted); + border-color: var(--app-border); +} + +.detail-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; } -.admin-card { - background: rgba(255, 255, 255, 0.6); - padding: 2rem; - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.5); - margin-bottom: 2.5rem; - box-shadow: 0 10px 30px rgba(0,0,0,0.05); +.detail-grid div { + background: var(--app-surface-muted); + border: 1px solid var(--app-border); + border-radius: var(--radius-md); + padding: 0.9rem 0.95rem; } -.admin-card h3 { - margin-top: 0; - margin-bottom: 1.5rem; +.detail-grid dt { + margin: 0 0 0.35rem; + font-size: 0.76rem; font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--app-muted); } -.btn-delete { - background: #dc3545; - color: white; - border: none; - padding: 0.25rem 0.5rem; - border-radius: 4px; - cursor: pointer; +.detail-grid dd { + margin: 0; + font-size: 0.95rem; + letter-spacing: -0.01em; } -.btn-add { - background: #212529; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 4px; - cursor: pointer; - margin-top: 1rem; -} - -.btn-save { - background: #0088cc; - color: white; - border: none; - padding: 0.8rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - width: 100%; - transition: all 0.3s ease; -} - -.webhook-url { - font-size: 0.85em; - color: #555; - margin-top: 0.5rem; -} - -.history-table-container { - overflow-x: auto; - background: rgba(255, 255, 255, 0.4); +.message-preview { + border: 1px solid var(--app-border); + border-radius: var(--radius-md); padding: 1rem; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.3); + background: var(--app-surface-muted); + color: var(--app-text); + white-space: pre-wrap; } -.history-table { - width: 100%; +.timeline-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + gap: 0.85rem; } -.history-table-time { - width: 15%; - white-space: nowrap; - font-size: 0.85em; - color: #555; +.timeline-list li { + display: flex; + flex-direction: column; + gap: 0.2rem; + padding: 0.9rem 0.95rem; + border: 1px solid var(--app-border); + border-radius: var(--radius-md); + background: var(--app-surface-muted); } -.history-table-user { - width: 35%; - background: rgba(255, 255, 255, 0.3); - border-radius: 8px; - padding: 8px; +.timeline-list strong { + font-size: 0.9rem; + letter-spacing: -0.01em; } -.history-table-ai { - width: 50%; - background: rgba(255, 255, 255, 0.5); - border-radius: 8px; - padding: 8px; +.timeline-list span { + color: var(--app-muted); + font-size: 0.9rem; } -.no-messages { - text-align: center; - color: #777; -} \ No newline at end of file +.loan-detail-summary { + min-width: min(100%, 20rem); +} + +.not-found-card { + max-width: 42rem; + margin: 0 auto; + padding: 2rem 1.5rem; +} + +.toast { + background: rgba(17, 24, 39, 0.92); + color: #fff; + border-radius: 0.9rem; +} + +.app-footer { + color: var(--app-muted); + font-size: 0.88rem; + padding: 0 0 1.5rem; +} + +.app-footer a { + font-weight: 600; +} + +@media (max-width: 991.98px) { + .app-container { + width: min(100% - 1rem, 100%); + } + + .search-form { + width: 100%; + flex-direction: column; + } + + .detail-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 767.98px) { + .app-navbar { + padding-top: 0.7rem; + padding-bottom: 0.7rem; + } + + .hero-card, + .section-card { + padding: 1rem; + } + + .hero-title { + font-size: 1.75rem; + } + + .app-table thead { + display: none; + } + + .app-table, + .app-table tbody, + .app-table tr, + .app-table td { + display: block; + width: 100%; + } + + .app-table tbody tr { + border: 1px solid rgba(17, 24, 39, 0.08); + border-radius: var(--radius-md); + margin-bottom: 0.85rem; + overflow: hidden; + box-shadow: var(--shadow-sm); + background: #fff; + } + + .app-table tbody td { + padding: 0.85rem 1rem; + border-bottom: 1px solid rgba(17, 24, 39, 0.05); + } + + .app-table tbody td:last-child { + border-bottom: none; + } + + .table-actions { + justify-content: flex-start; + } +} diff --git a/assets/js/main.js b/assets/js/main.js index d349598..cd902dc 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,39 +1,52 @@ document.addEventListener('DOMContentLoaded', () => { - const chatForm = document.getElementById('chat-form'); - const chatInput = document.getElementById('chat-input'); - const chatMessages = document.getElementById('chat-messages'); + const toastElement = document.getElementById('appToast'); + const toastBody = toastElement ? toastElement.querySelector('.toast-body') : null; + const appToast = toastElement && window.bootstrap ? new bootstrap.Toast(toastElement, { delay: 2600 }) : null; - const appendMessage = (text, sender) => { - const msgDiv = document.createElement('div'); - msgDiv.classList.add('message', sender); - msgDiv.textContent = text; - chatMessages.appendChild(msgDiv); - chatMessages.scrollTop = chatMessages.scrollHeight; + const showToast = (message) => { + if (!toastBody || !appToast) return; + toastBody.textContent = message; + appToast.show(); }; - chatForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const message = chatInput.value.trim(); - if (!message) return; + document.querySelectorAll('.copy-trigger').forEach((button) => { + button.addEventListener('click', async () => { + const text = button.getAttribute('data-copy-text') || ''; + const successMessage = button.getAttribute('data-copy-success') || 'Teks berhasil disalin.'; - appendMessage(message, 'visitor'); - chatInput.value = ''; + if (!text) return; - try { - const response = await fetch('api/chat.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message }) - }); - const data = await response.json(); - - // Artificial delay for realism - setTimeout(() => { - appendMessage(data.reply, 'bot'); - }, 500); - } catch (error) { - console.error('Error:', error); - appendMessage("Sorry, something went wrong. Please try again.", 'bot'); - } + try { + if (navigator.clipboard && navigator.clipboard.writeText) { + await navigator.clipboard.writeText(text); + } else { + const temp = document.createElement('textarea'); + temp.value = text; + temp.setAttribute('readonly', 'readonly'); + temp.style.position = 'absolute'; + temp.style.left = '-9999px'; + document.body.appendChild(temp); + temp.select(); + document.execCommand('copy'); + document.body.removeChild(temp); + } + showToast(successMessage); + } catch (error) { + showToast('Gagal menyalin otomatis. Salin manual dari halaman detail.'); + } + }); }); + + document.querySelectorAll('.auto-dismiss-alert').forEach((alertElement) => { + window.setTimeout(() => { + if (!window.bootstrap) return; + const instance = bootstrap.Alert.getOrCreateInstance(alertElement); + instance.close(); + }, 5200); + }); + + const firstInvalidField = document.querySelector('.is-invalid'); + if (firstInvalidField instanceof HTMLElement) { + firstInvalidField.focus(); + } }); diff --git a/healthz.php b/healthz.php new file mode 100644 index 0000000..8f543c8 --- /dev/null +++ b/healthz.php @@ -0,0 +1,26 @@ +query('SELECT 1 AS healthy'); + $healthy = (int) ($statement ? $statement->fetchColumn() : 0) === 1; + + http_response_code($healthy ? 200 : 500); + echo json_encode([ + 'status' => $healthy ? 'ok' : 'error', + 'database' => $healthy ? 'ok' : 'error', + 'checked_at' => gmdate('c'), + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); +} catch (Throwable $exception) { + http_response_code(500); + echo json_encode([ + 'status' => 'error', + 'database' => 'error', + 'checked_at' => gmdate('c'), + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); +} diff --git a/index.php b/index.php index 7205f3d..3b51032 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,338 @@ $stats['total_loans'], + 'active' => $stats['active_loans'] - $stats['overdue_loans'], + 'overdue' => $stats['overdue_loans'], + 'returned' => $stats['returned_loans'], +]; ?> - + - New Style - - - - - - - - - - - - - - - - - - - + + + + - -
-
-

Analyzing your requirements and generating your website…

-
- Loading… + +
+
+
+
+
+ + + +
+
+
+
+
+ Sistem peminjaman barang +

Catat barang keluar, pantau jatuh tempo, dan tutup pengembalian tanpa spreadsheet.

+

Dashboard ini memberi alur tipis namun utuh: input transaksi peminjaman, lihat pinjaman aktif atau terlambat, kirim pengingat, lalu proses pengembalian dari halaman detail.

+
+
+
+
+ Transaksi tercatat + +
+
+ Butuh perhatian hari ini + +
+
+ Pengembalian selesai + +
+
+
+
+
+ + 0): ?> + + + + + + + +
+
+
+
+
+

Input peminjaman baru

+

Catat siapa meminjam barang, berapa jumlahnya, dan kapan harus kembali.

+
+ Create → confirm → track +
+ +
+ + +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+

Setelah disimpan, sistem mengarahkan ke halaman detail untuk pengingat dan pengembalian.

+ +
+
+
+
+ +
+
+
+
+

Antrian prioritas

+

Pinjaman yang sudah terlambat atau segera jatuh tempo.

+
+ Today +
+ + +
+

Belum ada pinjaman prioritas

+

Saat ada pinjaman yang jatuh tempo atau terlambat, daftar ini akan membantu staff mengambil tindakan cepat.

+
+ +
+ +
+
+
+ +

+

· Qty

+

Tempo ·

+
+ +
+
+ + Buka detail +
+
+ +
+ +
+
+
+ +
+
+
+

Board peminjaman

+

Filter transaksi aktif, terlambat, atau selesai. Gunakan detail untuk proses reminder dan pengembalian.

+
+ +
+ +
+ + $option]; + if ($search !== '') { + $params['q'] = $search; + } + ?> + + 'Aktif', + 'overdue' => 'Terlambat', + 'returned' => 'Dikembalikan', + default => 'Semua', + }) ?> + + + +
+ + +
+

Belum ada transaksi yang cocok

+

Mulai dari form di atas untuk membuat peminjaman pertama. Setelah tersimpan, transaksi akan muncul di board ini lengkap dengan status jatuh tempo.

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
RefPeminjamBarangPeriodeStatusReminderAksi
+ + + + + + + + + + + + + + + + +
+ + + + Detail +
+
+
+ +
-