Synaplan
Open-source AI platform for companies: self-hosted AI agents, GDPR-compliant chatbot widget, RAG knowledge base, WhatsApp and Outlook AI, MCP, multi-model routing. Docker, Kubernetes or hosted in Germ
Absensi selfie, SDM, dan payroll karyawan dalam satu aplikasi.
Translation pending
BusinessTypeScript · JavaScriptGitHub @melansamudraOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Absensi selfie, data karyawan (SDM), dan payroll — produk SaaS berdiri sendiri. Next.js (App Router, TypeScript, Tailwind) + Supabase (Postgres, Auth, Storage), dengan database & backend independen (bukan berbagi database dengan produk lain) supaya bisa dijual ke bisnis mana pun.
src/
app/
login/, signup/, forgot-password/, reset-password/, auth/callback/ auth pemilik/admin bisnis
onboarding/ buat business pertama kali
dashboard/ halaman transit, redirect ke /business/[businessId]
business/[businessId]/(dashboard)/
employees/ CRUD karyawan, tunjangan-tetap/ (tunjangan berulang per karyawan)
attendance/ grid absensi manual harian
shifts/ shift template + jadwal per karyawan per hari
leave-types/, leave-requests/ jenis cuti, pengajuan + approval, sisa kuota
payroll/ daftar slip, rekap pra-slip, detail slip, kasbon/, pinjaman/
settings/ info bisnis, jam kerja, aturan payroll, link absen selfie & link cuti,
late-tiers/ (potongan telat bertingkat), holidays/ (tanggal merah tambahan)
activity-log/ riwayat aksi sensitif (hapus karyawan, slip lunas, cuti disetujui/ditolak, dst.)
absen/[slug]/ halaman publik absen selfie (tanpa login)
cuti/[slug]/ halaman publik pengajuan cuti (tanpa login)
api/attendance-checkin/ API route (service-role) untuk proses selfie check-in
components/
ledger-manager.tsx komponen generik ledger karyawan (dipakai kasbon & pinjaman pribadi)
lib/
supabase/ client.ts (browser), server.ts (RSC/actions), service.ts (service-role), middleware.ts
payroll/ calc.ts (rumus slip), aggregate.ts (rekap absensi), kasbon.ts, personal-loan.ts, payslip-total.ts
email/ resend.ts (client Resend), leave-notifications.ts (email approve/reject cuti, best-effort)
activity-log.ts helper logActivity() — best-effort, dipanggil setelah aksi utama berhasil
types/database.ts tipe hasil `supabase gen types typescript` (ditulis manual, ganti setelah project di-link)
supabase/
migrations/ schema database, urut sesuai modul
npm install
.env.example ke .env.local dan isi NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY / SUPABASE_SERVICE_ROLE_KEY dari Project Settings → API.npx supabase login
npx supabase link --project-ref <project-ref>
npx supabase db push
src/lib/types/database.ts):
npx supabase gen types typescript --project-id <project-ref> > src/lib/types/database.ts
npm run dev
20260909100000_extensions_and_helpers.sql — extension pgcrypto, skema private20260909100100_businesses.sql — businesses, business_staff, helper RLS private.owns_business()20260909100200_employees.sql — data karyawan20260909100300_attendance.sql — absensi (manual + selfie), storage bucket attendance-selfies, RPC publik get_attendance_checkin_info20260909100400_payslips.sql — slip gaji + adjustment (tunjangan/potongan bebas)20260909100500_employee_advances.sql — kasbon (ledger sederhana)20260909100600_payroll_rules.sql — tanggal merah tambahan, skema potongan telat bertingkat20260910100000_leave.sql — modul cuti: leave_types, leave_requests, RPC publik get_leave_request_info/submit_leave_request_public20260910100100_shifts.sql — shift_templates, employee_shift_assignments, kolom attendance.shift_template_id20260910100200_personal_loans.sql — employee_personal_loans, kolom payslips.personal_loan_deduction20260910100300_recurring_allowances.sql — employee_recurring_allowances20260911100000_employee_email.sql — kolom employees.email (opsional, dipakai notifikasi)20260912100000_spam_protection.sql — public_submission_log + cooldown 60 detik di RPC submit_leave_request_public20260912100100_activity_log.sql — activity_log20260920100000_pph21.sql — businesses.pph21_enabled, employees.ptkp_status, kolom payslips.pph21_amount/ptkp_status/ter_category20261008100000_attendance_security.sql — geofence (businesses.office_lat/office_lng/attendance_radius_m), PIN absen (employees.attendance_pin_hash, businesses.attendance_pin_required), bucket attendance-selfies jadi privat20261009100000_bpjs_portal_overtime.sql — BPJS (businesses.bpjs_*, , ), , tabel Setiap tabel dengan business_id diamankan RLS lewat private.owns_business(business_id) — akses hanya untuk businesses milik auth.uid() yang sedang login (owner atau staff aktif). Halaman /absen/[slug], /cuti/[slug], dan API /api/attendance-checkin sengaja tanpa login (karyawan tidak punya akun) — divalidasi manual di server lewat slug + employee id, lewat RPC security definer atau service-role client.
Approval cuti tidak menambah logic payroll baru: saat pengajuan disetujui, server action meng-upsert baris attendance untuk tiap tanggal dalam rentang jadi status='izin' — note diisi nama jenis cuti kalau jenis itu berbayar (masuk jalur "izin berketerangan" = dibayar penuh, logic yang sudah ada), atau dibiarkan kosong kalau tidak berbayar (masuk jalur "izin tanpa keterangan" = dipotong sesuai aturan izin bisnis). attendance.leave_request_id cuma jejak asal-usul, tidak dipakai kalkulasi.
Opsional (butuh setup terpisah) — saat cuti disetujui/ditolak (baik lewat approval maupun input manual admin), ABSENKU mencoba kirim email ke karyawan lewat Resend:
RESEND_API_KEY di .env.local.RESEND_FROM_EMAIL (mis. ABSENKU <[email protected]>). Kalau dikosongkan, dipakai alamat testing bawaan Resend ([email protected]) — cukup untuk uji coba, tapi terbatas untuk pemakaian nyata.RESEND_API_KEY kosong, atau karyawan tidak punya email (kolom opsional di form Karyawan), email cukup dilewati — tidak pernah menggagalkan approve/reject.Diatur di Pengaturan → "Keamanan Absen Selfie" dan di form Karyawan:
src/lib/attendance/pin.ts), tidak pernah dikirim ke browser. Karyawan yang punya PIN wajib mengisinya saat absen; kalau "Wajibkan PIN" dicentang, karyawan tanpa PIN tidak bisa absen selfie sama sekali. Setelah 5 kali salah PIN dalam 15 menit, absen karyawan itu dikunci sementara (dicatat di public_submission_log dengan kind absen_pin_gagal).attendance-selfies tidak lagi publik. Kolom attendance.check_in_photo_url/check_out_photo_url menyimpan path objek, dan dashboard membuat signed URL berumur 1 jam saat halaman dibuka (src/lib/attendance/selfie.ts).Foto selfie absen dihapus permanen (file di bucket + kolom check_in_photo_url/check_out_photo_url) dengan dua aturan:
purgeSelfies di src/lib/attendance/selfie.ts). Jam, lokasi GPS, dan status tetap tersimpan./api/cron/cleanup-selfies (harian, 02.00 WIB). Presensi yang ditolak juga mengikuti batas 30 hari ini supaya fotonya tersedia kalau ada sengketa.Foto lama sebelum fitur ini aktif dibersihkan sekali jalan dengan scripts/purge-old-selfies.mjs (default dry-run, tambahkan --apply untuk menghapus): node --env-file=.env.local scripts/purge-old-selfies.mjs.
Butuh env CRON_SECRET di Vercel (string acak panjang); tanpa itu endpoint cron menolak semua request. Ubah masa simpan lewat SELFIE_RETENTION_DAYS.
Mati secara default per bisnis (Pengaturan → BPJS). Kalau dinyalakan, setiap slip baru menghitung iuran (src/lib/payroll/bpjs.ts):
| Program | Perusahaan | Karyawan (memotong gaji) | Batas upah |
|---|---|---|---|
| BPJS Kesehatan | 4% | 1% | default Rp12 jt (bisa diubah) |
| JHT | 3,7% | 2% | — |
| JP | 2% | 1% | default Rp10.547.400 — perbarui tiap Maret sesuai pengumuman BPJS |
| JKK | 0,24–1,74% (pilih sesuai risiko) | — | — |
| JKM | 0,3% | — | — |
/karyawan/[slug] (slug sama dengan link absen, linknya ada di Pengaturan). Karyawan masuk dengan nama + PIN absen (karyawan tanpa PIN tidak bisa masuk), lalu bisa melihat rekap absensi bulan ini, sisa cuti, slip gaji (12 terakhir, termasuk rincian dan iuran BPJS perusahaan), kasbon & pinjaman pribadi (sisa, potongan yang akan datang, riwayat), dan mengajukan lembur.
src/lib/portal/session.ts). Kunci tanda tangan diturunkan dari SUPABASE_SERVICE_ROLE_KEY, atau dari PORTAL_SESSION_SECRET kalau diisi. Mengganti/menghapus PIN karyawan otomatis membatalkan sesinya.Diatur di Pengaturan → Lembur (src/lib/payroll/overtime.ts):
Kalau Pengaturan → Lembur → "harus diajukan & disetujui" dicentang, absen pulang selfie tidak lagi mengisi jam lembur otomatis. Karyawan mengajukan lembur lewat Portal Karyawan (maks. 31 hari ke belakang, 0,5–12 jam), admin menyetujui/menolak di menu Lembur (jam bisa dikoreksi saat menyetujui). Saat disetujui, jam ditulis ke attendance.overtime_hours tanggal itu dan ikut ke rekap payroll lewat jalur yang sudah ada. Butuh absensi berstatus "hadir" di tanggal tersebut.
/absen/[slug] (API /api/attendance-checkin) dan /cuti/[slug] (RPC submit_leave_request_public) sama-sama dilindungi cooldown sederhana berbasis tabel public_submission_log (bukan infra rate-limit eksternal): absen 5 detik per karyawan, pengajuan cuti 60 detik per karyawan. Cukup untuk mencegah spam-klik/bot dasar — bukan pengganti rate-limiting di level CDN/edge kalau butuh proteksi terhadap volumetric flood sungguhan.
Halaman detail slip punya tombol "Cetak Slip" yang memicu window.print() browser — tidak ada library PDF baru. Layout cetak (payslip-print-view.tsx) disembunyikan di layar dan cuma muncul saat print (Tailwind print: variant), sementara sidebar/nav dashboard disembunyikan sebaliknya lewat print:hidden di dashboard-shell.tsx.
Tombol "Import CSV" di halaman Karyawan menerima file dengan header nama (wajib), tipe_gaji, gaji_harian, gaji_bulanan, divisi, email (semua opsional kecuali nama) — urutan kolom bebas, dicocokkan lewat nama header. Parsing & validasi terjadi di server (import-actions.ts), baris yang gagal validasi dilaporkan per baris tanpa menggagalkan baris lain yang valid.
Mati secara default per bisnis (businesses.pph21_enabled, toggle di Pengaturan). Kalau dinyalakan:
src/lib/payroll/pph21.ts.⚠️ Batasan yang disengaja (baca sebelum dipakai untuk pelaporan pajak sungguhan):
Integrasi akuntansi/jurnal (sengaja tidak dibangun — di luar scope ABSENKU, lihat komentar di supabase/migrations/20260910100200_personal_loans.sql), notifikasi WhatsApp sebagai alternatif email, billing/subscription, undang admin/staff lewat UI (tabel business_staff sudah ada sejak v1, belum ada halamannya), landing page marketing, halaman legal (Syarat & Ketentuan/Privasi).
employees.bpjs_*payslips.bpjs_*businesses.overtime_approval_requiredovertime_requests20261010100000_overtime_rules.sql — aturan lembur per bisnis: minimum, pembulatan, maks. per hari, mode tarif flat/PP 35, hari kerja per minggu