From 162f8a38a4fb792977a566de48b0b16093ceeac6 Mon Sep 17 00:00:00 2001 From: wwartana Date: Mon, 19 Jan 2026 13:06:06 +0800 Subject: [PATCH] Initial commit: Aplikasi Foto Dokumentasi Nasabah Koperasi --- .gitignore | 37 +++ README.md | 195 ++++++++++++++++ app.js | 649 ++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 146 ++++++++++++ style.css | 650 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1677 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app.js create mode 100644 index.html create mode 100644 style.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ea6bd1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# OS files +Thumbs.db +.DS_Store + +# Logs +logs +*.log + +# Environment variables +.env +.env.local +.env.*.local + +# Build files +dist/ +build/ + +# Temporary files +*.tmp +*.temp diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a77808 --- /dev/null +++ b/README.md @@ -0,0 +1,195 @@ +# 📸 Aplikasi Dokumentasi Nasabah Koperasi + +Aplikasi web untuk mengambil foto dokumentasi nasabah saat penandatanganan surat perjanjian menggunakan webcam USB. + +## ✨ Fitur Utama + +### 1. Pengambilan Foto dengan Webcam +- ✅ Akses webcam USB secara otomatis +- ✅ Live preview sebelum mengambil foto +- ✅ Preview hasil foto sebelum menyimpan +- ✅ Opsi untuk mengambil ulang foto + +### 2. Input Data Nasabah +- ✅ Nama lengkap nasabah +- ✅ Nomor anggota koperasi +- ✅ Jenis perjanjian/dokumen +- ✅ Tanggal penandatanganan (auto-fill hari ini) +- ✅ Catatan tambahan (opsional) + +### 3. Penyimpanan Data +- ✅ Menggunakan IndexedDB (database lokal browser) +- ✅ Foto tersimpan dalam format base64 +- ✅ Data tersimpan secara permanen di browser +- ✅ Kapasitas besar (ratusan foto) + +### 4. Galeri & Pencarian +- ✅ Tampilan grid semua dokumentasi +- ✅ Pencarian berdasarkan nama/nomor anggota +- ✅ Thumbnail foto dengan info singkat +- ✅ Sorting otomatis (terbaru di atas) + +### 5. Detail & Pencetakan +- ✅ Lihat detail lengkap dokumentasi +- ✅ Cetak dokumentasi (format A4) +- ✅ Hapus dokumentasi yang tidak diperlukan + +### 6. Export Data +- ✅ Export data ke file CSV +- ✅ Berisi semua informasi kecuali foto +- ✅ Untuk keperluan backup atau analisis + +## 🚀 Cara Menggunakan + +### Instalasi + +1. **Download atau Clone Repository** + ```bash + # Download semua file ke folder C:\MyApp\webcam + ``` + +2. **Buka Aplikasi** + - Buka file `index.html` menggunakan browser modern (Chrome, Edge, atau Firefox) + - Atau double-click file `index.html` + +### Penggunaan + +1. **Mengambil Foto Dokumentasi** + - Isi form data nasabah (nama, nomor anggota, jenis perjanjian, tanggal) + - Klik tombol "Aktifkan Webcam" + - Izinkan akses webcam saat browser meminta + - Posisikan nasabah di depan webcam + - Klik "Ambil Foto" + - Preview hasil foto + - Jika sudah sesuai, klik "Simpan Dokumentasi" + - Jika ingin ambil ulang, klik "Ambil Ulang" + +2. **Melihat Galeri** + - Scroll ke bawah untuk melihat galeri dokumentasi + - Gunakan kotak pencarian untuk mencari nama/nomor anggota + - Klik pada card dokumentasi untuk melihat detail + +3. **Mencetak Dokumentasi** + - Klik card dokumentasi yang ingin dicetak + - Klik tombol "Cetak Dokumentasi" + - Pilih printer atau "Save as PDF" + +4. **Export Data** + - Klik tombol "Export Data" di galeri + - File CSV akan otomatis terdownload + - Buka dengan Excel atau Google Sheets + +## 🔧 Persyaratan Sistem + +- **Browser Modern:** + - Google Chrome 60+ (Direkomendasikan) + - Microsoft Edge 79+ + - Mozilla Firefox 60+ + +- **Webcam USB:** + - Webcam USB yang terhubung dan berfungsi + - Driver webcam sudah terinstal + +- **Koneksi Internet:** + - Hanya diperlukan untuk load font (Google Fonts) + - Aplikasi tetap berfungsi offline setelah font ter-cache + +## 💾 Penyimpanan Data + +### IndexedDB +- Data disimpan di IndexedDB browser +- Lokasi penyimpanan: Browser's Application Data +- Data bersifat permanen sampai dihapus manual +- Foto tersimpan dalam format base64 (JPEG dengan kualitas 90%) + +### Kapasitas +- Chrome/Edge: Hingga 60% dari disk space yang tersedia +- Firefox: Hingga 10% dari free disk space +- Estimasi: Bisa menyimpan ratusan hingga ribuan foto + +### Backup Data +- **Metode 1:** Export ke CSV (tanpa foto) +- **Metode 2:** Browser backup + - Chrome: `chrome://settings/cookies` > Site Data > klik domain + - Export IndexedDB menggunakan Developer Tools + +## 🎨 Kustomisasi + +### Mengubah Logo Koperasi +Edit file `style.css` dan tambahkan logo di header: +```css +.app-header::before { + content: url('logo-koperasi.png'); +} +``` + +### Menambah Jenis Perjanjian +Edit file `index.html` di bagian ` + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +
+

📷 Webcam

+
+ + + +
+ +
+ + + + +
+ +
+
+ + + + + + + + + + + +
+ + + + diff --git a/style.css b/style.css new file mode 100644 index 0000000..f7811d9 --- /dev/null +++ b/style.css @@ -0,0 +1,650 @@ +/* Reset & Base Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + /* Color Palette - Professional Blue Theme */ + --primary-color: #2563eb; + --primary-dark: #1e40af; + --primary-light: #3b82f6; + --secondary-color: #64748b; + --success-color: #10b981; + --warning-color: #f59e0b; + --danger-color: #ef4444; + + /* Neutral Colors */ + --bg-color: #f8fafc; + --card-bg: #ffffff; + --text-primary: #1e293b; + --text-secondary: #64748b; + --border-color: #e2e8f0; + + /* Gradients */ + --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --gradient-blue: linear-gradient(135deg, #2563eb 0%, #1e40af 100%); + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1); + + /* Spacing */ + --spacing-xs: 0.5rem; + --spacing-sm: 0.75rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + + /* Border Radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: var(--text-primary); + line-height: 1.6; + min-height: 100vh; + padding: var(--spacing-lg); +} + +.container { + max-width: 1400px; + margin: 0 auto; +} + +/* Header */ +.app-header { + background: var(--card-bg); + border-radius: var(--radius-xl); + padding: var(--spacing-xl); + margin-bottom: var(--spacing-xl); + box-shadow: var(--shadow-lg); + text-align: center; + background: linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.9) 100%); + backdrop-filter: blur(10px); +} + +.app-header h1 { + font-size: 2rem; + font-weight: 700; + background: var(--gradient-blue); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: var(--spacing-xs); +} + +.app-header p { + color: var(--text-secondary); + font-size: 1rem; +} + +/* Main Content */ +.main-content { + display: flex; + flex-direction: column; + gap: var(--spacing-xl); +} + +/* Card */ +.card { + background: var(--card-bg); + border-radius: var(--radius-xl); + padding: var(--spacing-xl); + box-shadow: var(--shadow-lg); + transition: all 0.3s ease; +} + +.card:hover { + box-shadow: var(--shadow-xl); + transform: translateY(-2px); +} + +.card h2 { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: var(--spacing-lg); + color: var(--text-primary); + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +/* Capture Section */ +.capture-section { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--spacing-xl); +} + +/* Form Styles */ +.form-group { + margin-bottom: var(--spacing-lg); +} + +.form-group label { + display: block; + font-weight: 500; + margin-bottom: var(--spacing-xs); + color: var(--text-primary); + font-size: 0.9rem; +} + +.required { + color: var(--danger-color); +} + +.form-group input, +.form-group select, +.form-group textarea { + width: 100%; + padding: 0.75rem; + border: 2px solid var(--border-color); + border-radius: var(--radius-md); + font-family: inherit; + font-size: 0.95rem; + transition: all 0.2s ease; + background: var(--bg-color); +} + +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +.form-group textarea { + resize: vertical; + min-height: 80px; +} + +/* Webcam Styles */ +.webcam-container { + position: relative; + width: 100%; + aspect-ratio: 4/3; + background: #000; + border-radius: var(--radius-lg); + overflow: hidden; + margin-bottom: var(--spacing-lg); +} + +#webcam, +.preview-container { + width: 100%; + height: 100%; + object-fit: cover; +} + +.preview-container img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.webcam-controls { + display: flex; + gap: var(--spacing-sm); + flex-wrap: wrap; +} + +.status-message { + margin-top: var(--spacing-md); + padding: var(--spacing-md); + border-radius: var(--radius-md); + font-size: 0.9rem; + text-align: center; +} + +.status-message.info { + background: #dbeafe; + color: #1e40af; +} + +.status-message.success { + background: #d1fae5; + color: #065f46; +} + +.status-message.error { + background: #fee2e2; + color: #991b1b; +} + +/* Buttons */ +.btn { + padding: 0.75rem 1.5rem; + border: none; + border-radius: var(--radius-md); + font-family: inherit; + font-size: 0.95rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + gap: var(--spacing-xs); + box-shadow: var(--shadow-sm); +} + +.btn:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.btn:active { + transform: translateY(0); +} + +.btn-icon { + font-size: 1.1rem; +} + +.btn-primary { + background: var(--gradient-blue); + color: white; +} + +.btn-primary:hover { + filter: brightness(1.1); +} + +.btn-success { + background: var(--success-color); + color: white; +} + +.btn-success:hover { + background: #059669; +} + +.btn-warning { + background: var(--warning-color); + color: white; +} + +.btn-warning:hover { + background: #d97706; +} + +.btn-danger { + background: var(--danger-color); + color: white; +} + +.btn-danger:hover { + background: #dc2626; +} + +.btn-secondary { + background: var(--secondary-color); + color: white; +} + +.btn-secondary:hover { + background: #475569; +} + +/* Gallery Section */ +.gallery-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--spacing-lg); + flex-wrap: wrap; + gap: var(--spacing-md); +} + +.gallery-controls { + display: flex; + gap: var(--spacing-md); + flex-wrap: wrap; +} + +#searchInput { + padding: 0.75rem 1rem; + border: 2px solid var(--border-color); + border-radius: var(--radius-md); + font-family: inherit; + font-size: 0.95rem; + min-width: 300px; + transition: all 0.2s ease; +} + +#searchInput:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +.gallery-stats { + background: var(--bg-color); + padding: var(--spacing-md); + border-radius: var(--radius-md); + margin-bottom: var(--spacing-lg); + font-size: 0.9rem; + color: var(--text-secondary); +} + +.gallery-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: var(--spacing-lg); +} + +.gallery-item { + background: var(--bg-color); + border-radius: var(--radius-lg); + overflow: hidden; + cursor: pointer; + transition: all 0.3s ease; + border: 2px solid transparent; +} + +.gallery-item:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-lg); + border-color: var(--primary-color); +} + +.gallery-item-image { + width: 100%; + aspect-ratio: 4/3; + object-fit: cover; + background: #000; +} + +.gallery-item-info { + padding: var(--spacing-md); +} + +.gallery-item-info h3 { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: var(--spacing-xs); + color: var(--text-primary); +} + +.gallery-item-info p { + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: var(--spacing-xs); +} + +.gallery-item-date { + font-size: 0.8rem; + color: var(--text-secondary); + font-style: italic; +} + +/* Empty State */ +.empty-state { + text-align: center; + padding: var(--spacing-xl) 0; + color: var(--text-secondary); +} + +.empty-icon { + font-size: 4rem; + margin-bottom: var(--spacing-md); +} + +.empty-state h3 { + font-size: 1.3rem; + margin-bottom: var(--spacing-sm); + color: var(--text-primary); +} + +/* Modal */ +.modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(5px); + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.modal.show { + display: flex; + align-items: center; + justify-content: center; +} + +.modal-content { + background: var(--card-bg); + border-radius: var(--radius-xl); + max-width: 800px; + width: 90%; + max-height: 90vh; + overflow-y: auto; + position: relative; + animation: slideUp 0.3s ease; +} + +@keyframes slideUp { + from { + transform: translateY(50px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.close-modal { + position: absolute; + right: var(--spacing-lg); + top: var(--spacing-lg); + font-size: 2rem; + font-weight: bold; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s ease; + z-index: 10; +} + +.close-modal:hover { + color: var(--text-primary); + transform: rotate(90deg); +} + +.modal-body { + padding: var(--spacing-xl); +} + +.modal-actions { + padding: var(--spacing-lg) var(--spacing-xl); + border-top: 2px solid var(--border-color); + display: flex; + gap: var(--spacing-md); + justify-content: flex-end; +} + +/* Detail View */ +.detail-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--spacing-xl); +} + +.detail-image { + width: 100%; + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); +} + +.detail-info h2 { + font-size: 1.8rem; + margin-bottom: var(--spacing-lg); + color: var(--text-primary); +} + +.detail-row { + margin-bottom: var(--spacing-md); + padding-bottom: var(--spacing-md); + border-bottom: 1px solid var(--border-color); +} + +.detail-row:last-child { + border-bottom: none; +} + +.detail-label { + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: var(--spacing-xs); + font-weight: 500; +} + +.detail-value { + font-size: 1rem; + color: var(--text-primary); + font-weight: 500; +} + +/* Toast Notification */ +.toast { + position: fixed; + bottom: var(--spacing-xl); + right: var(--spacing-xl); + background: var(--text-primary); + color: white; + padding: var(--spacing-md) var(--spacing-lg); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-xl); + display: none; + align-items: center; + gap: var(--spacing-sm); + z-index: 2000; + animation: slideInRight 0.3s ease; +} + +@keyframes slideInRight { + from { + transform: translateX(400px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +.toast.show { + display: flex; +} + +.toast.success { + background: var(--success-color); +} + +.toast.error { + background: var(--danger-color); +} + +/* Print Styles */ +@media print { + body { + background: white; + padding: 0; + } + + .app-header, + .webcam-controls, + .gallery-section, + .modal-actions, + .close-modal { + display: none !important; + } + + .modal { + display: block !important; + position: static; + background: white; + } + + .modal-content { + max-width: 100%; + width: 100%; + box-shadow: none; + max-height: none; + } + + .detail-container { + grid-template-columns: 1fr; + } + + .detail-image { + max-width: 100%; + page-break-inside: avoid; + } + + .card { + box-shadow: none; + page-break-inside: avoid; + } +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .capture-section { + grid-template-columns: 1fr; + } + + .detail-container { + grid-template-columns: 1fr; + } +} + +@media (max-width: 768px) { + body { + padding: var(--spacing-md); + } + + .app-header h1 { + font-size: 1.5rem; + } + + .gallery-header { + flex-direction: column; + align-items: stretch; + } + + #searchInput { + min-width: 100%; + } + + .gallery-grid { + grid-template-columns: 1fr; + } + + .webcam-controls { + flex-direction: column; + } + + .btn { + width: 100%; + justify-content: center; + } +}