131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
/**
|
|
* File: AppDataView.tsx
|
|
* Created by: AI Assistant
|
|
* Date: 2025-11-29
|
|
* Purpose: Data view component for kreatiVortex platform
|
|
* Part of: kreatiVortex - Platform Pembelajaran Tari Online
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import React from 'react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface Column<T> {
|
|
key: keyof T;
|
|
label: string;
|
|
render?: (value: T[keyof T], row: T) => React.ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
interface AppDataViewProps<T> {
|
|
data: T[];
|
|
columns: Column<T>[];
|
|
loading?: boolean;
|
|
className?: string;
|
|
emptyMessage?: string;
|
|
}
|
|
|
|
function AppDataView<T extends Record<string, unknown>>({
|
|
data,
|
|
columns,
|
|
loading = false,
|
|
className,
|
|
emptyMessage = 'Tidak ada data tersedia'
|
|
}: AppDataViewProps<T>) {
|
|
if (loading) {
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Skeleton rows */}
|
|
{Array.from({ length: 5 }).map((_, index) => (
|
|
<div key={index} className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
|
|
<div className="space-y-4">
|
|
<div className="h-4 bg-gray-600/20 rounded animate-pulse"></div>
|
|
<div className="h-4 bg-gray-600/20 rounded animate-pulse"></div>
|
|
<div className="h-4 bg-gray-600/20 rounded animate-pulse w-3/4"></div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (data.length === 0) {
|
|
return (
|
|
<div className={cn(
|
|
'bg-white/10 backdrop-blur-sm rounded-xl p-12 border border-white/20 text-center',
|
|
className
|
|
)}>
|
|
<div className="w-16 h-16 bg-gray-600/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-lg font-medium text-gray-300 mb-2">{emptyMessage}</h3>
|
|
<p className="text-gray-400">Coba ubah filter atau tambah data baru</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={cn(
|
|
'bg-white/10 backdrop-blur-sm rounded-xl border border-white/20 overflow-hidden',
|
|
className
|
|
)}>
|
|
{/* Table for desktop */}
|
|
<div className="hidden lg:block overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr className="border-b border-white/10">
|
|
{columns.map((column) => (
|
|
<th
|
|
key={column.key as string}
|
|
className={cn(
|
|
'px-6 py-4 text-left text-xs font-medium text-gray-300 uppercase tracking-wider',
|
|
column.className
|
|
)}
|
|
>
|
|
{column.label}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-white/10">
|
|
{data.map((row, index) => (
|
|
<tr key={index} className="hover:bg-white/5 transition-colors">
|
|
{columns.map((column) => (
|
|
<td
|
|
key={column.key as string}
|
|
className="px-6 py-4 whitespace-nowrap text-sm text-gray-300"
|
|
>
|
|
{column.render ? column.render(row[column.key], row) : String(row[column.key] || '')}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* Cards for mobile */}
|
|
<div className="lg:hidden space-y-4 p-4">
|
|
{data.map((row, index) => (
|
|
<div key={index} className="bg-white/5 rounded-lg p-4 border border-white/10">
|
|
{columns.map((column) => (
|
|
<div key={column.key as string} className="mb-3">
|
|
<div className="text-xs font-medium text-gray-400 uppercase tracking-wider mb-1">
|
|
{column.label}
|
|
</div>
|
|
<div className="text-sm text-gray-300">
|
|
{column.render ? column.render(row[column.key], row) : String(row[column.key] || '')}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default AppDataView; |