Change Database

This commit is contained in:
Randa Firman Putra
2025-06-20 00:45:19 +07:00
parent e028039ee2
commit 2f7ab6c0a9
45 changed files with 1896 additions and 953 deletions

View File

@@ -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' },

View File

@@ -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<User[]>(
'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);

View File

@@ -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' },

View File

@@ -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<AsalDaerah[]>(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<string, number>);
// 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();
}
}

View File

@@ -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(

View File

@@ -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<string, number>);
// 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(

View File

@@ -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(

View File

@@ -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;
@@ -14,38 +13,77 @@ 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.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];
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<AsalDaerahStatus[]>(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<string, number>);
// 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: {
@@ -62,13 +100,12 @@ export async function GET(request: Request) {
{
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();
}
}

View File

@@ -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<AsalDaerah[]>(`
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<string, number>);
// 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();
}
}

View File

@@ -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<string, number>);
// 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();
}
}

View File

@@ -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(

View File

@@ -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<RowDataPacket[]>(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();
}
}

View File

@@ -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<number, { total_ipk: number; count: number }>);
// 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(

View File

@@ -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(

View File

@@ -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<string, { tahun_angkatan: number; status_kuliah: string; total_mahasiswa: number; total_ipk: number }>);
// 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(

View File

@@ -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<IPKData[]>(`
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<number, { sum: number; count: number }>);
// 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();
}
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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<string, number>);
// 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(

View File

@@ -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(

View File

@@ -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;
@@ -14,38 +13,65 @@ 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.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];
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<JenisPendaftaranStatus[]>(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<string, number>);
// 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: {
@@ -62,13 +88,12 @@ export async function GET(request: Request) {
{
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();
}
}

View File

@@ -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<JenisPendaftaran[]>(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<string, number>);
// 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();
}
}

View File

@@ -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 }

View File

@@ -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<string, number>);
// 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 }

View File

@@ -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(

View File

@@ -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<MahasiswaProfile[]>(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' },

View File

@@ -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<MahasiswaStatistik[]>(`
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<number, MahasiswaStatistik>);
// 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();
}
}

View File

@@ -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<StatusKuliah[]>(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();
}
}

View File

@@ -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<StatusMahasiswa[]>(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<string, number>);
// 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();
}
}

View File

@@ -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<string, number>);
// 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(

View File

@@ -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();
}
}

View File

@@ -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(

View File

@@ -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<string, number>);
// 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(

View File

@@ -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(

View File

@@ -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<MahasiswaTotal[]>(`
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();
}
}

View File

@@ -94,14 +94,6 @@ export default function AsalDaerahChart() {
},
tickAmount: undefined,
},
grid: {
padding: {
top: 0,
right: 0,
bottom: 0,
left: 0
}
},
fill: {
opacity: 1,
},

View File

@@ -178,7 +178,6 @@ export default function AsalDaerahStatusChart({ selectedYear, selectedStatus }:
};
const series = processSeriesData();
console.log('Processed series data:', series);
if (loading) {
return (

View File

@@ -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<TahunAngkatan[]>([]);
const [tahunAngkatan, setTahunAngkatan] = useState<number[]>([]);
useEffect(() => {
const fetchTahunAngkatan = async () => {
@@ -45,10 +41,10 @@ export default function FilterTahunAngkatan({ selectedYear, onYearChange, showAl
)}
{tahunAngkatan.map((tahun) => (
<SelectItem
key={tahun.tahun_angkatan}
value={tahun.tahun_angkatan.toString()}
key={tahun}
value={tahun.toString()}
>
{tahun.tahun_angkatan}
{tahun}
</SelectItem>
))}
</SelectContent>

115
convert_routes.js Normal file
View File

@@ -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!');

17
create_env.js Normal file
View File

@@ -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);

23
create_functions.sql Normal file
View File

@@ -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;

View File

@@ -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 };
// Export both the client and the test function
export { supabase as default, testConnection };

261
package-lock.json generated
View File

@@ -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",

View File

@@ -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",