Change Database
This commit is contained in:
@@ -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' },
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -94,14 +94,6 @@ export default function AsalDaerahChart() {
|
||||
},
|
||||
tickAmount: undefined,
|
||||
},
|
||||
grid: {
|
||||
padding: {
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
opacity: 1,
|
||||
},
|
||||
|
||||
@@ -178,7 +178,6 @@ export default function AsalDaerahStatusChart({ selectedYear, selectedStatus }:
|
||||
};
|
||||
|
||||
const series = processSeriesData();
|
||||
console.log('Processed series data:', series);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
|
||||
@@ -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
115
convert_routes.js
Normal 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
17
create_env.js
Normal 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
23
create_functions.sql
Normal 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;
|
||||
33
lib/db.ts
33
lib/db.ts
@@ -1,22 +1,25 @@
|
||||
import mysql from 'mysql2/promise';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: '127.0.0.1',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: 'semogabisayok321',
|
||||
database: 'mhsdb',
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0
|
||||
});
|
||||
// Initialize Supabase client
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
||||
|
||||
const supabase = createClient(supabaseUrl, supabaseKey);
|
||||
|
||||
// Test the connection
|
||||
const testConnection = async () => {
|
||||
try {
|
||||
const connection = await pool.getConnection();
|
||||
const { data, error } = await supabase
|
||||
.from('mahasiswa')
|
||||
.select('count')
|
||||
.limit(1);
|
||||
|
||||
if (error) {
|
||||
console.error('Database connection failed:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('Database connection successful');
|
||||
connection.release();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Database connection failed:', error);
|
||||
@@ -24,5 +27,5 @@ const testConnection = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Export both the pool and the test function
|
||||
export { pool as default, testConnection };
|
||||
// Export both the client and the test function
|
||||
export { supabase as default, testConnection };
|
||||
261
package-lock.json
generated
261
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user