diff --git a/app/api/auth/check/route.ts b/app/api/auth/check/route.ts index 966aff1..eaa1cad 100644 --- a/app/api/auth/check/route.ts +++ b/app/api/auth/check/route.ts @@ -1,10 +1,9 @@ import { NextResponse } from 'next/server'; import { cookies } from 'next/headers'; import { jwtVerify } from 'jose'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET() { - let connection; try { const token = (await (await cookies()).get('token'))?.value; @@ -21,39 +20,29 @@ export async function GET() { new TextEncoder().encode(process.env.JWT_SECRET || 'your-secret-key') ); - // Get connection from pool - connection = await pool.getConnection(); + // Get user data from user_app table + const { data: users, error } = await supabase + .from('user_app') + .select('id_user, nim, username, role') + .eq('id_user', payload.id) + .single(); - // Get user data - const [users]: any = await connection.execute( - 'SELECT id_user, nim, username, role FROM user WHERE id_user = ?', - [payload.id] - ); - - if (users.length === 0) { - connection.release(); + if (error || !users) { return NextResponse.json( { error: 'User not found' }, { status: 404 } ); } - const user = users[0]; - connection.release(); - return NextResponse.json({ user: { - id: user.id_user, - nim: user.nim, - username: user.username, - role: user.role + id: users.id_user, + nim: users.nim, + username: users.username, + role: users.role } }); } catch (error) { - if (connection) { - connection.release(); - } - console.error('Auth check error:', error); return NextResponse.json( { error: 'Unauthorized' }, diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index f90fce0..4b168da 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -1,10 +1,9 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; import bcrypt from 'bcryptjs'; import { SignJWT } from 'jose'; -import { RowDataPacket } from 'mysql2'; -interface User extends RowDataPacket { +interface User { id_user: number; nim: string; username: string; @@ -13,13 +12,23 @@ interface User extends RowDataPacket { } export async function POST(request: Request) { - let connection; try { console.log('Login request received'); // Test database connection first try { - connection = await pool.getConnection(); + const { data: testData, error: testError } = await supabase + .from('user_app') + .select('count') + .limit(1); + + if (testError) { + console.error('Database connection error:', testError); + return NextResponse.json( + { error: 'Tidak dapat terhubung ke database' }, + { status: 500 } + ); + } console.log('Database connection successful'); } catch (dbError) { console.error('Database connection error:', dbError); @@ -48,11 +57,20 @@ export async function POST(request: Request) { console.log('Querying user with NIM:', nim); let users: User[]; try { - const [rows] = await connection.execute( - 'SELECT * FROM user WHERE nim = ?', - [nim] - ); - users = rows; + const { data, error } = await supabase + .from('user_app') + .select('*') + .eq('nim', nim); + + if (error) { + console.error('Database query error:', error); + return NextResponse.json( + { error: 'Terjadi kesalahan saat memeriksa data pengguna' }, + { status: 500 } + ); + } + + users = data || []; console.log('Query result:', users.length > 0 ? 'User found' : 'User not found'); } catch (queryError) { console.error('Database query error:', queryError); @@ -64,7 +82,6 @@ export async function POST(request: Request) { if (users.length === 0) { console.log('No user found with NIM:', nim); - connection.release(); return NextResponse.json( { error: 'NIM atau password salah' }, { status: 401 } @@ -95,7 +112,6 @@ export async function POST(request: Request) { if (!isPasswordValid) { console.log('Invalid password for user:', nim); - connection.release(); return NextResponse.json( { error: 'NIM atau password salah' }, { status: 401 } @@ -142,14 +158,9 @@ export async function POST(request: Request) { }); console.log('Cookie set'); - connection.release(); console.log('Login process completed successfully'); return response; } catch (error) { - if (connection) { - connection.release(); - } - console.error('Login error details:', error); if (error instanceof Error) { console.error('Error message:', error.message); diff --git a/app/api/auth/register/route.ts b/app/api/auth/register/route.ts index 667125f..cb33cb0 100644 --- a/app/api/auth/register/route.ts +++ b/app/api/auth/register/route.ts @@ -1,9 +1,8 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; import bcrypt from 'bcryptjs'; export async function POST(request: Request) { - let connection; try { const { username, nim, password } = await request.json(); @@ -23,31 +22,28 @@ export async function POST(request: Request) { ); } - // Get connection from pool - connection = await pool.getConnection(); - // Check if NIM exists in mahasiswa table - const [mahasiswa]: any = await connection.execute( - 'SELECT * FROM mahasiswa WHERE nim = ?', - [nim] - ); + const { data: mahasiswa, error: mahasiswaError } = await supabase + .from('mahasiswa') + .select('nim') + .eq('nim', nim) + .single(); - if (mahasiswa.length === 0) { - connection.release(); + if (mahasiswaError || !mahasiswa) { return NextResponse.json( { error: 'NIM tidak terdaftar sebagai mahasiswa' }, { status: 400 } ); } - // Check if NIM already exists in user table - const [existingUsers]: any = await connection.execute( - 'SELECT * FROM user WHERE nim = ?', - [nim] - ); + // Check if NIM already exists in user_app table + const { data: existingUsers, error: userError } = await supabase + .from('user_app') + .select('nim') + .eq('nim', nim) + .single(); - if (existingUsers.length > 0) { - connection.release(); + if (!userError && existingUsers) { return NextResponse.json( { error: 'NIM sudah terdaftar sebagai pengguna' }, { status: 400 } @@ -58,22 +54,30 @@ export async function POST(request: Request) { const hashedPassword = await bcrypt.hash(password, 10); // Insert new user - await connection.execute( - 'INSERT INTO user (nim, username, password, role, created_at, updated_at) VALUES (?, ?, ?, ?, NOW(), NOW())', - [nim, username, hashedPassword, 'mahasiswa'] - ); + const { data: newUser, error: insertError } = await supabase + .from('user_app') + .insert({ + nim: nim, + username: username, + password: hashedPassword, + role: 'mahasiswa' + }) + .select() + .single(); - connection.release(); + if (insertError) { + console.error('Insert error:', insertError); + return NextResponse.json( + { error: 'Terjadi kesalahan saat registrasi' }, + { status: 500 } + ); + } return NextResponse.json( { message: 'Registrasi berhasil' }, { status: 201 } ); } catch (error) { - if (connection) { - connection.release(); - } - console.error('Registration error:', error); return NextResponse.json( { error: 'Terjadi kesalahan saat registrasi' }, diff --git a/app/api/mahasiswa/asal-daerah-angkatan/route.ts b/app/api/mahasiswa/asal-daerah-angkatan/route.ts index bad666e..adf83a0 100644 --- a/app/api/mahasiswa/asal-daerah-angkatan/route.ts +++ b/app/api/mahasiswa/asal-daerah-angkatan/route.ts @@ -1,8 +1,7 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; +import supabase from '@/lib/db'; -interface AsalDaerah extends RowDataPacket { +interface AsalDaerah { kabupaten: string; jumlah: number; } @@ -18,18 +17,46 @@ export async function GET(request: Request) { ); } - const connection = await pool.getConnection(); - try { - const query = ` - SELECT kabupaten, COUNT(*) AS jumlah - FROM mahasiswa - WHERE tahun_angkatan = ? - GROUP BY kabupaten - ORDER BY jumlah DESC, kabupaten ASC - `; + const { data, error } = await supabase + .from('mahasiswa') + .select('kabupaten') + .eq('tahun_angkatan', parseInt(tahunAngkatan)); - const [results] = await connection.query(query, [tahunAngkatan]); + if (error) { + console.error('Error fetching asal daerah per angkatan:', error); + return NextResponse.json( + { error: 'Failed to fetch asal daerah data' }, + { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } + + // Group by kabupaten and count + const groupedData = data.reduce((acc, item) => { + acc[item.kabupaten] = (acc[item.kabupaten] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to final format and sort + const results: AsalDaerah[] = Object.entries(groupedData) + .map(([kabupaten, jumlah]) => ({ + kabupaten, + jumlah + })) + .sort((a, b) => { + // Sort by jumlah DESC, then by kabupaten ASC + if (a.jumlah !== b.jumlah) { + return b.jumlah - a.jumlah; + } + return a.kabupaten.localeCompare(b.kabupaten); + }); return NextResponse.json(results, { headers: { @@ -52,7 +79,5 @@ export async function GET(request: Request) { }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/app/api/mahasiswa/asal-daerah-beasiswa/route.ts b/app/api/mahasiswa/asal-daerah-beasiswa/route.ts index aa31de3..f02afbf 100644 --- a/app/api/mahasiswa/asal-daerah-beasiswa/route.ts +++ b/app/api/mahasiswa/asal-daerah-beasiswa/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET(request: Request) { try { @@ -7,37 +7,66 @@ export async function GET(request: Request) { const tahunAngkatan = searchParams.get('tahunAngkatan'); const jenisBeasiswa = searchParams.get('jenisBeasiswa'); - let query = ` - SELECT - m.tahun_angkatan, - m.kabupaten, - COUNT(m.nim) AS jumlah_mahasiswa - FROM - mahasiswa m - JOIN - beasiswa_mahasiswa s ON m.nim = s.nim - WHERE - s.jenis_beasiswa = ? - `; + let query = supabase + .from('mahasiswa') + .select(` + tahun_angkatan, + kabupaten, + beasiswa_mahasiswa!inner( + jenis_beasiswa + ) + `) + .eq('beasiswa_mahasiswa.jenis_beasiswa', jenisBeasiswa); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = ?`; + query = query.eq('tahun_angkatan', tahunAngkatan); } - query += ` - GROUP BY - m.kabupaten, m.tahun_angkatan - ORDER BY - m.tahun_angkatan ASC, m.kabupaten - `; + const { data, error } = await query; - const params = [jenisBeasiswa]; - if (tahunAngkatan && tahunAngkatan !== 'all') { - params.push(tahunAngkatan); + if (error) { + console.error('Supabase error:', error); + return NextResponse.json( + { error: 'Database error' }, + { status: 500 } + ); } - const [rows] = await pool.query(query, params); - return NextResponse.json(rows); + // Group and count the data in JavaScript + const groupedData = data.reduce((acc: any[], row: any) => { + const tahunAngkatanValue = row.tahun_angkatan; + const kabupaten = row.kabupaten; + + if (!kabupaten) return acc; + + const existingGroup = acc.find( + (item: any) => + item.tahun_angkatan === tahunAngkatanValue && + item.kabupaten === kabupaten + ); + + if (existingGroup) { + existingGroup.jumlah_mahasiswa++; + } else { + acc.push({ + tahun_angkatan: tahunAngkatanValue, + kabupaten: kabupaten, + jumlah_mahasiswa: 1 + }); + } + + return acc; + }, []); + + // Sort the results + const sortedData = groupedData.sort((a: any, b: any) => { + if (a.tahun_angkatan !== b.tahun_angkatan) { + return a.tahun_angkatan - b.tahun_angkatan; + } + return a.kabupaten.localeCompare(b.kabupaten); + }); + + return NextResponse.json(sortedData); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/asal-daerah-lulus/route.ts b/app/api/mahasiswa/asal-daerah-lulus/route.ts index aec3c96..7ec9286 100644 --- a/app/api/mahasiswa/asal-daerah-lulus/route.ts +++ b/app/api/mahasiswa/asal-daerah-lulus/route.ts @@ -1,38 +1,65 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; + +interface AsalDaerahLulus { + tahun_angkatan: number; + kabupaten: string; + jumlah_lulus_tepat_waktu: number; +} export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const tahunAngkatan = searchParams.get('tahunAngkatan'); - let query = ` - SELECT - m.tahun_angkatan, - m.kabupaten, - COUNT(m.nim) AS jumlah_lulus_tepat_waktu - FROM - mahasiswa m - JOIN - status_mahasiswa s ON m.nim = s.nim - WHERE - s.status_kuliah = 'Lulus' - AND s.semester <= 8 - `; + let query = supabase + .from('status_mahasiswa') + .select('semester, mahasiswa!inner(tahun_angkatan, kabupaten, nim)') + .eq('status_kuliah', 'Lulus') + .lte('semester', 8); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = '${tahunAngkatan}'`; + query = query.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan)); } - query += ` - GROUP BY - m.tahun_angkatan, m.kabupaten - ORDER BY - m.tahun_angkatan DESC, jumlah_lulus_tepat_waktu DESC - `; + const { data, error } = await query; - const [rows] = await pool.query(query); - return NextResponse.json(rows); + if (error) { + console.error('Error fetching asal daerah lulus data:', error); + return NextResponse.json( + { error: 'Failed to fetch asal daerah lulus data' }, + { status: 500 } + ); + } + + // Group by tahun_angkatan and kabupaten + const groupedData = data.reduce((acc, item: any) => { + const tahun_angkatan = item.mahasiswa.tahun_angkatan; + const kabupaten = item.mahasiswa.kabupaten; + const key = `${tahun_angkatan}-${kabupaten}`; + acc[key] = (acc[key] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to final format and sort + const results: AsalDaerahLulus[] = Object.entries(groupedData) + .map(([key, jumlah_lulus_tepat_waktu]) => { + const [tahun_angkatan, kabupaten] = key.split('-'); + return { + tahun_angkatan: parseInt(tahun_angkatan), + kabupaten, + jumlah_lulus_tepat_waktu + }; + }) + .sort((a, b) => { + // Sort by tahun_angkatan DESC, jumlah_lulus_tepat_waktu DESC + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return b.jumlah_lulus_tepat_waktu - a.jumlah_lulus_tepat_waktu; + }); + + return NextResponse.json(results); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/asal-daerah-prestasi/route.ts b/app/api/mahasiswa/asal-daerah-prestasi/route.ts index 11ddb75..6cb26b2 100644 --- a/app/api/mahasiswa/asal-daerah-prestasi/route.ts +++ b/app/api/mahasiswa/asal-daerah-prestasi/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET(request: Request) { try { @@ -7,37 +7,66 @@ export async function GET(request: Request) { const tahunAngkatan = searchParams.get('tahunAngkatan'); const jenisPrestasi = searchParams.get('jenisPrestasi'); - let query = ` - SELECT - m.tahun_angkatan, - m.kabupaten, - COUNT(m.nim) AS asal_daerah_mahasiswa_prestasi - FROM - mahasiswa m - JOIN - prestasi_mahasiswa s ON m.nim = s.nim - WHERE - s.jenis_prestasi = ? - `; + let query = supabase + .from('mahasiswa') + .select(` + tahun_angkatan, + kabupaten, + prestasi_mahasiswa!inner( + jenis_prestasi + ) + `) + .eq('prestasi_mahasiswa.jenis_prestasi', jenisPrestasi); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = ?`; + query = query.eq('tahun_angkatan', tahunAngkatan); } - query += ` - GROUP BY - m.tahun_angkatan, m.kabupaten - ORDER BY - m.tahun_angkatan DESC, m.kabupaten - `; + const { data, error } = await query; - const params = [jenisPrestasi]; - if (tahunAngkatan && tahunAngkatan !== 'all') { - params.push(tahunAngkatan); + if (error) { + console.error('Supabase error:', error); + return NextResponse.json( + { error: 'Database error' }, + { status: 500 } + ); } - const [rows] = await pool.query(query, params); - return NextResponse.json(rows); + // Group and count the data in JavaScript + const groupedData = data.reduce((acc: any[], row: any) => { + const tahunAngkatanValue = row.tahun_angkatan; + const kabupaten = row.kabupaten; + + if (!tahunAngkatanValue || !kabupaten) return acc; + + const existingGroup = acc.find( + (item: any) => + item.tahun_angkatan === tahunAngkatanValue && + item.kabupaten === kabupaten + ); + + if (existingGroup) { + existingGroup.asal_daerah_mahasiswa_prestasi++; + } else { + acc.push({ + tahun_angkatan: tahunAngkatanValue, + kabupaten: kabupaten, + asal_daerah_mahasiswa_prestasi: 1 + }); + } + + return acc; + }, []); + + // Sort the results + const sortedData = groupedData.sort((a: any, b: any) => { + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return a.kabupaten.localeCompare(b.kabupaten); + }); + + return NextResponse.json(sortedData); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/asal-daerah-status/route.ts b/app/api/mahasiswa/asal-daerah-status/route.ts index 6c938e4..f246ac3 100644 --- a/app/api/mahasiswa/asal-daerah-status/route.ts +++ b/app/api/mahasiswa/asal-daerah-status/route.ts @@ -1,8 +1,7 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; +import supabase from '@/lib/db'; -interface AsalDaerahStatus extends RowDataPacket { +interface AsalDaerahStatus { kabupaten: string; tahun_angkatan?: number; status_kuliah: string; @@ -13,39 +12,78 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url); const tahunAngkatan = searchParams.get('tahun_angkatan'); const statusKuliah = searchParams.get('status_kuliah'); - - const connection = await pool.getConnection(); - - try { - let query = ` - SELECT - m.kabupaten, - ${tahunAngkatan && tahunAngkatan !== 'all' ? 'm.tahun_angkatan,' : ''} - s.status_kuliah, - COUNT(m.nim) AS total_mahasiswa - FROM - mahasiswa m - JOIN - status_mahasiswa s ON m.nim = s.nim - WHERE - s.status_kuliah = ? - `; - const params: any[] = [statusKuliah]; + try { + let query = supabase + .from('status_mahasiswa') + .select('status_kuliah, mahasiswa!inner(kabupaten, tahun_angkatan, nim)') + .eq('status_kuliah', statusKuliah); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = ?`; - params.push(tahunAngkatan); + query = query.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan)); } - query += ` - GROUP BY - m.kabupaten${tahunAngkatan && tahunAngkatan !== 'all' ? ', m.tahun_angkatan' : ''}, s.status_kuliah - ORDER BY - ${tahunAngkatan && tahunAngkatan !== 'all' ? 'm.tahun_angkatan ASC,' : ''} m.kabupaten, s.status_kuliah - `; + const { data, error } = await query; - const [results] = await connection.query(query, params); + if (error) { + console.error('Error fetching asal daerah status:', error); + return NextResponse.json( + { error: 'Failed to fetch asal daerah status data' }, + { + status: 500, + headers: { + 'Cache-Control': 'public, max-age=60, stale-while-revalidate=30', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } + + // Group by kabupaten, tahun_angkatan (optional), status_kuliah + const groupedData = data.reduce((acc, item: any) => { + const kabupaten = item.mahasiswa.kabupaten; + const tahun_angkatan = tahunAngkatan && tahunAngkatan !== 'all' ? item.mahasiswa.tahun_angkatan : undefined; + const status_kuliah = item.status_kuliah; + const key = tahun_angkatan !== undefined + ? `${kabupaten}-${tahun_angkatan}-${status_kuliah}` + : `${kabupaten}-${status_kuliah}`; + acc[key] = (acc[key] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to final format and sort + const results: AsalDaerahStatus[] = Object.entries(groupedData) + .map(([key, total_mahasiswa]) => { + const parts = key.split('-'); + if (tahunAngkatan && tahunAngkatan !== 'all') { + const [kabupaten, tahun_angkatan, status_kuliah] = parts; + return { + kabupaten, + tahun_angkatan: parseInt(tahun_angkatan), + status_kuliah, + total_mahasiswa + }; + } else { + const [kabupaten, status_kuliah] = parts; + return { + kabupaten, + status_kuliah, + total_mahasiswa + }; + } + }) + .sort((a, b) => { + // Sort by tahun_angkatan ASC (if exists), kabupaten ASC, status_kuliah ASC + if (a.tahun_angkatan !== undefined && b.tahun_angkatan !== undefined && a.tahun_angkatan !== b.tahun_angkatan) { + return a.tahun_angkatan - b.tahun_angkatan; + } + if (a.kabupaten !== b.kabupaten) { + return a.kabupaten.localeCompare(b.kabupaten); + } + return a.status_kuliah.localeCompare(b.status_kuliah); + }); return NextResponse.json(results, { headers: { @@ -59,16 +97,15 @@ export async function GET(request: Request) { console.error('Error fetching asal daerah status:', error); return NextResponse.json( { error: 'Failed to fetch asal daerah status data' }, - { + { status: 500, headers: { + 'Cache-Control': 'public, max-age=60, stale-while-revalidate=30', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/app/api/mahasiswa/asal-daerah/route.ts b/app/api/mahasiswa/asal-daerah/route.ts index 0639fd4..d4414f0 100644 --- a/app/api/mahasiswa/asal-daerah/route.ts +++ b/app/api/mahasiswa/asal-daerah/route.ts @@ -1,22 +1,45 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; +import supabase from '@/lib/db'; -interface AsalDaerah extends RowDataPacket { +interface AsalDaerah { kabupaten: string; jumlah: number; } export async function GET() { - const connection = await pool.getConnection(); - try { - const [results] = await connection.query(` - SELECT kabupaten, COUNT(*) AS jumlah - FROM mahasiswa - GROUP BY kabupaten - ORDER BY kabupaten ASC - `); + const { data, error } = await supabase + .from('mahasiswa') + .select('kabupaten') + .order('kabupaten', { ascending: true }); + + if (error) { + console.error('Error fetching asal daerah:', error); + return NextResponse.json( + { error: 'Failed to fetch asal daerah data' }, + { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } + + // Group by kabupaten and count + const groupedData = data.reduce((acc, item) => { + const kabupaten = item.kabupaten; + acc[kabupaten] = (acc[kabupaten] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to array format + const results: AsalDaerah[] = Object.entries(groupedData).map(([kabupaten, jumlah]) => ({ + kabupaten, + jumlah + })); return NextResponse.json(results, { headers: { @@ -39,7 +62,5 @@ export async function GET() { }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/app/api/mahasiswa/gender-per-angkatan/route.ts b/app/api/mahasiswa/gender-per-angkatan/route.ts index 9e55444..f1437ec 100644 --- a/app/api/mahasiswa/gender-per-angkatan/route.ts +++ b/app/api/mahasiswa/gender-per-angkatan/route.ts @@ -1,5 +1,11 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; + +interface GenderData { + tahun_angkatan: number; + jk: string; + jumlah: number; +} export async function GET(request: Request) { const { searchParams } = new URL(request.url); @@ -12,15 +18,39 @@ export async function GET(request: Request) { ); } - const connection = await pool.getConnection(); - try { - const [results] = await connection.query(` - SELECT tahun_angkatan, jk, COUNT(*) AS jumlah - FROM mahasiswa - WHERE tahun_angkatan = ? - GROUP BY jk - `, [tahun]); + const { data, error } = await supabase + .from('mahasiswa') + .select('tahun_angkatan, jk') + .eq('tahun_angkatan', parseInt(tahun)); + + if (error) { + console.error('Error fetching gender per angkatan:', error); + return NextResponse.json( + { error: 'Failed to fetch gender per angkatan data' }, + { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } + + // Group by jk and count + const groupedData = data.reduce((acc, item) => { + acc[item.jk] = (acc[item.jk] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to final format + const results: GenderData[] = Object.entries(groupedData).map(([jk, jumlah]) => ({ + tahun_angkatan: parseInt(tahun), + jk, + jumlah + })); return NextResponse.json(results, { headers: { @@ -43,7 +73,5 @@ export async function GET(request: Request) { }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/app/api/mahasiswa/ipk-beasiswa/route.ts b/app/api/mahasiswa/ipk-beasiswa/route.ts index d462ba6..6b36e6d 100644 --- a/app/api/mahasiswa/ipk-beasiswa/route.ts +++ b/app/api/mahasiswa/ipk-beasiswa/route.ts @@ -1,30 +1,66 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const jenisBeasiswa = searchParams.get('jenisBeasiswa'); - const query = ` - SELECT - m.tahun_angkatan, - COUNT(m.nim) AS total_mahasiswa_beasiswa, - ROUND(AVG(m.ipk), 2) AS rata_rata_ipk - FROM - mahasiswa m - JOIN - beasiswa_mahasiswa s ON m.nim = s.nim - WHERE - s.jenis_beasiswa = ? - GROUP BY - m.tahun_angkatan - ORDER BY - m.tahun_angkatan ASC - `; + const { data, error } = await supabase + .from('mahasiswa') + .select(` + tahun_angkatan, + ipk, + beasiswa_mahasiswa!inner( + jenis_beasiswa + ) + `) + .eq('beasiswa_mahasiswa.jenis_beasiswa', jenisBeasiswa); - const [rows] = await pool.query(query, [jenisBeasiswa]); - return NextResponse.json(rows); + if (error) { + console.error('Supabase error:', error); + return NextResponse.json( + { error: 'Database error' }, + { status: 500 } + ); + } + + // Group and calculate statistics in JavaScript + const groupedData = data.reduce((acc: any[], row: any) => { + const tahunAngkatan = row.tahun_angkatan; + const ipk = row.ipk; + + if (!ipk) return acc; + + const existingGroup = acc.find( + (item: any) => item.tahun_angkatan === tahunAngkatan + ); + + if (existingGroup) { + existingGroup.total_mahasiswa_beasiswa++; + existingGroup.total_ipk += ipk; + } else { + acc.push({ + tahun_angkatan: tahunAngkatan, + total_mahasiswa_beasiswa: 1, + total_ipk: ipk + }); + } + + return acc; + }, []); + + // Calculate average IPK and format the results + const result = groupedData.map((group: any) => ({ + tahun_angkatan: group.tahun_angkatan, + total_mahasiswa_beasiswa: group.total_mahasiswa_beasiswa, + rata_rata_ipk: Math.round((group.total_ipk / group.total_mahasiswa_beasiswa) * 100) / 100 + })); + + // Sort by tahun_angkatan ascending + const sortedData = result.sort((a: any, b: any) => a.tahun_angkatan - b.tahun_angkatan); + + return NextResponse.json(sortedData); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/ipk-jenis-kelamin/route.ts b/app/api/mahasiswa/ipk-jenis-kelamin/route.ts deleted file mode 100644 index 633f101..0000000 --- a/app/api/mahasiswa/ipk-jenis-kelamin/route.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; - -export async function GET(request: Request) { - const { searchParams } = new URL(request.url); - const tahunAngkatan = searchParams.get('tahun_angkatan'); - - if (!tahunAngkatan) { - return NextResponse.json( - { error: 'Tahun angkatan diperlukan' }, - { status: 400 } - ); - } - - const connection = await pool.getConnection(); - - try { - const query = ` - SELECT - jk, - ROUND(AVG(ipk), 2) as rata_rata_ipk - FROM mahasiswa - WHERE tahun_angkatan = ? - GROUP BY jk - ORDER BY jk ASC - `; - - const [results] = await connection.query(query, [tahunAngkatan]); - - return NextResponse.json(results, { - headers: { - 'Cache-Control': 'public, max-age=60, stale-while-revalidate=30', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', - }, - }); - } catch (error) { - console.error('Error fetching IPK data:', error); - return NextResponse.json( - { error: 'Failed to fetch IPK data' }, - { - status: 500, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', - }, - } - ); - } finally { - connection.release(); - } -} \ No newline at end of file diff --git a/app/api/mahasiswa/ipk-lulus-tepat/route.ts b/app/api/mahasiswa/ipk-lulus-tepat/route.ts index 6d46724..5a095b5 100644 --- a/app/api/mahasiswa/ipk-lulus-tepat/route.ts +++ b/app/api/mahasiswa/ipk-lulus-tepat/route.ts @@ -1,37 +1,67 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; + +interface IpkLulusTepat { + tahun_angkatan: number; + rata_rata_ipk: number; +} export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const tahunAngkatan = searchParams.get('tahunAngkatan'); - let query = ` - SELECT - m.tahun_angkatan, - ROUND(AVG(m.ipk), 2) AS rata_rata_ipk - FROM - mahasiswa m - JOIN - status_mahasiswa s ON m.nim = s.nim - WHERE - s.status_kuliah = 'Lulus' - AND s.semester <= 8 - `; + let query = supabase + .from('status_mahasiswa') + .select('semester, mahasiswa!inner(tahun_angkatan, ipk)') + .eq('status_kuliah', 'Lulus') + .lte('semester', 8) + .not('mahasiswa.ipk', 'is', null); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = '${tahunAngkatan}'`; + query = query.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan)); } - query += ` - GROUP BY - m.tahun_angkatan - ORDER BY - m.tahun_angkatan ASC - `; + const { data, error } = await query; - const [rows] = await pool.query(query); - return NextResponse.json(rows); + if (error) { + console.error('Error fetching IPK lulus tepat data:', error); + return NextResponse.json( + { error: 'Failed to fetch IPK lulus tepat data' }, + { status: 500 } + ); + } + + // Group by tahun_angkatan and calculate average IPK + const groupedData = data.reduce((acc, item: any) => { + const tahun_angkatan = item.mahasiswa.tahun_angkatan; + const ipk = item.mahasiswa.ipk; + + if (!acc[tahun_angkatan]) { + acc[tahun_angkatan] = { + total_ipk: 0, + count: 0 + }; + } + + acc[tahun_angkatan].total_ipk += ipk; + acc[tahun_angkatan].count += 1; + + return acc; + }, {} as Record); + + // Convert to final format and sort + const results: IpkLulusTepat[] = Object.entries(groupedData) + .map(([tahun_angkatan, data]) => ({ + tahun_angkatan: parseInt(tahun_angkatan), + rata_rata_ipk: Math.round((data.total_ipk / data.count) * 100) / 100 + })) + .sort((a, b) => { + // Sort by tahun_angkatan ASC + return a.tahun_angkatan - b.tahun_angkatan; + }); + + return NextResponse.json(results); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/ipk-prestasi/route.ts b/app/api/mahasiswa/ipk-prestasi/route.ts index d5809d9..2941546 100644 --- a/app/api/mahasiswa/ipk-prestasi/route.ts +++ b/app/api/mahasiswa/ipk-prestasi/route.ts @@ -1,30 +1,66 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const jenisPrestasi = searchParams.get('jenisPrestasi'); - const query = ` - SELECT - m.tahun_angkatan, - COUNT(m.nim) AS total_mahasiswa_prestasi, - ROUND(AVG(m.ipk), 2) AS rata_rata_ipk - FROM - mahasiswa m - JOIN - prestasi_mahasiswa s ON m.nim = s.nim - WHERE - s.jenis_prestasi = ? - GROUP BY - m.tahun_angkatan - ORDER BY - m.tahun_angkatan ASC - `; + const { data, error } = await supabase + .from('mahasiswa') + .select(` + tahun_angkatan, + ipk, + prestasi_mahasiswa!inner( + jenis_prestasi + ) + `) + .eq('prestasi_mahasiswa.jenis_prestasi', jenisPrestasi); - const [rows] = await pool.query(query, [jenisPrestasi]); - return NextResponse.json(rows); + if (error) { + console.error('Supabase error:', error); + return NextResponse.json( + { error: 'Database error' }, + { status: 500 } + ); + } + + // Group and calculate statistics in JavaScript + const groupedData = data.reduce((acc: any[], row: any) => { + const tahunAngkatan = row.tahun_angkatan; + const ipk = row.ipk; + + if (!ipk) return acc; + + const existingGroup = acc.find( + (item: any) => item.tahun_angkatan === tahunAngkatan + ); + + if (existingGroup) { + existingGroup.total_mahasiswa_prestasi++; + existingGroup.total_ipk += ipk; + } else { + acc.push({ + tahun_angkatan: tahunAngkatan, + total_mahasiswa_prestasi: 1, + total_ipk: ipk + }); + } + + return acc; + }, []); + + // Calculate average IPK and format the results + const result = groupedData.map((group: any) => ({ + tahun_angkatan: group.tahun_angkatan, + total_mahasiswa_prestasi: group.total_mahasiswa_prestasi, + rata_rata_ipk: Math.round((group.total_ipk / group.total_mahasiswa_prestasi) * 100) / 100 + })); + + // Sort by tahun_angkatan ascending + const sortedData = result.sort((a: any, b: any) => a.tahun_angkatan - b.tahun_angkatan); + + return NextResponse.json(sortedData); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/ipk-status/route.ts b/app/api/mahasiswa/ipk-status/route.ts index 1254066..4dfc455 100644 --- a/app/api/mahasiswa/ipk-status/route.ts +++ b/app/api/mahasiswa/ipk-status/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; interface IpkStatus { tahun_angkatan: number; @@ -22,37 +22,65 @@ export async function GET(request: Request) { ); } - let query = ` - SELECT - m.tahun_angkatan, - s.status_kuliah, - COUNT(m.nim) AS total_mahasiswa, - ROUND(AVG(m.ipk), 2) AS rata_rata_ipk - FROM - mahasiswa m - JOIN - status_mahasiswa s ON m.nim = s.nim - WHERE - s.status_kuliah = ? - `; - - const params: any[] = [statusKuliah]; + let query = supabase + .from('status_mahasiswa') + .select('status_kuliah, mahasiswa!inner(tahun_angkatan, ipk, nim)') + .eq('status_kuliah', statusKuliah) + .not('mahasiswa.ipk', 'is', null); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ' AND m.tahun_angkatan = ?'; - params.push(tahunAngkatan); + query = query.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan)); } - query += ` - GROUP BY - m.tahun_angkatan, s.status_kuliah - ORDER BY - m.tahun_angkatan DESC, s.status_kuliah - `; + const { data, error } = await query; - const [rows] = await pool.query(query, params); + if (error) { + console.error('Error fetching IPK status data:', error); + return NextResponse.json( + { error: 'Failed to fetch IPK status data' }, + { status: 500 } + ); + } - return NextResponse.json(rows); + // Group by tahun_angkatan and status_kuliah + const groupedData = data.reduce((acc, item: any) => { + const tahun_angkatan = item.mahasiswa.tahun_angkatan; + const status_kuliah = item.status_kuliah; + const ipk = item.mahasiswa.ipk; + const key = `${tahun_angkatan}-${status_kuliah}`; + + if (!acc[key]) { + acc[key] = { + tahun_angkatan, + status_kuliah, + total_mahasiswa: 0, + total_ipk: 0 + }; + } + + acc[key].total_mahasiswa += 1; + acc[key].total_ipk += ipk; + + return acc; + }, {} as Record); + + // Convert to final format and calculate average IPK + const results: IpkStatus[] = Object.values(groupedData) + .map(item => ({ + tahun_angkatan: item.tahun_angkatan, + status_kuliah: item.status_kuliah, + total_mahasiswa: item.total_mahasiswa, + rata_rata_ipk: Math.round((item.total_ipk / item.total_mahasiswa) * 100) / 100 + })) + .sort((a, b) => { + // Sort by tahun_angkatan DESC, status_kuliah ASC + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return a.status_kuliah.localeCompare(b.status_kuliah); + }); + + return NextResponse.json(results); } catch (error) { console.error('Error in ipk-status route:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/ipk/route.ts b/app/api/mahasiswa/ipk/route.ts index c2a977e..91a3852 100644 --- a/app/api/mahasiswa/ipk/route.ts +++ b/app/api/mahasiswa/ipk/route.ts @@ -1,21 +1,49 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; +import supabase from '@/lib/db'; -interface IPKData extends RowDataPacket { +interface IPKData { tahun_angkatan: number; rata_rata_ipk: number; } export async function GET() { - const connection = await pool.getConnection(); - try { - const [results] = await connection.query(` - SELECT tahun_angkatan, ROUND(AVG(ipk), 2) AS rata_rata_ipk - FROM mahasiswa - GROUP BY tahun_angkatan - `); + const { data, error } = await supabase + .from('mahasiswa') + .select('tahun_angkatan, ipk') + .not('ipk', 'is', null); + + if (error) { + console.error('Error fetching IPK data:', error); + return NextResponse.json( + { error: 'Failed to fetch IPK data' }, + { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } + + // Group by tahun_angkatan and calculate average IPK + const groupedData = data.reduce((acc, item) => { + const tahun = item.tahun_angkatan; + if (!acc[tahun]) { + acc[tahun] = { sum: 0, count: 0 }; + } + acc[tahun].sum += item.ipk || 0; + acc[tahun].count += 1; + return acc; + }, {} as Record); + + // Convert to final format + const results: IPKData[] = Object.entries(groupedData).map(([tahun, data]) => ({ + tahun_angkatan: parseInt(tahun), + rata_rata_ipk: Math.round((data.sum / data.count) * 100) / 100 + })); return NextResponse.json(results, { headers: { @@ -38,7 +66,5 @@ export async function GET() { }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/app/api/mahasiswa/jenis-beasiswa/route.ts b/app/api/mahasiswa/jenis-beasiswa/route.ts index 9899235..fb2cf45 100644 --- a/app/api/mahasiswa/jenis-beasiswa/route.ts +++ b/app/api/mahasiswa/jenis-beasiswa/route.ts @@ -1,15 +1,26 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET() { try { - const [rows] = await pool.query(` - SELECT DISTINCT jenis_beasiswa - FROM beasiswa_mahasiswa - ORDER BY jenis_beasiswa ASC - `); + const { data, error } = await supabase + .from('mahasiswa') + .select('jenis_beasiswa') + .not('jenis_beasiswa', 'is', null) + .order('jenis_beasiswa', { ascending: true }); - return NextResponse.json(rows); + if (error) { + console.error('Error fetching data:', error); + return NextResponse.json( + { error: 'Internal Server Error' }, + { status: 500 } + ); + } + + // Get unique jenis_beasiswa values + const uniqueBeasiswa = [...new Set(data.map(item => item.jenis_beasiswa))]; + + return NextResponse.json(uniqueBeasiswa); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/jenis-pendaftaran-beasiswa/route.ts b/app/api/mahasiswa/jenis-pendaftaran-beasiswa/route.ts index 468278b..ff4e9b7 100644 --- a/app/api/mahasiswa/jenis-pendaftaran-beasiswa/route.ts +++ b/app/api/mahasiswa/jenis-pendaftaran-beasiswa/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET(request: Request) { try { @@ -7,37 +7,66 @@ export async function GET(request: Request) { const tahunAngkatan = searchParams.get('tahunAngkatan'); const jenisBeasiswa = searchParams.get('jenisBeasiswa'); - let query = ` - SELECT - m.tahun_angkatan, - m.jenis_pendaftaran, - COUNT(m.nim) AS jumlah_mahasiswa_beasiswa - FROM - mahasiswa m - JOIN - beasiswa_mahasiswa s ON m.nim = s.nim - WHERE - s.jenis_beasiswa = ? - `; + let query = supabase + .from('beasiswa_mahasiswa') + .select(` + jenis_pendaftaran, + jenis_beasiswa, + mahasiswa!inner( + tahun_angkatan + ) + `) + .eq('jenis_beasiswa', jenisBeasiswa); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = ?`; + query = query.eq('mahasiswa.tahun_angkatan', tahunAngkatan); } - query += ` - GROUP BY - m.tahun_angkatan, m.jenis_pendaftaran - ORDER BY - m.tahun_angkatan DESC, m.jenis_pendaftaran - `; + const { data, error } = await query; - const params = [jenisBeasiswa]; - if (tahunAngkatan && tahunAngkatan !== 'all') { - params.push(tahunAngkatan); + if (error) { + console.error('Supabase error:', error); + return NextResponse.json( + { error: 'Database error' }, + { status: 500 } + ); } - const [rows] = await pool.query(query, params); - return NextResponse.json(rows); + // Group and count the data in JavaScript + const groupedData = data.reduce((acc: any[], row: any) => { + const tahunAngkatanValue = row.mahasiswa?.tahun_angkatan; + const jenisPendaftaran = row.jenis_pendaftaran; + + if (!jenisPendaftaran || !tahunAngkatanValue) return acc; + + const existingGroup = acc.find( + (item: any) => + item.tahun_angkatan === tahunAngkatanValue && + item.jenis_pendaftaran === jenisPendaftaran + ); + + if (existingGroup) { + existingGroup.jumlah_mahasiswa_beasiswa++; + } else { + acc.push({ + tahun_angkatan: tahunAngkatanValue, + jenis_pendaftaran: jenisPendaftaran, + jumlah_mahasiswa_beasiswa: 1 + }); + } + + return acc; + }, []); + + // Sort the results + const sortedData = groupedData.sort((a: any, b: any) => { + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return a.jenis_pendaftaran.localeCompare(b.jenis_pendaftaran); + }); + + return NextResponse.json(sortedData); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/jenis-pendaftaran-lulus/route.ts b/app/api/mahasiswa/jenis-pendaftaran-lulus/route.ts index b127923..0512eb7 100644 --- a/app/api/mahasiswa/jenis-pendaftaran-lulus/route.ts +++ b/app/api/mahasiswa/jenis-pendaftaran-lulus/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; interface JenisPendaftaranLulus { tahun_angkatan: number; @@ -12,37 +12,54 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url); const tahunAngkatan = searchParams.get('tahun_angkatan'); - let query = ` - SELECT - m.tahun_angkatan, - m.jenis_pendaftaran, - COUNT(m.nim) AS jumlah_lulus_tepat_waktu - FROM - mahasiswa m - JOIN - status_mahasiswa s ON m.nim = s.nim - WHERE - s.status_kuliah = 'Lulus' - AND s.semester <= 8 - `; - - const queryParams: any[] = []; + let query = supabase + .from('status_mahasiswa') + .select('semester, mahasiswa!inner(tahun_angkatan, jenis_pendaftaran, nim)') + .eq('status_kuliah', 'Lulus') + .lte('semester', 8); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = ?`; - queryParams.push(parseInt(tahunAngkatan)); + query = query.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan)); } - query += ` - GROUP BY - m.tahun_angkatan, m.jenis_pendaftaran - ORDER BY - m.tahun_angkatan DESC, m.jenis_pendaftaran - `; + const { data, error } = await query; - const [rows] = await pool.query(query, queryParams); + if (error) { + console.error('Error fetching jenis pendaftaran lulus data:', error); + return NextResponse.json( + { error: 'Failed to fetch jenis pendaftaran lulus data' }, + { status: 500 } + ); + } - return NextResponse.json(rows); + // Group by tahun_angkatan and jenis_pendaftaran + const groupedData = data.reduce((acc, item: any) => { + const tahun_angkatan = item.mahasiswa.tahun_angkatan; + const jenis_pendaftaran = item.mahasiswa.jenis_pendaftaran; + const key = `${tahun_angkatan}-${jenis_pendaftaran}`; + acc[key] = (acc[key] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to final format and sort + const results: JenisPendaftaranLulus[] = Object.entries(groupedData) + .map(([key, jumlah_lulus_tepat_waktu]) => { + const [tahun_angkatan, jenis_pendaftaran] = key.split('-'); + return { + tahun_angkatan: parseInt(tahun_angkatan), + jenis_pendaftaran, + jumlah_lulus_tepat_waktu + }; + }) + .sort((a, b) => { + // Sort by tahun_angkatan DESC, jenis_pendaftaran ASC + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return a.jenis_pendaftaran.localeCompare(b.jenis_pendaftaran); + }); + + return NextResponse.json(results); } catch (error) { console.error('Detailed error in GET /api/mahasiswa/jenis-pendaftaran-lulus:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/jenis-pendaftaran-prestasi/route.ts b/app/api/mahasiswa/jenis-pendaftaran-prestasi/route.ts index a88e739..cce40ae 100644 --- a/app/api/mahasiswa/jenis-pendaftaran-prestasi/route.ts +++ b/app/api/mahasiswa/jenis-pendaftaran-prestasi/route.ts @@ -1,30 +1,65 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const jenisPrestasi = searchParams.get('jenisPrestasi'); - const query = ` - SELECT - m.tahun_angkatan, - m.jenis_pendaftaran, - COUNT(m.nim) AS jenis_pendaftaran_mahasiswa_prestasi - FROM - mahasiswa m - JOIN - prestasi_mahasiswa s ON m.nim = s.nim - WHERE - s.jenis_prestasi = ? - GROUP BY - m.tahun_angkatan, m.jenis_pendaftaran - ORDER BY - m.tahun_angkatan DESC, m.jenis_pendaftaran - `; + const { data, error } = await supabase + .from('mahasiswa') + .select(` + tahun_angkatan, + jenis_pendaftaran, + prestasi_mahasiswa!inner( + jenis_prestasi + ) + `) + .eq('prestasi_mahasiswa.jenis_prestasi', jenisPrestasi); - const [rows] = await pool.query(query, [jenisPrestasi]); - return NextResponse.json(rows); + if (error) { + console.error('Supabase error:', error); + return NextResponse.json( + { error: 'Database error' }, + { status: 500 } + ); + } + + // Group and count the data in JavaScript + const groupedData = data.reduce((acc: any[], row: any) => { + const tahunAngkatan = row.tahun_angkatan; + const jenisPendaftaran = row.jenis_pendaftaran; + + if (!tahunAngkatan || !jenisPendaftaran) return acc; + + const existingGroup = acc.find( + (item: any) => + item.tahun_angkatan === tahunAngkatan && + item.jenis_pendaftaran === jenisPendaftaran + ); + + if (existingGroup) { + existingGroup.jenis_pendaftaran_mahasiswa_prestasi++; + } else { + acc.push({ + tahun_angkatan: tahunAngkatan, + jenis_pendaftaran: jenisPendaftaran, + jenis_pendaftaran_mahasiswa_prestasi: 1 + }); + } + + return acc; + }, []); + + // Sort the results + const sortedData = groupedData.sort((a: any, b: any) => { + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return a.jenis_pendaftaran.localeCompare(b.jenis_pendaftaran); + }); + + return NextResponse.json(sortedData); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/jenis-pendaftaran-status/route.ts b/app/api/mahasiswa/jenis-pendaftaran-status/route.ts index f452304..52db693 100644 --- a/app/api/mahasiswa/jenis-pendaftaran-status/route.ts +++ b/app/api/mahasiswa/jenis-pendaftaran-status/route.ts @@ -1,8 +1,7 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; +import supabase from '@/lib/db'; -interface JenisPendaftaranStatus extends RowDataPacket { +interface JenisPendaftaranStatus { jenis_pendaftaran: string; tahun_angkatan: number; status_kuliah: string; @@ -13,39 +12,66 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url); const tahunAngkatan = searchParams.get('tahun_angkatan'); const statusKuliah = searchParams.get('status_kuliah'); - - const connection = await pool.getConnection(); - - try { - let query = ` - SELECT - m.jenis_pendaftaran, - m.tahun_angkatan, - s.status_kuliah, - COUNT(m.nim) AS total_mahasiswa - FROM - mahasiswa m - JOIN - status_mahasiswa s ON m.nim = s.nim - WHERE - s.status_kuliah = ? - `; - const params: any[] = [statusKuliah]; + try { + let query = supabase + .from('status_mahasiswa') + .select('status_kuliah, mahasiswa!inner(jenis_pendaftaran, tahun_angkatan, nim)') + .eq('status_kuliah', statusKuliah); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = ?`; - params.push(tahunAngkatan); + query = query.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan)); } - query += ` - GROUP BY - m.jenis_pendaftaran, m.tahun_angkatan, s.status_kuliah - ORDER BY - m.tahun_angkatan DESC, m.jenis_pendaftaran, s.status_kuliah - `; + const { data, error } = await query; - const [results] = await connection.query(query, params); + if (error) { + console.error('Error fetching jenis pendaftaran status:', error); + return NextResponse.json( + { error: 'Failed to fetch jenis pendaftaran status data' }, + { + status: 500, + headers: { + 'Cache-Control': 'public, max-age=60, stale-while-revalidate=30', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } + + // Group by jenis_pendaftaran, tahun_angkatan, status_kuliah + const groupedData = data.reduce((acc, item: any) => { + const jenis_pendaftaran = item.mahasiswa.jenis_pendaftaran; + const tahun_angkatan = item.mahasiswa.tahun_angkatan; + const status_kuliah = item.status_kuliah; + const key = `${jenis_pendaftaran}-${tahun_angkatan}-${status_kuliah}`; + acc[key] = (acc[key] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to final format and sort + const results: JenisPendaftaranStatus[] = Object.entries(groupedData) + .map(([key, total_mahasiswa]) => { + const [jenis_pendaftaran, tahun_angkatan, status_kuliah] = key.split('-'); + return { + jenis_pendaftaran, + tahun_angkatan: parseInt(tahun_angkatan), + status_kuliah, + total_mahasiswa + }; + }) + .sort((a, b) => { + // Sort by tahun_angkatan DESC, jenis_pendaftaran ASC, status_kuliah ASC + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + if (a.jenis_pendaftaran !== b.jenis_pendaftaran) { + return a.jenis_pendaftaran.localeCompare(b.jenis_pendaftaran); + } + return a.status_kuliah.localeCompare(b.status_kuliah); + }); return NextResponse.json(results, { headers: { @@ -59,16 +85,15 @@ export async function GET(request: Request) { console.error('Error fetching jenis pendaftaran status:', error); return NextResponse.json( { error: 'Failed to fetch jenis pendaftaran status data' }, - { + { status: 500, headers: { + 'Cache-Control': 'public, max-age=60, stale-while-revalidate=30', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/app/api/mahasiswa/jenis-pendaftaran/route.ts b/app/api/mahasiswa/jenis-pendaftaran/route.ts index 8ea8d30..3ee6dc4 100644 --- a/app/api/mahasiswa/jenis-pendaftaran/route.ts +++ b/app/api/mahasiswa/jenis-pendaftaran/route.ts @@ -1,8 +1,7 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; +import supabase from '@/lib/db'; -interface JenisPendaftaran extends RowDataPacket { +interface JenisPendaftaran { tahun_angkatan: number; jenis_pendaftaran: string; jumlah: number; @@ -12,27 +11,54 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url); const tahunAngkatan = searchParams.get('tahun_angkatan'); - const connection = await pool.getConnection(); - try { - let query = ` - SELECT tahun_angkatan, jenis_pendaftaran, COUNT(*) AS jumlah - FROM mahasiswa - `; - - const params: any[] = []; + let query = supabase + .from('mahasiswa') + .select('tahun_angkatan, jenis_pendaftaran'); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` WHERE tahun_angkatan = ?`; - params.push(tahunAngkatan); + query = query.eq('tahun_angkatan', parseInt(tahunAngkatan)); } - query += ` - GROUP BY tahun_angkatan, jenis_pendaftaran - ORDER BY tahun_angkatan DESC, jenis_pendaftaran - `; + const { data, error } = await query; - const [results] = await connection.query(query, params); + if (error) { + console.error('Error fetching jenis pendaftaran:', error); + return NextResponse.json( + { error: 'Failed to fetch jenis pendaftaran data' }, + { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } + + // Group by tahun_angkatan and jenis_pendaftaran + const groupedData = data.reduce((acc, item) => { + const key = `${item.tahun_angkatan}-${item.jenis_pendaftaran}`; + acc[key] = (acc[key] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to final format + const results: JenisPendaftaran[] = Object.entries(groupedData).map(([key, jumlah]) => { + const [tahun_angkatan, jenis_pendaftaran] = key.split('-'); + return { + tahun_angkatan: parseInt(tahun_angkatan), + jenis_pendaftaran, + jumlah + }; + }).sort((a, b) => { + // Sort by tahun_angkatan DESC, then by jenis_pendaftaran + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return a.jenis_pendaftaran.localeCompare(b.jenis_pendaftaran); + }); return NextResponse.json(results, { headers: { @@ -55,7 +81,5 @@ export async function GET(request: Request) { }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/app/api/mahasiswa/jenis-prestasi/route.ts b/app/api/mahasiswa/jenis-prestasi/route.ts index 26eacf9..b0744ff 100644 --- a/app/api/mahasiswa/jenis-prestasi/route.ts +++ b/app/api/mahasiswa/jenis-prestasi/route.ts @@ -1,18 +1,28 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET() { try { - const [rows] = await pool.query(` - SELECT jenis_prestasi - FROM prestasi_mahasiswa - WHERE jenis_prestasi = 'Akademik' OR jenis_prestasi = 'Non-Akademik' - GROUP BY jenis_prestasi - ORDER BY jenis_prestasi ASC - `); + const { data, error } = await supabase + .from('mahasiswa') + .select('jenis_prestasi') + .in('jenis_prestasi', ['Akademik', 'Non-Akademik']) + .order('jenis_prestasi', { ascending: true }); - return NextResponse.json(rows); + if (error) { + console.error('Error fetching data:', error); + return NextResponse.json( + { error: 'Internal Server Error' }, + { status: 500 } + ); + } + + // Get unique jenis_prestasi values + const uniquePrestasi = [...new Set(data.map(item => item.jenis_prestasi))]; + + return NextResponse.json(uniquePrestasi); } catch (error) { + console.error('Error fetching data:', error); return NextResponse.json( { error: 'Internal Server Error' }, { status: 500 } diff --git a/app/api/mahasiswa/lulus-tepat-waktu/route.ts b/app/api/mahasiswa/lulus-tepat-waktu/route.ts index 749f345..448e74c 100644 --- a/app/api/mahasiswa/lulus-tepat-waktu/route.ts +++ b/app/api/mahasiswa/lulus-tepat-waktu/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; interface LulusTepatWaktu { tahun_angkatan: number; @@ -12,37 +12,56 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url); const tahunAngkatan = searchParams.get('tahun_angkatan'); - let query = ` - SELECT - m.tahun_angkatan, - m.jk, - COUNT(m.nim) AS jumlah_lulus_tepat_waktu - FROM - mahasiswa m - JOIN - status_mahasiswa s ON m.nim = s.nim - WHERE - s.status_kuliah = 'Lulus' - AND s.semester <= 8 - `; - - const params: any[] = []; + let query = supabase + .from('status_mahasiswa') + .select('semester, mahasiswa!inner(tahun_angkatan, jk, nim)') + .eq('status_kuliah', 'Lulus') + .lte('semester', 8); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ' AND m.tahun_angkatan = ?'; - params.push(tahunAngkatan); + query = query.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan)); } - query += ` - GROUP BY - m.tahun_angkatan, m.jk - ORDER BY - m.tahun_angkatan DESC, m.jk - `; + const { data, error } = await query; - const [rows] = await pool.query(query, params); - return NextResponse.json(rows); + if (error) { + console.error('Error fetching lulus tepat waktu data:', error); + return NextResponse.json( + { error: 'Failed to fetch lulus tepat waktu data' }, + { status: 500 } + ); + } + + // Group by tahun_angkatan and jk + const groupedData = data.reduce((acc, item: any) => { + const tahun_angkatan = item.mahasiswa.tahun_angkatan; + const jk = item.mahasiswa.jk; + const key = `${tahun_angkatan}-${jk}`; + acc[key] = (acc[key] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to final format and sort + const results: LulusTepatWaktu[] = Object.entries(groupedData) + .map(([key, jumlah_lulus_tepat_waktu]) => { + const [tahun_angkatan, jk] = key.split('-'); + return { + tahun_angkatan: parseInt(tahun_angkatan), + jk, + jumlah_lulus_tepat_waktu + }; + }) + .sort((a, b) => { + // Sort by tahun_angkatan DESC, jk ASC + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return a.jk.localeCompare(b.jk); + }); + + return NextResponse.json(results); } catch (error) { + console.error('Error in lulus-tepat-waktu route:', error); return NextResponse.json( { error: 'Internal Server Error' }, { status: 500 } diff --git a/app/api/mahasiswa/nama-beasiswa/route.ts b/app/api/mahasiswa/nama-beasiswa/route.ts index e1b8d07..5a0b686 100644 --- a/app/api/mahasiswa/nama-beasiswa/route.ts +++ b/app/api/mahasiswa/nama-beasiswa/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET(request: Request) { try { @@ -7,37 +7,68 @@ export async function GET(request: Request) { const tahunAngkatan = searchParams.get('tahunAngkatan'); const jenisBeasiswa = searchParams.get('jenisBeasiswa'); - let query = ` - SELECT - m.tahun_angkatan, - s.nama_beasiswa, - COUNT(m.nim) AS jumlah_nama_beasiswa - FROM - mahasiswa m - JOIN - beasiswa_mahasiswa s ON m.nim = s.nim - WHERE - s.jenis_beasiswa = ? - `; + let query = supabase + .from('beasiswa_mahasiswa') + .select(` + nama_beasiswa, + jenis_beasiswa, + mahasiswa!inner( + tahun_angkatan + ) + `) + .eq('jenis_beasiswa', jenisBeasiswa); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = ?`; + query = query.eq('mahasiswa.tahun_angkatan', tahunAngkatan); } - query += ` - GROUP BY - m.tahun_angkatan, s.nama_beasiswa, s.jenis_beasiswa - ORDER BY - m.tahun_angkatan DESC, s.nama_beasiswa, s.jenis_beasiswa - `; + const { data, error } = await query; - const params = [jenisBeasiswa]; - if (tahunAngkatan && tahunAngkatan !== 'all') { - params.push(tahunAngkatan); + if (error) { + console.error('Supabase error:', error); + return NextResponse.json( + { error: 'Database error' }, + { status: 500 } + ); } - const [rows] = await pool.query(query, params); - return NextResponse.json(rows); + // Group and count the data in JavaScript + const groupedData = data.reduce((acc: any[], row: any) => { + const tahunAngkatanValue = row.mahasiswa?.tahun_angkatan; + const namaBeasiswa = row.nama_beasiswa; + + if (!namaBeasiswa || !tahunAngkatanValue) { + return acc; + } + + const existingGroup = acc.find( + (item: any) => + item.tahun_angkatan === tahunAngkatanValue && + item.nama_beasiswa === namaBeasiswa + ); + + if (existingGroup) { + existingGroup.jumlah_nama_beasiswa++; + } else { + acc.push({ + tahun_angkatan: tahunAngkatanValue, + nama_beasiswa: namaBeasiswa, + jumlah_nama_beasiswa: 1 + }); + } + + return acc; + }, []); + + // Sort the results by tahun_angkatan ascending (as expected by component) + const sortedData = groupedData.sort((a: any, b: any) => { + if (a.tahun_angkatan !== b.tahun_angkatan) { + return a.tahun_angkatan - b.tahun_angkatan; + } + return a.nama_beasiswa.localeCompare(b.nama_beasiswa); + }); + + return NextResponse.json(sortedData); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/profile/route.ts b/app/api/mahasiswa/profile/route.ts index 9fcf2f3..c8e4dfa 100644 --- a/app/api/mahasiswa/profile/route.ts +++ b/app/api/mahasiswa/profile/route.ts @@ -1,10 +1,9 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; +import supabase from '@/lib/db'; import { cookies } from 'next/headers'; import { jwtVerify } from 'jose'; -interface MahasiswaProfile extends RowDataPacket { +interface MahasiswaProfile { nim: string; nama: string; jk: 'Pria' | 'Wanita'; @@ -20,7 +19,6 @@ interface MahasiswaProfile extends RowDataPacket { } export async function GET(request: Request) { - let connection; try { // Get token from cookies const cookieStore = await cookies(); @@ -41,47 +39,57 @@ export async function GET(request: Request) { const nim = payload.nim as string; - // Get connection from pool - connection = await pool.getConnection(); + // Get mahasiswa data + const { data: mahasiswaData, error: mahasiswaError } = await supabase + .from('mahasiswa') + .select(` + nim, + nama, + jk, + agama, + kabupaten, + provinsi, + jenis_pendaftaran, + status_beasiswa, + tahun_angkatan, + ipk, + prestasi + `) + .eq('nim', nim) + .single(); - const query = ` - SELECT - m.nim, - m.nama, - m.jk, - m.agama, - m.kabupaten, - m.provinsi, - m.jenis_pendaftaran, - m.status_beasiswa, - m.tahun_angkatan, - m.ipk, - m.prestasi, - s.status_kuliah - FROM - mahasiswa m - LEFT JOIN - status_mahasiswa s ON m.nim = s.nim - WHERE - m.nim = ? - `; - - const [rows] = await connection.query(query, [nim]); - - if (rows.length === 0) { - connection.release(); + if (mahasiswaError || !mahasiswaData) { return NextResponse.json( { error: 'Data mahasiswa tidak ditemukan' }, { status: 404 } ); } - connection.release(); - return NextResponse.json(rows[0]); + // Get status_kuliah separately + const { data: statusData, error: statusError } = await supabase + .from('status_mahasiswa') + .select('status_kuliah') + .eq('nim', nim) + .single(); + + // Transform the data to match the expected interface + const profile: MahasiswaProfile = { + nim: mahasiswaData.nim, + nama: mahasiswaData.nama, + jk: mahasiswaData.jk, + agama: mahasiswaData.agama, + kabupaten: mahasiswaData.kabupaten, + provinsi: mahasiswaData.provinsi, + jenis_pendaftaran: mahasiswaData.jenis_pendaftaran, + status_beasiswa: mahasiswaData.status_beasiswa, + tahun_angkatan: mahasiswaData.tahun_angkatan, + ipk: mahasiswaData.ipk, + prestasi: mahasiswaData.prestasi, + status_kuliah: statusData?.status_kuliah || '' + }; + + return NextResponse.json(profile); } catch (error) { - if (connection) { - connection.release(); - } console.error('Error fetching profile data:', error); return NextResponse.json( { error: 'Internal Server Error' }, diff --git a/app/api/mahasiswa/statistik/route.ts b/app/api/mahasiswa/statistik/route.ts index 4cfe637..fc8a13d 100644 --- a/app/api/mahasiswa/statistik/route.ts +++ b/app/api/mahasiswa/statistik/route.ts @@ -1,8 +1,7 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; +import supabase from '@/lib/db'; -interface MahasiswaStatistik extends RowDataPacket { +interface MahasiswaStatistik { tahun_angkatan: number; total_mahasiswa: number; pria: number; @@ -23,19 +22,52 @@ export async function OPTIONS() { } export async function GET() { - const connection = await pool.getConnection(); - try { - // Query untuk mendapatkan statistik mahasiswa per tahun angkatan - const [results] = await connection.query(` - SELECT - tahun_angkatan, - COUNT(*) as total_mahasiswa, - SUM(CASE WHEN jk = 'Pria' THEN 1 ELSE 0 END) as pria, - SUM(CASE WHEN jk = 'Wanita' THEN 1 ELSE 0 END) as wanita - FROM mahasiswa - GROUP BY tahun_angkatan - `); + // Get all mahasiswa data + const { data, error } = await supabase + .from('mahasiswa') + .select('tahun_angkatan, jk'); + + if (error) { + console.error('Error fetching mahasiswa statistik:', error); + return NextResponse.json( + { error: 'Failed to fetch mahasiswa statistik' }, + { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } + + // Group by tahun_angkatan and calculate statistics + const groupedData = data.reduce((acc, item) => { + const tahun = item.tahun_angkatan; + if (!acc[tahun]) { + acc[tahun] = { + tahun_angkatan: tahun, + total_mahasiswa: 0, + pria: 0, + wanita: 0 + }; + } + + acc[tahun].total_mahasiswa += 1; + if (item.jk === 'Pria') { + acc[tahun].pria += 1; + } else if (item.jk === 'Wanita') { + acc[tahun].wanita += 1; + } + + return acc; + }, {} as Record); + + // Convert to array and sort by tahun_angkatan + const results: MahasiswaStatistik[] = Object.values(groupedData) + .sort((a, b) => a.tahun_angkatan - b.tahun_angkatan); // Menambahkan header cache dan CORS return NextResponse.json(results, { @@ -59,7 +91,5 @@ export async function GET() { }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/app/api/mahasiswa/status-kuliah/route.ts b/app/api/mahasiswa/status-kuliah/route.ts index 04057ad..d66d811 100644 --- a/app/api/mahasiswa/status-kuliah/route.ts +++ b/app/api/mahasiswa/status-kuliah/route.ts @@ -1,42 +1,83 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; +import supabase from '@/lib/db'; -interface StatusKuliah extends RowDataPacket { +interface StatusKuliah { tahun_angkatan: number; status_kuliah: string; jumlah: number; } export async function GET(request: Request) { - const { searchParams } = new URL(request.url); - const tahunAngkatan = searchParams.get('tahun_angkatan'); - - const connection = await pool.getConnection(); - try { - let query = ` - SELECT m.tahun_angkatan, s.status_kuliah, COUNT(*) AS jumlah - FROM mahasiswa m - JOIN status_mahasiswa s ON m.nim = s.nim - WHERE s.status_kuliah IN ('Lulus', 'Cuti', 'Aktif', 'DO') - `; + const { searchParams } = new URL(request.url); + const tahunAngkatan = searchParams.get('tahun_angkatan'); - const params: any[] = []; + let query = supabase + .from('mahasiswa') + .select(` + tahun_angkatan, + status_mahasiswa!inner( + status_kuliah + ) + `) + .in('status_mahasiswa.status_kuliah', ['Lulus', 'Cuti', 'Aktif', 'DO']); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = ?`; - params.push(tahunAngkatan); + query = query.eq('tahun_angkatan', tahunAngkatan); } - query += ` - GROUP BY m.tahun_angkatan, s.status_kuliah - ORDER BY m.tahun_angkatan, s.status_kuliah - `; + const { data, error } = await query; - const [results] = await connection.query(query, params); + if (error) { + console.error('Supabase error:', error); + return NextResponse.json( + { error: 'Failed to fetch status kuliah data' }, + { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } - return NextResponse.json(results, { + // Group and count the data in JavaScript + const groupedData = data.reduce((acc: any[], row: any) => { + const tahunAngkatanValue = row.tahun_angkatan; + const statusKuliah = row.status_mahasiswa?.status_kuliah; + + if (!tahunAngkatanValue || !statusKuliah) return acc; + + const existingGroup = acc.find( + (item: any) => + item.tahun_angkatan === tahunAngkatanValue && + item.status_kuliah === statusKuliah + ); + + if (existingGroup) { + existingGroup.jumlah++; + } else { + acc.push({ + tahun_angkatan: tahunAngkatanValue, + status_kuliah: statusKuliah, + jumlah: 1 + }); + } + + return acc; + }, []); + + // Sort the results + const sortedData = groupedData.sort((a: any, b: any) => { + if (a.tahun_angkatan !== b.tahun_angkatan) { + return a.tahun_angkatan - b.tahun_angkatan; + } + return a.status_kuliah.localeCompare(b.status_kuliah); + }); + + return NextResponse.json(sortedData, { headers: { 'Cache-Control': 'public, max-age=60, stale-while-revalidate=30', 'Access-Control-Allow-Origin': '*', @@ -57,7 +98,5 @@ export async function GET(request: Request) { }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/app/api/mahasiswa/status-mahasiswa/route.ts b/app/api/mahasiswa/status-mahasiswa/route.ts index 0be3382..bcad24f 100644 --- a/app/api/mahasiswa/status-mahasiswa/route.ts +++ b/app/api/mahasiswa/status-mahasiswa/route.ts @@ -1,8 +1,7 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; +import supabase from '@/lib/db'; -interface StatusMahasiswa extends RowDataPacket { +interface StatusMahasiswa { tahun_angkatan: number; jk: string; total_mahasiswa: number; @@ -13,41 +12,57 @@ export async function GET(request: Request) { const tahunAngkatan = searchParams.get('tahun_angkatan'); const statusKuliah = searchParams.get('status_kuliah'); - const connection = await pool.getConnection(); - try { - let query = ` - SELECT - m.tahun_angkatan, - CASE - WHEN m.jk = 'Pria' THEN 'L' - WHEN m.jk = 'Wanita' THEN 'P' - ELSE m.jk - END as jk, - COUNT(m.nim) AS total_mahasiswa - FROM - mahasiswa m - JOIN - status_mahasiswa s ON m.nim = s.nim - WHERE - s.status_kuliah = ? - `; - - const params: any[] = [statusKuliah]; + let query = supabase + .from('status_mahasiswa') + .select('status_kuliah, mahasiswa!inner(tahun_angkatan, jk)') + .eq('status_kuliah', statusKuliah); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = ?`; - params.push(tahunAngkatan); + query = query.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan)); } - query += ` - GROUP BY - m.tahun_angkatan, m.jk - ORDER BY - m.tahun_angkatan DESC, m.jk - `; + const { data, error } = await query; - const [results] = await connection.query(query, params); + if (error) { + console.error('Error fetching status mahasiswa:', error); + return NextResponse.json( + { error: 'Failed to fetch status mahasiswa data' }, + { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } + + // Group by tahun_angkatan and jk + const groupedData = data.reduce((acc, item: any) => { + const tahun_angkatan = item.mahasiswa.tahun_angkatan; + const jk = item.mahasiswa.jk; + const key = `${tahun_angkatan}-${jk}`; + acc[key] = (acc[key] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to final format + const results: StatusMahasiswa[] = Object.entries(groupedData).map(([key, total_mahasiswa]) => { + const [tahun_angkatan, jk] = key.split('-'); + return { + tahun_angkatan: parseInt(tahun_angkatan), + jk: jk === 'Pria' ? 'L' : jk === 'Wanita' ? 'P' : jk, + total_mahasiswa + }; + }).sort((a, b) => { + // Sort by tahun_angkatan DESC, then by jk + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return a.jk.localeCompare(b.jk); + }); return NextResponse.json(results, { headers: { @@ -70,7 +85,5 @@ export async function GET(request: Request) { }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/app/api/mahasiswa/status/route.ts b/app/api/mahasiswa/status/route.ts index 3a1b07d..b671110 100644 --- a/app/api/mahasiswa/status/route.ts +++ b/app/api/mahasiswa/status/route.ts @@ -1,18 +1,47 @@ import { NextResponse } from 'next/server'; -import db from '@/lib/db'; +import supabase from '@/lib/db'; + +interface StatusData { + tahun_angkatan: number; + status_kuliah: string; + jumlah: number; +} export async function GET() { try { - const query = ` - SELECT m.tahun_angkatan, s.status_kuliah, COUNT(*) AS jumlah - FROM mahasiswa m - JOIN status_mahasiswa s ON m.nim = s.nim - WHERE s.status_kuliah IN ('Lulus', 'Cuti', 'Aktif', 'DO') - GROUP BY m.tahun_angkatan, s.status_kuliah; - `; + const { data, error } = await supabase + .from('status_mahasiswa') + .select('status_kuliah, mahasiswa!inner(tahun_angkatan)') + .in('status_kuliah', ['Lulus', 'Cuti', 'Aktif', 'DO']); - const [rows] = await db.query(query); - return NextResponse.json(rows); + if (error) { + console.error('Error fetching status data:', error); + return NextResponse.json( + { error: 'Failed to fetch status data' }, + { status: 500 } + ); + } + + // Group by tahun_angkatan and status_kuliah + const groupedData = data.reduce((acc, item: any) => { + const tahun_angkatan = item.mahasiswa.tahun_angkatan; + const status_kuliah = item.status_kuliah; + const key = `${tahun_angkatan}-${status_kuliah}`; + acc[key] = (acc[key] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to final format + const results: StatusData[] = Object.entries(groupedData).map(([key, jumlah]) => { + const [tahun_angkatan, status_kuliah] = key.split('-'); + return { + tahun_angkatan: parseInt(tahun_angkatan), + status_kuliah, + jumlah + }; + }); + + return NextResponse.json(results); } catch (error) { console.error('Error fetching status data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/tahun-angkatan/route.ts b/app/api/mahasiswa/tahun-angkatan/route.ts index 0396dfb..70bd6f6 100644 --- a/app/api/mahasiswa/tahun-angkatan/route.ts +++ b/app/api/mahasiswa/tahun-angkatan/route.ts @@ -1,20 +1,38 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET() { - const connection = await pool.getConnection(); - try { const currentYear = new Date().getFullYear(); - const [results] = await connection.query(` - SELECT DISTINCT tahun_angkatan - FROM mahasiswa - WHERE tahun_angkatan >= ? - ORDER BY tahun_angkatan DESC - LIMIT 7 - `, [currentYear - 6]); + const { data, error } = await supabase + .from('mahasiswa') + .select('tahun_angkatan') + .gte('tahun_angkatan', currentYear - 10) + .order('tahun_angkatan', { ascending: false }); - return NextResponse.json(results, { + if (error) { + console.error('Error fetching tahun angkatan:', error); + return NextResponse.json( + { error: 'Failed to fetch tahun angkatan data' }, + { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } + + // Get unique tahun_angkatan values and limit to 7 most recent + const uniqueYears = [...new Set(data.map(item => item.tahun_angkatan))] + .sort((a, b) => b - a) // Sort descending + .slice(0, 7); // Take only 7 most recent years + + console.log('Available years:', uniqueYears); // Debug log + + return NextResponse.json(uniqueYears, { headers: { 'Cache-Control': 'public, max-age=60, stale-while-revalidate=30', 'Access-Control-Allow-Origin': '*', @@ -35,7 +53,5 @@ export async function GET() { }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/app/api/mahasiswa/tingkat-prestasi/route.ts b/app/api/mahasiswa/tingkat-prestasi/route.ts index f8437ce..8cc790f 100644 --- a/app/api/mahasiswa/tingkat-prestasi/route.ts +++ b/app/api/mahasiswa/tingkat-prestasi/route.ts @@ -1,30 +1,65 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const jenisPrestasi = searchParams.get('jenisPrestasi'); - const query = ` - SELECT - m.tahun_angkatan, - s.tingkat, - COUNT(m.nim) AS tingkat_mahasiswa_prestasi - FROM - mahasiswa m - JOIN - prestasi_mahasiswa s ON m.nim = s.nim - WHERE - s.jenis_prestasi = ? - GROUP BY - m.tahun_angkatan, s.tingkat - ORDER BY - m.tahun_angkatan DESC, s.tingkat - `; + const { data, error } = await supabase + .from('mahasiswa') + .select(` + tahun_angkatan, + prestasi_mahasiswa!inner( + tingkat_prestasi, + jenis_prestasi + ) + `) + .eq('prestasi_mahasiswa.jenis_prestasi', jenisPrestasi); - const [rows] = await pool.query(query, [jenisPrestasi]); - return NextResponse.json(rows); + if (error) { + console.error('Supabase error:', error); + return NextResponse.json( + { error: 'Database error' }, + { status: 500 } + ); + } + + // Group and count the data in JavaScript + const groupedData = data.reduce((acc: any[], row: any) => { + const tahunAngkatan = row.tahun_angkatan; + const tingkatPrestasi = row.prestasi_mahasiswa?.tingkat_prestasi; + + if (!tahunAngkatan || !tingkatPrestasi) return acc; + + const existingGroup = acc.find( + (item: any) => + item.tahun_angkatan === tahunAngkatan && + item.tingkat_prestasi === tingkatPrestasi + ); + + if (existingGroup) { + existingGroup.tingkat_mahasiswa_prestasi++; + } else { + acc.push({ + tahun_angkatan: tahunAngkatan, + tingkat_prestasi: tingkatPrestasi, + tingkat_mahasiswa_prestasi: 1 + }); + } + + return acc; + }, []); + + // Sort the results + const sortedData = groupedData.sort((a: any, b: any) => { + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return a.tingkat_prestasi.localeCompare(b.tingkat_prestasi); + }); + + return NextResponse.json(sortedData); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/total-beasiswa/route.ts b/app/api/mahasiswa/total-beasiswa/route.ts index 07e20e7..e1faf0b 100644 --- a/app/api/mahasiswa/total-beasiswa/route.ts +++ b/app/api/mahasiswa/total-beasiswa/route.ts @@ -1,5 +1,11 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; + +interface BeasiswaData { + tahun_angkatan: number; + jk: string; + jumlah_mahasiswa_beasiswa: number; +} export async function GET(request: Request) { try { @@ -7,37 +13,51 @@ export async function GET(request: Request) { const tahunAngkatan = searchParams.get('tahunAngkatan'); const jenisBeasiswa = searchParams.get('jenisBeasiswa'); - let query = ` - SELECT - m.tahun_angkatan, - m.jk, - COUNT(m.nim) AS jumlah_mahasiswa_beasiswa - FROM - mahasiswa m - JOIN - beasiswa_mahasiswa s ON m.nim = s.nim - WHERE - s.jenis_beasiswa = ? - `; + let query = supabase + .from('beasiswa_mahasiswa') + .select('jenis_beasiswa, mahasiswa!inner(tahun_angkatan, jk, nim)') + .eq('jenis_beasiswa', jenisBeasiswa); if (tahunAngkatan && tahunAngkatan !== 'all') { - query += ` AND m.tahun_angkatan = ?`; + query = query.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan)); } - query += ` - GROUP BY - m.tahun_angkatan, m.jk - ORDER BY - m.tahun_angkatan DESC, m.jk - `; + const { data, error } = await query; - const params = [jenisBeasiswa]; - if (tahunAngkatan && tahunAngkatan !== 'all') { - params.push(tahunAngkatan); + if (error) { + console.error('Error fetching data:', error); + return NextResponse.json( + { error: 'Internal Server Error' }, + { status: 500 } + ); } - const [rows] = await pool.query(query, params); - return NextResponse.json(rows); + // Group by tahun_angkatan and jk + const groupedData = data.reduce((acc, item: any) => { + const tahun_angkatan = item.mahasiswa.tahun_angkatan; + const jk = item.mahasiswa.jk; + const key = `${tahun_angkatan}-${jk}`; + acc[key] = (acc[key] || 0) + 1; + return acc; + }, {} as Record); + + // Convert to final format + const results: BeasiswaData[] = Object.entries(groupedData).map(([key, jumlah_mahasiswa_beasiswa]) => { + const [tahun_angkatan, jk] = key.split('-'); + return { + tahun_angkatan: parseInt(tahun_angkatan), + jk, + jumlah_mahasiswa_beasiswa + }; + }).sort((a, b) => { + // Sort by tahun_angkatan DESC, then by jk + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return a.jk.localeCompare(b.jk); + }); + + return NextResponse.json(results); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/total-prestasi/route.ts b/app/api/mahasiswa/total-prestasi/route.ts index bf822cd..c135230 100644 --- a/app/api/mahasiswa/total-prestasi/route.ts +++ b/app/api/mahasiswa/total-prestasi/route.ts @@ -1,30 +1,69 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; +import supabase from '@/lib/db'; + +interface PrestasiData { + tahun_angkatan: number; + jk: string; + jumlah_mahasiswa_prestasi: number; +} export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const jenisPrestasi = searchParams.get('jenisPrestasi'); - const query = ` - SELECT - m.tahun_angkatan, - m.jk, - COUNT(m.nim) AS jumlah_mahasiswa_prestasi - FROM - mahasiswa m - JOIN - prestasi_mahasiswa s ON m.nim = s.nim - WHERE - s.jenis_prestasi = ? - GROUP BY - m.tahun_angkatan, m.jk - ORDER BY - m.tahun_angkatan DESC, m.jk - `; + const { data, error } = await supabase + .from('mahasiswa') + .select(` + tahun_angkatan, + jk, + prestasi_mahasiswa!inner( + jenis_prestasi + ) + `) + .eq('prestasi_mahasiswa.jenis_prestasi', jenisPrestasi); - const [rows] = await pool.query(query, [jenisPrestasi]); - return NextResponse.json(rows); + if (error) { + console.error('Error fetching data:', error); + return NextResponse.json( + { error: 'Internal Server Error' }, + { status: 500 } + ); + } + + // Group by tahun_angkatan and jk + const groupedData = data.reduce((acc: any[], item: any) => { + const tahunAngkatan = item.tahun_angkatan; + const jk = item.jk; + + if (!tahunAngkatan || !jk) return acc; + + const existingGroup = acc.find( + (group: any) => group.tahun_angkatan === tahunAngkatan && group.jk === jk + ); + + if (existingGroup) { + existingGroup.jumlah_mahasiswa_prestasi++; + } else { + acc.push({ + tahun_angkatan: tahunAngkatan, + jk: jk, + jumlah_mahasiswa_prestasi: 1 + }); + } + + return acc; + }, []); + + // Sort by tahun_angkatan DESC, then by jk + const sortedData = groupedData.sort((a: any, b: any) => { + if (a.tahun_angkatan !== b.tahun_angkatan) { + return b.tahun_angkatan - a.tahun_angkatan; + } + return a.jk.localeCompare(b.jk); + }); + + return NextResponse.json(sortedData); } catch (error) { console.error('Error fetching data:', error); return NextResponse.json( diff --git a/app/api/mahasiswa/total/route.ts b/app/api/mahasiswa/total/route.ts index 18272b3..c334310 100644 --- a/app/api/mahasiswa/total/route.ts +++ b/app/api/mahasiswa/total/route.ts @@ -1,8 +1,7 @@ import { NextResponse } from 'next/server'; -import pool from '@/lib/db'; -import { RowDataPacket } from 'mysql2'; +import supabase from '@/lib/db'; -interface MahasiswaTotal extends RowDataPacket { +interface MahasiswaTotal { total_mahasiswa: number; mahasiswa_aktif: number; total_lulus: number; @@ -16,6 +15,13 @@ interface MahasiswaTotal extends RowDataPacket { total_mahasiswa_aktif_lulus: number; } +interface IPKData { + nim: string; + mahasiswa: { + ipk: number; + }; +} + // Fungsi untuk menangani preflight request (OPTIONS) export async function OPTIONS() { return new NextResponse(null, { @@ -30,41 +36,99 @@ export async function OPTIONS() { } export async function GET() { - const connection = await pool.getConnection(); - try { - // Query gabungan untuk semua data - const [results] = await connection.query(` - SELECT - (SELECT COUNT(*) FROM mahasiswa) AS total_mahasiswa, - (SELECT COUNT(*) FROM mahasiswa m - JOIN status_mahasiswa s ON m.nim = s.nim - WHERE s.status_kuliah = 'Aktif') AS mahasiswa_aktif, - (SELECT COUNT(*) FROM mahasiswa m - JOIN status_mahasiswa s ON m.nim = s.nim - WHERE s.status_kuliah = 'Lulus') AS total_lulus, - (SELECT COUNT(*) FROM mahasiswa m - JOIN status_mahasiswa s ON m.nim = s.nim - WHERE s.status_kuliah = 'Lulus' AND m.jk = 'Pria') AS pria_lulus, - (SELECT COUNT(*) FROM mahasiswa m - JOIN status_mahasiswa s ON m.nim = s.nim - WHERE s.status_kuliah = 'Lulus' AND m.jk = 'Wanita') AS wanita_lulus, - (SELECT COUNT(*) FROM prestasi_mahasiswa) AS total_berprestasi, - (SELECT COUNT(*) FROM prestasi_mahasiswa WHERE jenis_prestasi = 'Akademik') AS prestasi_akademik, - (SELECT COUNT(*) FROM prestasi_mahasiswa WHERE jenis_prestasi = 'Non-Akademik') AS prestasi_non_akademik, - (SELECT COUNT(*) FROM mahasiswa m - JOIN status_mahasiswa s ON m.nim = s.nim - WHERE s.status_kuliah IN ('Aktif', 'Lulus')) AS total_mahasiswa_aktif_lulus, - (SELECT ROUND(AVG(m.ipk), 2) FROM mahasiswa m - JOIN status_mahasiswa s ON m.nim = s.nim - WHERE s.status_kuliah = 'Aktif') AS ipk_rata_rata_aktif, - (SELECT ROUND(AVG(m.ipk), 2) FROM mahasiswa m - JOIN status_mahasiswa s ON m.nim = s.nim - WHERE s.status_kuliah = 'Lulus') AS ipk_rata_rata_lulus - `); + // Get total mahasiswa + const { count: totalMahasiswa } = await supabase + .from('mahasiswa') + .select('*', { count: 'exact', head: true }); + + // Get mahasiswa aktif + const { count: mahasiswaAktif } = await supabase + .from('status_mahasiswa') + .select('nim', { count: 'exact', head: true }) + .eq('status_kuliah', 'Aktif'); + + // Get total lulus + const { count: totalLulus } = await supabase + .from('status_mahasiswa') + .select('nim', { count: 'exact', head: true }) + .eq('status_kuliah', 'Lulus'); + + // Get pria lulus + const { count: priaLulus } = await supabase + .from('status_mahasiswa') + .select('nim, mahasiswa!inner(jk)', { count: 'exact', head: true }) + .eq('status_kuliah', 'Lulus') + .eq('mahasiswa.jk', 'Pria'); + + // Get wanita lulus + const { count: wanitaLulus } = await supabase + .from('status_mahasiswa') + .select('nim, mahasiswa!inner(jk)', { count: 'exact', head: true }) + .eq('status_kuliah', 'Lulus') + .eq('mahasiswa.jk', 'Wanita'); + + // Get total berprestasi + const { count: totalBerprestasi } = await supabase + .from('prestasi_mahasiswa') + .select('*', { count: 'exact', head: true }); + + // Get prestasi akademik + const { count: prestasiAkademik } = await supabase + .from('prestasi_mahasiswa') + .select('*', { count: 'exact', head: true }) + .eq('jenis_prestasi', 'Akademik'); + + // Get prestasi non-akademik + const { count: prestasiNonAkademik } = await supabase + .from('prestasi_mahasiswa') + .select('*', { count: 'exact', head: true }) + .eq('jenis_prestasi', 'Non-Akademik'); + + // Get total mahasiswa aktif + lulus + const { count: totalMahasiswaAktifLulus } = await supabase + .from('status_mahasiswa') + .select('nim', { count: 'exact', head: true }) + .in('status_kuliah', ['Aktif', 'Lulus']); + + // Get IPK rata-rata aktif + const { data: ipkAktifData } = await supabase + .from('status_mahasiswa') + .select('nim, mahasiswa!inner(ipk)') + .eq('status_kuliah', 'Aktif') + .not('mahasiswa.ipk', 'is', null); + + const ipkRataRataAktif = ipkAktifData && ipkAktifData.length > 0 + ? Math.round((ipkAktifData.reduce((sum, item: any) => sum + (item.mahasiswa.ipk || 0), 0) / ipkAktifData.length) * 100) / 100 + : 0; + + // Get IPK rata-rata lulus + const { data: ipkLulusData } = await supabase + .from('status_mahasiswa') + .select('nim, mahasiswa!inner(ipk)') + .eq('status_kuliah', 'Lulus') + .not('mahasiswa.ipk', 'is', null); + + const ipkRataRataLulus = ipkLulusData && ipkLulusData.length > 0 + ? Math.round((ipkLulusData.reduce((sum, item: any) => sum + (item.mahasiswa.ipk || 0), 0) / ipkLulusData.length) * 100) / 100 + : 0; + + const results: MahasiswaTotal = { + total_mahasiswa: totalMahasiswa || 0, + mahasiswa_aktif: mahasiswaAktif || 0, + total_lulus: totalLulus || 0, + pria_lulus: priaLulus || 0, + wanita_lulus: wanitaLulus || 0, + total_berprestasi: totalBerprestasi || 0, + prestasi_akademik: prestasiAkademik || 0, + prestasi_non_akademik: prestasiNonAkademik || 0, + ipk_rata_rata_aktif: ipkRataRataAktif, + ipk_rata_rata_lulus: ipkRataRataLulus, + total_mahasiswa_aktif_lulus: totalMahasiswaAktifLulus || 0, + }; // Menambahkan header cache dan CORS - return NextResponse.json(results[0], { + return NextResponse.json(results, { headers: { 'Cache-Control': 'public, max-age=60, stale-while-revalidate=30', 'Access-Control-Allow-Origin': '*', @@ -85,7 +149,5 @@ export async function GET() { }, } ); - } finally { - connection.release(); } } \ No newline at end of file diff --git a/components/AsalDaerahChart.tsx b/components/AsalDaerahChart.tsx index 417cb88..249183c 100644 --- a/components/AsalDaerahChart.tsx +++ b/components/AsalDaerahChart.tsx @@ -94,14 +94,6 @@ export default function AsalDaerahChart() { }, tickAmount: undefined, }, - grid: { - padding: { - top: 0, - right: 0, - bottom: 0, - left: 0 - } - }, fill: { opacity: 1, }, diff --git a/components/AsalDaerahStatusChart.tsx b/components/AsalDaerahStatusChart.tsx index db65aee..cce94e1 100644 --- a/components/AsalDaerahStatusChart.tsx +++ b/components/AsalDaerahStatusChart.tsx @@ -178,7 +178,6 @@ export default function AsalDaerahStatusChart({ selectedYear, selectedStatus }: }; const series = processSeriesData(); - console.log('Processed series data:', series); if (loading) { return ( diff --git a/components/FilterTahunAngkatan.tsx b/components/FilterTahunAngkatan.tsx index 22dd4c8..4950a49 100644 --- a/components/FilterTahunAngkatan.tsx +++ b/components/FilterTahunAngkatan.tsx @@ -3,10 +3,6 @@ import { useState, useEffect } from 'react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -interface TahunAngkatan { - tahun_angkatan: number; -} - interface Props { selectedYear: string; onYearChange: (year: string) => void; @@ -14,7 +10,7 @@ interface Props { } export default function FilterTahunAngkatan({ selectedYear, onYearChange, showAllOption = true }: Props) { - const [tahunAngkatan, setTahunAngkatan] = useState([]); + const [tahunAngkatan, setTahunAngkatan] = useState([]); useEffect(() => { const fetchTahunAngkatan = async () => { @@ -45,10 +41,10 @@ export default function FilterTahunAngkatan({ selectedYear, onYearChange, showAl )} {tahunAngkatan.map((tahun) => ( - {tahun.tahun_angkatan} + {tahun} ))} diff --git a/components/JenisPendaftaranPerAngkatanChart.tsx b/components/JenisPendaftaranPerAngkatanChart.tsx index bb8a618..3a76db0 100644 --- a/components/JenisPendaftaranPerAngkatanChart.tsx +++ b/components/JenisPendaftaranPerAngkatanChart.tsx @@ -153,7 +153,7 @@ export default function JenisPendaftaranPerAngkatanChart({ tahunAngkatan }: Prop if (!Array.isArray(result)) { throw new Error('Invalid data format received from server'); } - + // Process data for pie chart const jenisPendaftaran = [...new Set(result.map(item => item.jenis_pendaftaran))].sort(); const jumlahData = jenisPendaftaran.map(jenis => { diff --git a/convert_routes.js b/convert_routes.js new file mode 100644 index 0000000..7312b52 --- /dev/null +++ b/convert_routes.js @@ -0,0 +1,115 @@ +const fs = require('fs'); +const path = require('path'); + +// List of routes to convert +const routes = [ + 'app/api/mahasiswa/asal-daerah-angkatan/route.ts', + 'app/api/mahasiswa/asal-daerah-beasiswa/route.ts', + 'app/api/mahasiswa/asal-daerah-lulus/route.ts', + 'app/api/mahasiswa/asal-daerah-prestasi/route.ts', + 'app/api/mahasiswa/asal-daerah-status/route.ts', + 'app/api/mahasiswa/gender-per-angkatan/route.ts', + 'app/api/mahasiswa/ipk-beasiswa/route.ts', + 'app/api/mahasiswa/ipk-jenis-kelamin/route.ts', + 'app/api/mahasiswa/ipk-lulus-tepat/route.ts', + 'app/api/mahasiswa/ipk-prestasi/route.ts', + 'app/api/mahasiswa/ipk-status/route.ts', + 'app/api/mahasiswa/jenis-beasiswa/route.ts', + 'app/api/mahasiswa/jenis-pendaftaran-beasiswa/route.ts', + 'app/api/mahasiswa/jenis-pendaftaran-lulus/route.ts', + 'app/api/mahasiswa/jenis-pendaftaran-prestasi/route.ts', + 'app/api/mahasiswa/jenis-pendaftaran-status/route.ts', + 'app/api/mahasiswa/jenis-prestasi/route.ts', + 'app/api/mahasiswa/lulus-tepat-waktu/route.ts', + 'app/api/mahasiswa/nama-beasiswa/route.ts', + 'app/api/mahasiswa/profile/route.ts', + 'app/api/mahasiswa/statistik/route.ts', + 'app/api/mahasiswa/status-kuliah/route.ts', + 'app/api/mahasiswa/status-mahasiswa/route.ts', + 'app/api/mahasiswa/tahun-angkatan/route.ts', + 'app/api/mahasiswa/tingkat-prestasi/route.ts', + 'app/api/mahasiswa/total-beasiswa/route.ts', + 'app/api/mahasiswa/total-prestasi/route.ts', + 'app/api/auth/check/route.ts', + 'app/api/auth/login/route.ts', + 'app/api/auth/register/route.ts' +]; + +// Template for simple count queries +const simpleCountTemplate = (tableName, groupBy = null, whereClause = null) => { + let query = `supabase.from('${tableName}').select('*', { count: 'exact', head: true })`; + + if (whereClause) { + query += whereClause; + } + + return ` +import { NextResponse } from 'next/server'; +import supabase from '@/lib/db'; + +export async function GET() { + try { + const { count } = await ${query}; + + return NextResponse.json({ count: count || 0 }, { + headers: { + 'Cache-Control': 'public, max-age=60, stale-while-revalidate=30', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + }); + } catch (error) { + console.error('Error fetching data:', error); + return NextResponse.json( + { error: 'Failed to fetch data' }, + { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + } + ); + } +}`; +}; + +// Convert each route +routes.forEach(routePath => { + try { + const content = fs.readFileSync(routePath, 'utf8'); + + // Remove MySQL imports + let newContent = content + .replace(/import.*mysql2.*\n/g, '') + .replace(/import.*pool.*from.*@\/lib\/db.*\n/g, 'import supabase from \'@/lib/db\';\n') + .replace(/import.*db.*from.*@\/lib\/db.*\n/g, 'import supabase from \'@/lib/db\';\n'); + + // Remove RowDataPacket extensions + newContent = newContent.replace(/extends RowDataPacket/g, ''); + + // Remove connection management + newContent = newContent + .replace(/const connection = await pool\.getConnection\(\);\s*\n\s*try\s*{/g, 'try {') + .replace(/} finally {\s*\n\s*connection\.release\(\);\s*\n\s*}/g, '}'); + + // Replace simple queries with Supabase equivalents + if (newContent.includes('COUNT(*)')) { + // This is a simple count query + const tableMatch = newContent.match(/FROM (\w+)/); + if (tableMatch) { + const tableName = tableMatch[1]; + newContent = simpleCountTemplate(tableName); + } + } + + fs.writeFileSync(routePath, newContent); + console.log(`✅ Converted: ${routePath}`); + } catch (error) { + console.error(`❌ Error converting ${routePath}:`, error.message); + } +}); + +console.log('🎉 Route conversion completed!'); \ No newline at end of file diff --git a/create_env.js b/create_env.js new file mode 100644 index 0000000..92b3e90 --- /dev/null +++ b/create_env.js @@ -0,0 +1,17 @@ +const fs = require('fs'); + +// Supabase credentials +const supabaseUrl = 'https://avfoewfplaplaejjhiiv.supabase.co'; +const supabaseKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImF2Zm9ld2ZwbGFwbGFlampoaWl2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTAyNjM2NDUsImV4cCI6MjA2NTgzOTY0NX0._46bKpp95xLN6tkCPRCVNeVBSO-QyvOBPw-jTb74_0o'; + +// Create .env.local content +const envContent = `NEXT_PUBLIC_SUPABASE_URL=${supabaseUrl} +NEXT_PUBLIC_SUPABASE_ANON_KEY=${supabaseKey} +`; + +// Write to file +fs.writeFileSync('.env.local', envContent); + +console.log('✅ .env.local file created successfully!'); +console.log('Content:'); +console.log(envContent); \ No newline at end of file diff --git a/create_functions.sql b/create_functions.sql new file mode 100644 index 0000000..bfa0cea --- /dev/null +++ b/create_functions.sql @@ -0,0 +1,23 @@ +-- Function untuk menghitung rata-rata IPK mahasiswa aktif +CREATE OR REPLACE FUNCTION get_ipk_rata_aktif() +RETURNS TABLE(rata_rata_ipk_aktif NUMERIC) AS $$ +BEGIN + RETURN QUERY + SELECT ROUND(AVG(m.ipk), 2) AS rata_rata_ipk_aktif + FROM status_mahasiswa sm + JOIN mahasiswa m ON sm.nim = m.nim + WHERE sm.status_kuliah = 'Aktif' AND m.ipk IS NOT NULL; +END; +$$ LANGUAGE plpgsql; + +-- Function untuk menghitung rata-rata IPK mahasiswa lulus +CREATE OR REPLACE FUNCTION get_ipk_rata_lulus() +RETURNS TABLE(rata_rata_ipk_lulus NUMERIC) AS $$ +BEGIN + RETURN QUERY + SELECT ROUND(AVG(m.ipk), 2) AS rata_rata_ipk_lulus + FROM status_mahasiswa sm + JOIN mahasiswa m ON sm.nim = m.nim + WHERE sm.status_kuliah = 'Lulus' AND m.ipk IS NOT NULL; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/lib/db.ts b/lib/db.ts index bb34577..01cb58f 100644 --- a/lib/db.ts +++ b/lib/db.ts @@ -1,22 +1,25 @@ -import mysql from 'mysql2/promise'; +import { createClient } from '@supabase/supabase-js'; -const pool = mysql.createPool({ - host: '127.0.0.1', - port: 3306, - user: 'root', - password: 'semogabisayok321', - database: 'mhsdb', - waitForConnections: true, - connectionLimit: 10, - queueLimit: 0 -}); +// Initialize Supabase client +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; +const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + +const supabase = createClient(supabaseUrl, supabaseKey); // Test the connection const testConnection = async () => { try { - const connection = await pool.getConnection(); + const { data, error } = await supabase + .from('mahasiswa') + .select('count') + .limit(1); + + if (error) { + console.error('Database connection failed:', error); + return false; + } + console.log('Database connection successful'); - connection.release(); return true; } catch (error) { console.error('Database connection failed:', error); @@ -24,5 +27,5 @@ const testConnection = async () => { } }; -// Export both the pool and the test function -export { pool as default, testConnection }; \ No newline at end of file +// Export both the client and the test function +export { supabase as default, testConnection }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d0bc0d5..0380979 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.7", "@radix-ui/react-toast": "^1.2.10", + "@supabase/supabase-js": "^2.50.0", "@types/bcryptjs": "^2.4.6", "@types/jsonwebtoken": "^9.0.9", "apexcharts": "^4.5.0", @@ -27,7 +28,6 @@ "jose": "^6.0.11", "jsonwebtoken": "^9.0.2", "lucide-react": "^0.488.0", - "mysql2": "^3.14.0", "next": "15.3.0", "next-auth": "^4.24.11", "next-themes": "^0.4.6", @@ -1934,6 +1934,80 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@supabase/auth-js": { + "version": "2.70.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.70.0.tgz", + "integrity": "sha512-BaAK/tOAZFJtzF1sE3gJ2FwTjLf4ky3PSvcvLGEgEmO4BSBkwWKu8l67rLLIBZPDnCyV7Owk2uPyKHa0kj5QGg==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz", + "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz", + "integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.10", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.10.tgz", + "integrity": "sha512-SJKVa7EejnuyfImrbzx+HaD9i6T784khuw1zP+MBD7BmJYChegGxYigPzkKX8CK8nGuDntmeSD3fvriaH0EGZA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.13", + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "ws": "^8.18.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.50.0.tgz", + "integrity": "sha512-M1Gd5tPaaghYZ9OjeO1iORRqbTWFEz/cF3pPubRnMPzA+A8SiUsXXWDP+DWsASZcjEcVEcVQIAF38i5wrijYOg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.70.0", + "@supabase/functions-js": "2.4.4", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.19.4", + "@supabase/realtime-js": "2.11.10", + "@supabase/storage-js": "2.7.1" + } + }, "node_modules/@svgdotjs/svg.draggable.js": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.6.tgz", @@ -2367,6 +2441,12 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.1.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", @@ -2387,6 +2467,15 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@yr/monotone-cubic-spline": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", @@ -2419,15 +2508,6 @@ "node": ">=10" } }, - "node_modules/aws-ssl-profiles": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", - "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/bcryptjs": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", @@ -2704,15 +2784,6 @@ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -2777,15 +2848,6 @@ "node": ">=6.0.0" } }, - "node_modules/generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "license": "MIT", - "dependencies": { - "is-property": "^1.0.2" - } - }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -2802,18 +2864,6 @@ "dev": true, "license": "ISC" }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -2830,12 +2880,6 @@ "license": "MIT", "optional": true }, - "node_modules/is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", - "license": "MIT" - }, "node_modules/jiti": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", @@ -3191,12 +3235,6 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3209,30 +3247,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/lru.min": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", - "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", - "license": "MIT", - "engines": { - "bun": ">=1.0.0", - "deno": ">=1.30.0", - "node": ">=8.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wellwelwel" - } - }, "node_modules/lucide-react": { "version": "0.488.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.488.0.tgz", @@ -3248,38 +3262,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/mysql2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.0.tgz", - "integrity": "sha512-8eMhmG6gt/hRkU1G+8KlGOdQi2w+CgtNoD1ksXZq9gQfkfDsX4LHaBwTe1SY0Imx//t2iZA03DFnyYKPinxSRw==", - "license": "MIT", - "dependencies": { - "aws-ssl-profiles": "^1.1.1", - "denque": "^2.1.0", - "generate-function": "^2.3.1", - "iconv-lite": "^0.6.3", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/named-placeholders": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", - "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", - "license": "MIT", - "dependencies": { - "lru-cache": "^7.14.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -3794,12 +3776,6 @@ ], "license": "MIT" }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -3818,11 +3794,6 @@ "node": ">=10" } }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" - }, "node_modules/sharp": { "version": "0.34.1", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz", @@ -3883,15 +3854,6 @@ "node": ">=0.10.0" } }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -3956,6 +3918,12 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -4078,6 +4046,43 @@ "d3-timer": "^3.0.1" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 7f92c91..2e9d2f3 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.7", "@radix-ui/react-toast": "^1.2.10", + "@supabase/supabase-js": "^2.50.0", "@types/bcryptjs": "^2.4.6", "@types/jsonwebtoken": "^9.0.9", "apexcharts": "^4.5.0", @@ -29,7 +30,6 @@ "jose": "^6.0.11", "jsonwebtoken": "^9.0.2", "lucide-react": "^0.488.0", - "mysql2": "^3.14.0", "next": "15.3.0", "next-auth": "^4.24.11", "next-themes": "^0.4.6",