forked from izu/student-web-if-development-kit
feat(acaab): implementasi halaman prestasi & karya mahasiswa
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { Achievement, Project } from '../data';
|
||||
|
||||
// Determine API Base URL dynamically from Vite env, fallback to hardcoded if not set
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://api.ifuntanhub.dev';
|
||||
|
||||
/**
|
||||
* Helper to parse Rank/Juara from Achievement title
|
||||
*/
|
||||
function extractRank(title: string): string {
|
||||
const match = title.match(/(Juara\s+\d+|Juara\s+Harapan\s*\d*|Top\s+\d+|Finalis|Peringkat\s+\d+|Medali\s+\w+)/i);
|
||||
return match ? match[0] : 'Pemenang';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to parse Level (Nasional, Internasional, Regional) from title/category
|
||||
*/
|
||||
function extractLevel(title: string, category: string): 'Nasional' | 'Internasional' | 'Regional' {
|
||||
const text = `${title} ${category}`.toLowerCase();
|
||||
if (
|
||||
text.includes('internasional') ||
|
||||
text.includes('international') ||
|
||||
text.includes('global') ||
|
||||
text.includes('world') ||
|
||||
text.includes('asean') ||
|
||||
text.includes('unesco')
|
||||
) {
|
||||
return 'Internasional';
|
||||
}
|
||||
if (
|
||||
text.includes('regional') ||
|
||||
text.includes('kalbar') ||
|
||||
text.includes('kalimantan') ||
|
||||
text.includes('provinsi') ||
|
||||
text.includes('kota') ||
|
||||
text.includes('kabupaten') ||
|
||||
text.includes('lokal')
|
||||
) {
|
||||
return 'Regional';
|
||||
}
|
||||
return 'Nasional'; // Default fallback
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to extract competition name from full achievement title
|
||||
*/
|
||||
function extractCompetition(title: string): string {
|
||||
return title
|
||||
.replace(/^(Juara\s+\d+|Juara\s+Harapan\s*\d*|Top\s+\d+|Finalis|Peringkat\s+\d+|Medali\s+\w+)\s+(di\s+|pada\s+|dalam\s+)?/i, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch achievements from API
|
||||
*/
|
||||
export async function fetchAchievementsFromAPI(): Promise<Achievement[]> {
|
||||
const response = await fetch(`${API_BASE_URL}/items/prestasi`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch achievements: ${response.statusText}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
const data = result.data || [];
|
||||
|
||||
return data
|
||||
.filter((item: any) => item.status === 'published')
|
||||
.map((item: any) => {
|
||||
const id = String(item.id);
|
||||
const title = item.nama_prestasi || 'Untitled Achievement';
|
||||
const category = item.bidang || 'Akademik';
|
||||
const rank = extractRank(title);
|
||||
const level = extractLevel(title, category);
|
||||
const competition = extractCompetition(title);
|
||||
const year = parseInt(item.tahun) || new Date().getFullYear();
|
||||
const team = item.nama_tim || 'Mahasiswa';
|
||||
|
||||
// Handle image url mapping for directus assets
|
||||
const image = item.foto
|
||||
? `${API_BASE_URL}/assets/${item.foto}`
|
||||
: `https://picsum.photos/seed/prestasi-${id}/800/600`;
|
||||
|
||||
const avatar = (item.nama_tim || item.nama_prestasi || 'IF')
|
||||
.substring(0, 2)
|
||||
.toUpperCase();
|
||||
|
||||
// Create a friendly fallback description since Directus model lacks one
|
||||
const description = `${team} berhasil meraih prestasi luar biasa sebagai ${rank} dalam ajang ${competition} tahun ${year} pada bidang ${category.toLowerCase()}.`;
|
||||
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
category,
|
||||
competition,
|
||||
rank,
|
||||
year,
|
||||
level,
|
||||
description,
|
||||
team,
|
||||
nim: item.nim || undefined,
|
||||
generation: item.angkatan ? `Angkatan ${item.angkatan}` : 'Informatika',
|
||||
image,
|
||||
avatar,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch projects/portfolio from API
|
||||
*/
|
||||
export async function fetchProjectsFromAPI(): Promise<Project[]> {
|
||||
const response = await fetch(`${API_BASE_URL}/items/portfolio`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch portfolio: ${response.statusText}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
const data = result.data || [];
|
||||
|
||||
return data
|
||||
.filter((item: any) => item.status === 'published')
|
||||
.map((item: any) => {
|
||||
const id = String(item.id);
|
||||
const title = item.nama_proyek || 'Untitled Project';
|
||||
const studentName = item.nama_mahasiswa || 'Mahasiswa';
|
||||
const year = parseInt(item.tahun) || new Date().getFullYear();
|
||||
const category = item.bidang || 'Software Development';
|
||||
const description = item.deskripsi || 'Tidak ada deskripsi.';
|
||||
let link = item.url || undefined;
|
||||
if (link && !/^https?:\/\//i.test(link)) {
|
||||
link = `https://${link}`;
|
||||
}
|
||||
|
||||
const image = item.foto
|
||||
? `${API_BASE_URL}/assets/${item.foto}`
|
||||
: `https://picsum.photos/seed/project-${id}/800/600`;
|
||||
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
studentName,
|
||||
nim: item.nim || undefined,
|
||||
year,
|
||||
category,
|
||||
description,
|
||||
image,
|
||||
link,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Achievement, Project } from '../data';
|
||||
|
||||
/**
|
||||
* Google Sheets Service
|
||||
*/
|
||||
|
||||
const SPREADSHEET_ID = import.meta.env.VITE_SPREADSHEET_ID;
|
||||
const API_KEY = import.meta.env.VITE_GOOGLE_API_KEY;
|
||||
|
||||
export async function fetchSheetData(range: string) {
|
||||
const url = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${range}?key=${API_KEY}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return data.values;
|
||||
} catch (error) {
|
||||
console.error('Error fetching sheet data:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMappedData() {
|
||||
const values = await fetchSheetData('Form Responses 1!A2:J200');
|
||||
if (!values) return { achievements: [], projects: [] };
|
||||
|
||||
const achievements: Achievement[] = [];
|
||||
const projects: Project[] = [];
|
||||
|
||||
values.forEach((row: any[], index: number) => {
|
||||
// New Mapping:
|
||||
// 0: Nama Mahasiswa (A)
|
||||
// 1: NIM Mahasiswa (B)
|
||||
// 2: Angkatan (C)
|
||||
// 3: Jenis Data (D)
|
||||
// 4: Nama Prestasi (E)
|
||||
// 5: Tahun (F)
|
||||
// 6: Tingkat (G)
|
||||
// 7: Bidang (H)
|
||||
// 8: Deskripsi (I)
|
||||
// 9: Gambar (J)
|
||||
|
||||
const type = row[3]; // Jenis Data
|
||||
const id = `sheet-${index}`;
|
||||
const imageUrl = row[9] || `https://picsum.photos/seed/${id}/800/600`;
|
||||
|
||||
if (type === 'Prestasi') {
|
||||
achievements.push({
|
||||
id,
|
||||
title: row[4] || 'Untitled Achievement',
|
||||
category: row[7] || 'Umum',
|
||||
competition: row[4] || 'Kompetisi',
|
||||
rank: 'Peserta',
|
||||
year: parseInt(row[5]) || new Date().getFullYear(),
|
||||
level: (row[6] as any) || 'Nasional',
|
||||
description: row[8] || '',
|
||||
team: row[0] || 'Mahasiswa',
|
||||
nim: row[1],
|
||||
generation: `Angkatan ${row[2] || '-'}`,
|
||||
image: imageUrl,
|
||||
avatar: (row[0] || 'M').substring(0, 2).toUpperCase(),
|
||||
});
|
||||
} else if (type === 'Proyek') {
|
||||
projects.push({
|
||||
id: `p-${id}`,
|
||||
title: row[4] || 'Untitled Project',
|
||||
studentName: row[0] || 'Mahasiswa',
|
||||
nim: row[1],
|
||||
year: parseInt(row[5]) || new Date().getFullYear(),
|
||||
category: row[7] || 'Software',
|
||||
description: row[8] || '',
|
||||
image: imageUrl,
|
||||
link: undefined, // No link column mentioned in new structure
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { achievements, projects };
|
||||
}
|
||||
Reference in New Issue
Block a user