155 lines
5.7 KiB
TypeScript
155 lines
5.7 KiB
TypeScript
/**
|
|
* File: page.tsx
|
|
* Created by: AI Assistant
|
|
* Date: 2025-11-29
|
|
* Purpose: Class list page for kreatiVortex platform
|
|
* Part of: kreatiVortex - Platform Pembelajaran Tari Online
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { Link } from '@/i18n/routing';
|
|
import ActionButton from '@/components/ActionButton';
|
|
import { useFetch } from '@/hooks/useFetch';
|
|
import { useTranslations } from 'next-intl';
|
|
import { useState, useEffect } from 'react';
|
|
|
|
interface ClassData {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
educator: {
|
|
user: {
|
|
name: string;
|
|
};
|
|
};
|
|
schedule: string;
|
|
_count: {
|
|
members: number;
|
|
};
|
|
status: string; // Assuming we add status to API or calculate it
|
|
}
|
|
|
|
interface UserProfile {
|
|
role: {
|
|
name: string;
|
|
};
|
|
}
|
|
|
|
export default function ClassPage() {
|
|
const t = useTranslations('Classes');
|
|
const { data: classes, loading } = useFetch<ClassData[]>('/api/classes');
|
|
const [userProfile, setUserProfile] = useState<UserProfile | null>(null);
|
|
|
|
useEffect(() => {
|
|
// Fetch user profile to check role
|
|
const fetchProfile = async () => {
|
|
try {
|
|
const response = await fetch('/api/user/profile', {
|
|
credentials: 'include'
|
|
});
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
setUserProfile(data.data);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching user profile:', error);
|
|
}
|
|
};
|
|
|
|
fetchProfile();
|
|
}, []);
|
|
|
|
const canCreateClass = userProfile?.role?.name === 'PENDIDIK' || userProfile?.role?.name === 'ADMIN';
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex justify-between items-center">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-white">{t('title')}</h1>
|
|
<p className="text-gray-400">{t('subtitle')}</p>
|
|
</div>
|
|
{canCreateClass ? (
|
|
<Link href="/dashboard/classes/new">
|
|
<ActionButton>
|
|
{t('createButton')}
|
|
</ActionButton>
|
|
</Link>
|
|
) : userProfile?.role?.name === 'UMUM' ? (
|
|
<div className="text-sm text-gray-400">
|
|
<p>Upgrade ke Pendidik untuk membuat kelas</p>
|
|
<Link
|
|
href="/dashboard/register-role"
|
|
className="text-gold-400 hover:text-gold-300 underline"
|
|
>
|
|
Daftar sebagai Pendidik →
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
<div className="text-sm text-gray-400">
|
|
Hanya Pendidik yang dapat membuat kelas
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="text-center text-gray-400 py-12">{t('loading')}</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{classes?.map((cls) => (
|
|
<div key={cls.id} className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20 hover:bg-white/15 transition-all cursor-pointer group">
|
|
<div className="flex justify-between items-start mb-4">
|
|
<span className={`px-2 py-1 text-xs rounded border bg-green-900/50 text-green-200 border-green-500/30`}>
|
|
{t('active')}
|
|
</span>
|
|
<span className="text-sm text-gray-400 flex items-center">
|
|
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
|
</svg>
|
|
{cls._count.members} {t('students')}
|
|
</span>
|
|
</div>
|
|
|
|
<h3 className="text-xl font-semibold text-white mb-2 group-hover:text-gold-400 transition-colors">
|
|
{cls.name}
|
|
</h3>
|
|
<p className="text-gray-400 text-sm mb-4 line-clamp-2">
|
|
{cls.description}
|
|
</p>
|
|
|
|
<div className="space-y-2 text-sm text-gray-300">
|
|
<div className="flex items-center">
|
|
<svg className="w-4 h-4 mr-2 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
|
</svg>
|
|
{cls.educator?.user.name || t('unknownEducator')}
|
|
</div>
|
|
{/* Schedule not in model yet, placeholder */}
|
|
<div className="flex items-center">
|
|
<svg className="w-4 h-4 mr-2 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
{t('scheduleNotSet')}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-6 pt-4 border-t border-white/10 flex justify-end">
|
|
<Link href={`/dashboard/classes/${cls.id}`} className="text-gold-400 hover:text-gold-300 text-sm font-medium flex items-center">
|
|
{t('enterClass')}
|
|
<svg className="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
|
|
</svg>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
))}
|
|
{(!classes || classes.length === 0) && (
|
|
<div className="col-span-full text-center py-12 text-gray-400 bg-white/5 rounded-xl border border-white/10">
|
|
{t('noClasses')}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |