Initial commit apps directory with .gitignore
This commit is contained in:
BIN
spj-komite/._package-lock.json
generated
Normal file
BIN
spj-komite/._package-lock.json
generated
Normal file
Binary file not shown.
BIN
spj-komite/._server.js
Normal file
BIN
spj-komite/._server.js
Normal file
Binary file not shown.
129
spj-komite/.gitignore
vendored
Normal file
129
spj-komite/.gitignore
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.test
|
||||
.env.production
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# OS metadata
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Project specific backups
|
||||
*.tar.gz
|
||||
BIN
spj-komite/database/._schema.sql
Normal file
BIN
spj-komite/database/._schema.sql
Normal file
Binary file not shown.
175
spj-komite/database/schema.sql
Normal file
175
spj-komite/database/schema.sql
Normal file
@@ -0,0 +1,175 @@
|
||||
-- SPJ Komite MySQL Database Schema
|
||||
-- Database: db_spjkomite
|
||||
|
||||
-- Tabel untuk kode kegiatan
|
||||
CREATE TABLE IF NOT EXISTS kode_kegiatan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
kode VARCHAR(100) NOT NULL UNIQUE,
|
||||
uraian TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Tabel untuk pengaturan/manajemen
|
||||
CREATE TABLE IF NOT EXISTS pengaturan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama_ketua_komite VARCHAR(255),
|
||||
bendahara VARCHAR(255),
|
||||
nama_tim_teknis_a VARCHAR(255),
|
||||
jabatan_tim_teknis_a VARCHAR(255),
|
||||
nama_tim_teknis_b VARCHAR(255),
|
||||
jabatan_tim_teknis_b VARCHAR(255),
|
||||
nama_tim_teknis_c VARCHAR(255),
|
||||
jabatan_tim_teknis_c VARCHAR(255),
|
||||
tahun_anggaran VARCHAR(10),
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Tabel untuk RKS (Rencana Kerja dan Syarat)
|
||||
CREATE TABLE IF NOT EXISTS rks (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
pekerjaan TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Counter untuk nomor dokumen
|
||||
CREATE TABLE IF NOT EXISTS nomor_counter (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tahun_anggaran VARCHAR(10) NOT NULL,
|
||||
counter INT DEFAULT 0,
|
||||
UNIQUE KEY unique_year (tahun_anggaran)
|
||||
);
|
||||
|
||||
-- Tabel untuk pesanan (surat pesanan)
|
||||
CREATE TABLE IF NOT EXISTS pesanan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tanggal_pesanan DATE NOT NULL,
|
||||
nomor_pesanan VARCHAR(255) NOT NULL,
|
||||
penyedia VARCHAR(255) NOT NULL,
|
||||
nama_jenis_barang VARCHAR(255) NOT NULL,
|
||||
spesifikasi_teknis VARCHAR(500),
|
||||
volume DECIMAL(10,2) NOT NULL,
|
||||
satuan VARCHAR(50) NOT NULL,
|
||||
keterangan TEXT,
|
||||
waktu_pengerjaan INT NOT NULL,
|
||||
batas_tanggal_pengerjaan DATE NOT NULL,
|
||||
penerima_pesanan VARCHAR(255),
|
||||
yang_memesan VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_nomor_pesanan (nomor_pesanan)
|
||||
);
|
||||
|
||||
-- Tabel untuk BAP (Berita Acara Pemeriksaan)
|
||||
CREATE TABLE IF NOT EXISTS bap (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nomor_bap VARCHAR(255) NOT NULL UNIQUE,
|
||||
pekerjaan TEXT NOT NULL,
|
||||
tanggal_pemeriksaan DATE NOT NULL,
|
||||
nama_tim_teknis_a VARCHAR(255),
|
||||
nama_tim_teknis_b VARCHAR(255),
|
||||
nama_tim_teknis_c VARCHAR(255),
|
||||
jabatan_tim_teknis_a VARCHAR(255),
|
||||
jabatan_tim_teknis_b VARCHAR(255),
|
||||
jabatan_tim_teknis_c VARCHAR(255),
|
||||
tujuan TEXT,
|
||||
alamat_penyedia TEXT,
|
||||
ketua_komite VARCHAR(255),
|
||||
nomor_pesanan VARCHAR(255),
|
||||
kode_kegiatan VARCHAR(100),
|
||||
kode_sub_kegiatan VARCHAR(100),
|
||||
kode_rekening VARCHAR(100),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_nomor_bap (nomor_bap),
|
||||
INDEX idx_nomor_pesanan (nomor_pesanan)
|
||||
);
|
||||
|
||||
-- Tabel untuk BAST (Berita Acara Serah Terima)
|
||||
CREATE TABLE IF NOT EXISTS bast (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nomor_bast VARCHAR(255) NOT NULL UNIQUE,
|
||||
tanggal_bast DATE NOT NULL,
|
||||
nomor_bap VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_nomor_bast (nomor_bast),
|
||||
INDEX idx_nomor_bap (nomor_bap)
|
||||
);
|
||||
|
||||
-- Tabel untuk Nota
|
||||
CREATE TABLE IF NOT EXISTS nota (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nomor INT NOT NULL,
|
||||
volume DECIMAL(10,2) NOT NULL,
|
||||
satuan VARCHAR(50) NOT NULL,
|
||||
nama_barang VARCHAR(255) NOT NULL,
|
||||
harga_satuan DECIMAL(15,2) NOT NULL,
|
||||
jumlah DECIMAL(15,2) NOT NULL,
|
||||
nomor_bap VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_nomor_bap (nomor_bap)
|
||||
);
|
||||
|
||||
-- Tabel untuk Foto Dokumentasi
|
||||
CREATE TABLE IF NOT EXISTS foto (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nomor_bap VARCHAR(255) NOT NULL,
|
||||
related_id VARCHAR(255),
|
||||
filename VARCHAR(255),
|
||||
url LONGTEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_nomor_bap (nomor_bap)
|
||||
);
|
||||
|
||||
-- Tabel untuk Aset Aplikasi (Logo dan KOP)
|
||||
CREATE TABLE IF NOT EXISTS aset_aplikasi (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tipe VARCHAR(50) NOT NULL UNIQUE,
|
||||
data_url LONGTEXT,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Insert default aset_aplikasi rows
|
||||
INSERT INTO aset_aplikasi (tipe, data_url)
|
||||
VALUES ('logo', NULL), ('kop', NULL)
|
||||
ON DUPLICATE KEY UPDATE tipe=tipe;
|
||||
|
||||
-- Tabel untuk Penyedia (Supplier) dengan KOP
|
||||
CREATE TABLE IF NOT EXISTS penyedia (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nama VARCHAR(255) NOT NULL UNIQUE,
|
||||
alamat TEXT,
|
||||
kop_url LONGTEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Tabel untuk SPJ Lomba (Kegiatan Lomba - Pembayaran Non-Tunai)
|
||||
CREATE TABLE IF NOT EXISTS spj_lomba (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
nomor_spj VARCHAR(255) NOT NULL UNIQUE,
|
||||
tanggal DATE NOT NULL,
|
||||
nama_kegiatan VARCHAR(500) NOT NULL,
|
||||
jenis_pembayaran VARCHAR(100) NOT NULL,
|
||||
penerima VARCHAR(255) NOT NULL,
|
||||
nama_bank VARCHAR(100) NOT NULL,
|
||||
nomor_rekening VARCHAR(50) NOT NULL,
|
||||
atas_nama VARCHAR(255) NOT NULL,
|
||||
nominal DECIMAL(15,2) NOT NULL,
|
||||
keterangan TEXT,
|
||||
bukti_transfer LONGTEXT,
|
||||
kode_kegiatan VARCHAR(100),
|
||||
kode_sub_kegiatan VARCHAR(100),
|
||||
kode_rekening VARCHAR(100),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_nomor_spj (nomor_spj),
|
||||
INDEX idx_tanggal (tanggal)
|
||||
);
|
||||
|
||||
-- Insert default pengaturan row
|
||||
INSERT INTO pengaturan (id, nama_ketua_komite, bendahara, tahun_anggaran)
|
||||
VALUES (1, '', '', '2026')
|
||||
ON DUPLICATE KEY UPDATE id=id;
|
||||
|
||||
-- Insert default counter for current year
|
||||
INSERT INTO nomor_counter (tahun_anggaran, counter)
|
||||
VALUES ('2026', 0)
|
||||
ON DUPLICATE KEY UPDATE tahun_anggaran=tahun_anggaran;
|
||||
|
||||
3785
spj-komite/package-lock.json
generated
Normal file
3785
spj-komite/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
spj-komite/package.json
Normal file
34
spj-komite/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "spj-komite-smanab",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "npx vite",
|
||||
"build": "npx vite build",
|
||||
"start": "node server.js",
|
||||
"preview": "npx vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^4.19.2",
|
||||
"lucide-react": "^0.379.0",
|
||||
"multer": "^2.0.2",
|
||||
"mysql2": "^3.16.0",
|
||||
"postcss": "^8.4.38",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.2.0",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22"
|
||||
}
|
||||
}
|
||||
BIN
spj-komite/public/assets/kop.png
Normal file
BIN
spj-komite/public/assets/kop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 123 KiB |
BIN
spj-komite/public/assets/logo.png
Normal file
BIN
spj-komite/public/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
354
spj-komite/public/login.html
Normal file
354
spj-komite/public/login.html
Normal file
@@ -0,0 +1,354 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login - SPJ Komite SMA Negeri 1 Abiansemal</title>
|
||||
<link rel="icon" type="image/png" href="/assets/logo.png">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<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=Nunito:wght@400;600;700&family=Poppins:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet">
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Nunito', 'sans-serif'],
|
||||
heading: ['Poppins', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
'primary-blue': '#0056b3',
|
||||
'primary-blue-dark': '#004085',
|
||||
'accent-green': '#28a745',
|
||||
'accent-green-dark': '#218838',
|
||||
'neutral-light': '#f8f9fa',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Nunito', sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-bg {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.login-bg::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow:
|
||||
0 25px 50px -12px rgba(0, 0, 0, 0.25),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1) inset;
|
||||
}
|
||||
|
||||
.floating-shapes div {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
opacity: 0.1;
|
||||
animation: float 20s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.floating-shapes .shape1 {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: #fff;
|
||||
top: -100px;
|
||||
left: -100px;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.floating-shapes .shape2 {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: #fff;
|
||||
bottom: -50px;
|
||||
right: -50px;
|
||||
animation-delay: -5s;
|
||||
}
|
||||
|
||||
.floating-shapes .shape3 {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
background: #fff;
|
||||
top: 50%;
|
||||
right: 10%;
|
||||
animation-delay: -10s;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0) rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-30px) rotate(10deg);
|
||||
}
|
||||
}
|
||||
|
||||
.input-field {
|
||||
transition: all 0.3s ease;
|
||||
background: rgba(248, 249, 250, 0.8);
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
background: white;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.15);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
.login-btn:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.login-btn:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 15px 30px -5px rgba(102, 126, 234, 0.5);
|
||||
}
|
||||
|
||||
.login-btn:active {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
animation: pulse-glow 3s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 0 20px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 0 40px rgba(102, 126, 234, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.6s ease-out forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-in-delay-1 {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
.fade-in-delay-2 {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.fade-in-delay-3 {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.fade-in-delay-4 {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.register-link {
|
||||
color: #667eea;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.register-link:hover {
|
||||
color: #764ba2;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="login-bg">
|
||||
<div class="floating-shapes">
|
||||
<div class="shape1"></div>
|
||||
<div class="shape2"></div>
|
||||
<div class="shape3"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative min-h-screen flex items-center justify-center p-4">
|
||||
<div class="w-full max-w-md">
|
||||
<div class="glass-card rounded-3xl p-8 md:p-10">
|
||||
<!-- Logo & Header -->
|
||||
<div class="text-center mb-8 fade-in fade-in-delay-1">
|
||||
<div class="logo-container w-24 h-24 mx-auto mb-5 rounded-2xl bg-white p-2 shadow-lg">
|
||||
<img src="/assets/logo.png" alt="Logo" class="w-full h-full object-contain">
|
||||
</div>
|
||||
<h1
|
||||
class="font-heading text-2xl md:text-3xl font-bold bg-gradient-to-r from-purple-600 to-purple-800 bg-clip-text text-transparent">
|
||||
SPJ Komite
|
||||
</h1>
|
||||
<p class="text-gray-500 mt-2">SMA Negeri 1 Abiansemal</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form id="loginForm" class="space-y-5">
|
||||
<div class="fade-in fade-in-delay-2">
|
||||
<label for="email" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="user" class="w-4 h-4"></i>
|
||||
User
|
||||
</div>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input type="text" id="email" name="email" required placeholder="Masukkan username"
|
||||
class="input-field w-full px-4 py-3.5 border border-gray-200 rounded-xl outline-none text-gray-700">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fade-in fade-in-delay-3">
|
||||
<label for="password" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="lock" class="w-4 h-4"></i>
|
||||
Password
|
||||
</div>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input type="password" id="password" name="password" required placeholder="••••••••"
|
||||
class="input-field w-full px-4 py-3.5 border border-gray-200 rounded-xl outline-none text-gray-700">
|
||||
<button type="button" id="togglePassword"
|
||||
class="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600">
|
||||
<i data-lucide="eye" class="w-5 h-5" id="eyeIcon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="errorMessage"
|
||||
class="hidden p-4 bg-red-50 border border-red-200 text-red-600 rounded-xl text-sm flex items-center gap-3">
|
||||
<i data-lucide="alert-circle" class="w-5 h-5 flex-shrink-0"></i>
|
||||
<span>User atau password salah. Silakan coba lagi.</span>
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="login-btn w-full py-4 text-white font-semibold rounded-xl flex items-center justify-center gap-2 text-lg fade-in fade-in-delay-4">
|
||||
<i data-lucide="log-in" class="w-5 h-5"></i>
|
||||
<span>Masuk</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Footer -->
|
||||
<p class="text-center text-gray-400 text-xs mt-8 fade-in fade-in-delay-4">
|
||||
© 2026 SPJ Komite - SMA Negeri 1 Abiansemal
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
|
||||
// Demo credentials
|
||||
const DEMO_USERS = [
|
||||
{ email: 'adminkomite', password: '$manab#1', role: 'admin', name: 'Admin Komite' },
|
||||
];
|
||||
|
||||
// Check if already authenticated
|
||||
if (sessionStorage.getItem('isAuthenticated') === 'true') {
|
||||
window.location.href = '/';
|
||||
}
|
||||
|
||||
// Toggle password visibility
|
||||
document.getElementById('togglePassword').addEventListener('click', function () {
|
||||
const passwordInput = document.getElementById('password');
|
||||
const eyeIcon = document.getElementById('eyeIcon');
|
||||
|
||||
if (passwordInput.type === 'password') {
|
||||
passwordInput.type = 'text';
|
||||
eyeIcon.setAttribute('data-lucide', 'eye-off');
|
||||
} else {
|
||||
passwordInput.type = 'password';
|
||||
eyeIcon.setAttribute('data-lucide', 'eye');
|
||||
}
|
||||
lucide.createIcons();
|
||||
});
|
||||
|
||||
// Handle login form submission
|
||||
document.getElementById('loginForm').addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById('email').value.trim();
|
||||
const password = document.getElementById('password').value;
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const submitBtn = this.querySelector('button[type="submit"]');
|
||||
|
||||
errorMessage.classList.add('hidden');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<svg class="animate-spin w-5 h-5" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg><span>Memproses...</span>';
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const user = DEMO_USERS.find(u => u.email === email && u.password === password);
|
||||
|
||||
if (user) {
|
||||
sessionStorage.setItem('isAuthenticated', 'true');
|
||||
sessionStorage.setItem('userRole', user.role);
|
||||
sessionStorage.setItem('userName', user.name);
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
errorMessage.classList.remove('hidden');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = '<i data-lucide="log-in" class="w-5 h-5"></i><span>Masuk</span>';
|
||||
lucide.createIcons();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
3
spj-komite/public/template_kode_kegiatan.csv
Normal file
3
spj-komite/public/template_kode_kegiatan.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
Kode,Uraian
|
||||
400.3.8.1,Kurikulum
|
||||
400.3.8.2,Bahan Ajar
|
||||
|
4
spj-komite/public/template_rks.csv
Normal file
4
spj-komite/public/template_rks.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
Pekerjaan
|
||||
"Pengadaan Alat Tulis Kantor (ATK)"
|
||||
"Pengadaan Bahan Praktik Siswa"
|
||||
"Pengadaan Alat Kebersihan dan Bahan Pembersih"
|
||||
|
1270
spj-komite/server.js
Normal file
1270
spj-komite/server.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user