./ 0000775 0001747 0001747 00000000000 15254645641 010217 5 ustar coder coder ./vite.config.js 0000644 0001747 0001747 00000000464 15235024305 012755 0 ustar coder coder import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [react(), tailwindcss()], server: { host: "0.0.0.0", port: 3000, strictPort: true, hmr: { port: 3000, }, }, }); ./src/ 0000755 0001747 0001747 00000000000 15254645414 011002 5 ustar coder coder ./src/App.tsx 0000644 0001747 0001747 00000002006 15254645624 012263 0 ustar coder coder import { useState, useEffect } from 'react'; import LoginPage from './components/LoginPage'; import Dashboard from './components/Dashboard'; function App() { const [isAuthenticated, setIsAuthenticated] = useState(false); const [username, setUsername] = useState(''); useEffect(() => { // Check if user is already logged in (session storage) const stored = sessionStorage.getItem('ppid_auth'); if (stored) { const data = JSON.parse(stored); setIsAuthenticated(true); setUsername(data.username); } }, []); const handleLogin = (user: string) => { setIsAuthenticated(true); setUsername(user); sessionStorage.setItem('ppid_auth', JSON.stringify({ username: user })); }; const handleLogout = () => { setIsAuthenticated(false); setUsername(''); sessionStorage.removeItem('ppid_auth'); }; if (!isAuthenticated) { return ; } return ; } export default App; ./src/index.css 0000644 0001747 0001747 00000001244 15254645635 012631 0 ustar coder coder @import "tailwindcss"; @keyframes shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); } 20%, 40%, 60%, 80% { transform: translateX(4px); } } .animate-shake { animation: shake 0.5s ease-in-out; } /* Custom scrollbar */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: #9ca3af; } /* Smooth transitions */ * { scroll-behavior: smooth; } /* Table responsive */ @media (max-width: 640px) { table { font-size: 0.8rem; } } ./src/main.tsx 0000644 0001747 0001747 00000000277 15234570653 012474 0 ustar coder coder import React from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; import App from "./App.tsx"; ReactDOM.createRoot(document.getElementById("root")!).render(); ./src/components/ 0000755 0001747 0001747 00000000000 15254645612 013167 5 ustar coder coder ./src/components/LoginPage.tsx 0000644 0001747 0001747 00000015714 15254645414 015604 0 ustar coder coder import { useState } from 'react'; interface LoginPageProps { onLogin: (username: string) => void; } export default function LoginPage({ onLogin }: LoginPageProps) { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [showPassword, setShowPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setError(''); setIsLoading(true); setTimeout(() => { if (username === 'ppid2026' && password === 'PPID.20261Tilkam') { onLogin(username); } else { setError('Username atau password salah!'); setIsLoading(false); } }, 800); }; return (
{/* Background Pattern */}
{/* Floating Elements */}
{/* Logo & Header */}

PPID SMAN 1 Tilatang Kamang

Sistem Administrasi Dashboard

{/* Login Card */}

Login Admin

Masukkan kredensial untuk mengakses dashboard

setUsername(e.target.value)} className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all outline-none text-gray-800 bg-gray-50 hover:bg-white" placeholder="Masukkan username" required />
setPassword(e.target.value)} className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all outline-none text-gray-800 bg-gray-50 hover:bg-white pr-12" placeholder="Masukkan password" required />
{error && (
{error}
)}

Sistem dilindungi dengan autentikasi keamanan

{/* Footer */}

© 2026 PPID SMAN 1 Tilatang Kamang. All rights reserved.

); } ./src/components/Dashboard.tsx 0000644 0001747 0001747 00000122621 15254645612 015622 0 ustar coder coder import { useState } from 'react'; import type { FileItem } from '../types'; interface DashboardProps { username: string; onLogout: () => void; } const initialFiles: FileItem[] = [ { id: '1', name: 'Laporan Informasi Publik 2025.pdf', category: 'Laporan', type: 'PDF', size: '2.4 MB', uploadDate: '2025-12-15', description: 'Laporan informasi publik tahunan', status: 'published' }, { id: '2', name: 'SK PPID 2026.pdf', category: 'Dokumen Hukum', type: 'PDF', size: '1.1 MB', uploadDate: '2026-01-05', description: 'Surat Keputusan PPID tahun 2026', status: 'published' }, { id: '3', name: 'Profil Sekolah.docx', category: 'Profil', type: 'DOCX', size: '3.7 MB', uploadDate: '2025-11-20', description: 'Profil lengkap SMAN 1 Tilatang Kamang', status: 'published' }, { id: '4', name: 'Data Statistik Siswa.xlsx', category: 'Data', type: 'XLSX', size: '890 KB', uploadDate: '2026-01-10', description: 'Data statistik siswa tahun ajaran 2025/2026', status: 'draft' }, { id: '5', name: 'Regulasi PPID.pdf', category: 'Dokumen Hukum', type: 'PDF', size: '1.5 MB', uploadDate: '2025-10-08', description: 'Regulasi terkait PPID dan keterbukaan informasi', status: 'published' }, { id: '6', name: 'Formulir Permohonan Informasi.docx', category: 'Formulir', type: 'DOCX', size: '245 KB', uploadDate: '2026-01-12', description: 'Formulir permohonan informasi publik', status: 'published' }, { id: '7', name: 'Berita Acara Rapat.pdf', category: 'Laporan', type: 'PDF', size: '1.8 MB', uploadDate: '2026-01-08', description: 'Berita acara rapat koordinasi PPID', status: 'draft' }, { id: '8', name: 'Struktur Organisasi PPID.png', category: 'Profil', type: 'PNG', size: '520 KB', uploadDate: '2025-09-15', description: 'Bagan struktur organisasi PPID', status: 'published' }, ]; const categories = ['Semua', 'Laporan', 'Dokumen Hukum', 'Profil', 'Data', 'Formulir']; export default function Dashboard({ username, onLogout }: DashboardProps) { const [files, setFiles] = useState(initialFiles); const [activeTab, setActiveTab] = useState('dashboard'); const [searchTerm, setSearchTerm] = useState(''); const [selectedCategory, setSelectedCategory] = useState('Semua'); const [sidebarOpen, setSidebarOpen] = useState(false); const [showUploadModal, setShowUploadModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [editingFile, setEditingFile] = useState(null); const [deleteConfirm, setDeleteConfirm] = useState(null); // Upload form state const [newFile, setNewFile] = useState({ name: '', category: 'Laporan', type: 'PDF', description: '', status: 'draft' as 'published' | 'draft', }); const filteredFiles = files.filter(file => { const matchesSearch = file.name.toLowerCase().includes(searchTerm.toLowerCase()) || file.description.toLowerCase().includes(searchTerm.toLowerCase()); const matchesCategory = selectedCategory === 'Semua' || file.category === selectedCategory; return matchesSearch && matchesCategory; }); const stats = { totalFiles: files.length, published: files.filter(f => f.status === 'published').length, drafts: files.filter(f => f.status === 'draft').length, categories: [...new Set(files.map(f => f.category))].length, }; const handleUpload = () => { if (!newFile.name) return; const file: FileItem = { id: Date.now().toString(), name: newFile.name, category: newFile.category, type: newFile.type, size: `${(Math.random() * 5 + 0.1).toFixed(1)} MB`, uploadDate: new Date().toISOString().split('T')[0], description: newFile.description, status: newFile.status, }; setFiles([file, ...files]); setNewFile({ name: '', category: 'Laporan', type: 'PDF', description: '', status: 'draft' }); setShowUploadModal(false); }; const handleDelete = (id: string) => { setFiles(files.filter(f => f.id !== id)); setDeleteConfirm(null); }; const handleEdit = (file: FileItem) => { setEditingFile({ ...file }); setShowEditModal(true); }; const handleSaveEdit = () => { if (!editingFile) return; setFiles(files.map(f => f.id === editingFile.id ? editingFile : f)); setShowEditModal(false); setEditingFile(null); }; const toggleStatus = (id: string) => { setFiles(files.map(f => f.id === id ? { ...f, status: f.status === 'published' ? 'draft' : 'published' } : f)); }; const menuItems = [ { id: 'dashboard', icon: 'fa-tachometer-alt', label: 'Dashboard' }, { id: 'files', icon: 'fa-folder-open', label: 'Kelola File' }, { id: 'upload', icon: 'fa-cloud-upload-alt', label: 'Upload File' }, { id: 'categories', icon: 'fa-tags', label: 'Kategori' }, { id: 'settings', icon: 'fa-cog', label: 'Pengaturan' }, ]; return (
{/* Sidebar Overlay for Mobile */} {sidebarOpen && (
setSidebarOpen(false)} /> )} {/* Sidebar */} {/* Main Content */}
{/* Top Bar */}

{menuItems.find(m => m.id === activeTab)?.label || 'Dashboard'}

Selamat datang, {username}

{new Date().toLocaleDateString('id-ID', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}
{/* Content Area */}
{/* Dashboard Tab */} {activeTab === 'dashboard' && (
{/* Stats Cards */}

Total File

{stats.totalFiles}

Dipublikasi

{stats.published}

Draft

{stats.drafts}

Kategori

{stats.categories}

{/* Recent Files */}

File Terbaru

{files.slice(0, 5).map(file => (
{file.type}

{file.name}

{file.uploadDate} • {file.size}

{file.status === 'published' ? 'Published' : 'Draft'}
))}
{/* Quick Actions */}

Aksi Cepat

)} {/* Files Tab */} {activeTab === 'files' && (
{/* Search & Filter */}
setSearchTerm(e.target.value)} placeholder="Cari file..." className="w-full pl-10 pr-4 py-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 outline-none text-sm" />
{/* File List */}
{filteredFiles.map(file => ( ))}
File Kategori Ukuran Tanggal Status Aksi
{file.type}

{file.name}

{file.description}

{file.category} {file.size} {file.uploadDate}
{filteredFiles.length === 0 && (

Tidak ada file ditemukan

)}
)} {/* Upload Tab */} {activeTab === 'upload' && (

Upload File Baru

setNewFile({ ...newFile, name: e.target.value })} className="w-full px-4 py-2.5 border border-gray-200 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 outline-none text-sm" placeholder="Masukkan nama file" />