diff --git a/package.json b/package.json
index 44dae91..28c53b7 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,8 @@
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-slot": "^1.1.1",
+ "@refinedev/core": "^4.57.5",
+ "@refinedev/simple-rest": "^5.0.10",
"@tanstack/react-query": "^5.65.1",
"axios": "^1.7.9",
"class-variance-authority": "^0.7.1",
diff --git a/src/app/(refine)/layout.tsx b/src/app/(refine)/layout.tsx
new file mode 100644
index 0000000..7dd3ec2
--- /dev/null
+++ b/src/app/(refine)/layout.tsx
@@ -0,0 +1,38 @@
+'use client';
+
+import { Refine } from '@refinedev/core';
+import dataProvider from '@refinedev/simple-rest';
+import axios from 'axios';
+import { config } from '@/app/config';
+
+// Create axios instance with auth headers
+const axiosInstance = axios.create({
+ baseURL: config.baseURL + '/api',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+});
+
+axiosInstance.interceptors.request.use((config) => {
+ const token =
+ typeof window !== 'undefined' ? localStorage.getItem('auth-data') : null;
+ if (token) {
+ const parsedToken = JSON.parse(token);
+ config.headers.Authorization = `Bearer ${parsedToken.accessToken}`;
+ }
+ return config;
+});
+
+// Create authenticated data provider
+const customDataProvider = dataProvider(config.baseURL + '/api', axiosInstance);
+
+
+export default function Layout({ children }: React.PropsWithChildren) {
+ return (
+
+ {children}
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/(refine)/refine/_components/checkInList.tsx b/src/app/(refine)/refine/_components/checkInList.tsx
new file mode 100644
index 0000000..aea5875
--- /dev/null
+++ b/src/app/(refine)/refine/_components/checkInList.tsx
@@ -0,0 +1,70 @@
+// components/CheckInList.tsx
+'use client';
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+import dayjs from 'dayjs';
+import { User } from '../libs/interface';
+
+export const CheckInList = ({
+ checkIns,
+ page,
+ pageSize,
+ setPage,
+}: {
+ checkIns: User[];
+ page: number;
+ pageSize: number;
+ setPage: (page: number) => void;
+}) => {
+ const totalPages = Math.ceil(checkIns.length / pageSize);
+ const paginatedCheckIns = checkIns.slice((page - 1) * pageSize, page * pageSize);
+
+ return (
+
+
+
+ เช็คอินวันนี้: {checkIns.length} คน
+
+
+
+
+
+ {checkIns.length > pageSize && (
+
+
+
+ หน้า {page} จาก {totalPages}
+
+
+
+
+
+
+
+ )}
+
+ );
+};
\ No newline at end of file
diff --git a/src/app/(refine)/refine/_components/doughnutChart.tsx b/src/app/(refine)/refine/_components/doughnutChart.tsx
new file mode 100644
index 0000000..b0f6314
--- /dev/null
+++ b/src/app/(refine)/refine/_components/doughnutChart.tsx
@@ -0,0 +1,15 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+// components/DoughnutChart.tsx
+'use client';
+import { Doughnut } from 'react-chartjs-2';
+import { ChartOptions } from 'chart.js';
+
+export const DoughnutChart = ({
+ data,
+ options,
+}: {
+ data: any;
+ options: ChartOptions<'doughnut'>;
+}) => {
+ return ;
+};
\ No newline at end of file
diff --git a/src/app/(refine)/refine/_components/header.tsx b/src/app/(refine)/refine/_components/header.tsx
new file mode 100644
index 0000000..4cbc2f7
--- /dev/null
+++ b/src/app/(refine)/refine/_components/header.tsx
@@ -0,0 +1,35 @@
+// components/DashboardHeader.tsx
+'use client';
+import Link from 'next/link';
+import { ChevronLeft, Users } from 'lucide-react';
+
+export const DashboardHeader = ({
+ total,
+ children,
+}: {
+ total: number;
+ children: React.ReactNode;
+}) => (
+
+
+
+
+
+
+ แดชบอร์ดลงทะเบียน
+
+
+
+
+
+ ผู้ลงทะเบียนทั้งหมด: {total} คน
+
+
+
+
{children}
+
+
+);
\ No newline at end of file
diff --git a/src/app/(refine)/refine/_components/lineChart.tsx b/src/app/(refine)/refine/_components/lineChart.tsx
new file mode 100644
index 0000000..3da6933
--- /dev/null
+++ b/src/app/(refine)/refine/_components/lineChart.tsx
@@ -0,0 +1,15 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+// components/LineChart.tsx
+'use client';
+import { Line } from 'react-chartjs-2';
+import { ChartOptions } from 'chart.js';
+
+export const LineChart = ({
+ data,
+ options,
+}: {
+ data: any;
+ options: ChartOptions<'line'>;
+}) => {
+ return ;
+};
\ No newline at end of file
diff --git a/src/app/(refine)/refine/dashboard/_components/exportButton.tsx b/src/app/(refine)/refine/dashboard/_components/exportButton.tsx
new file mode 100644
index 0000000..a44eb53
--- /dev/null
+++ b/src/app/(refine)/refine/dashboard/_components/exportButton.tsx
@@ -0,0 +1,74 @@
+// components/ExportButton.tsx
+'use client';
+import { Download } from 'lucide-react';
+import { FieldEntry, User } from '../../libs/interface';
+import dayjs from 'dayjs';
+
+export const ExportButton = ({ data }: { data: User[] }) => {
+ // Define fields to export (all User fields except imageUrl)
+ const fieldEntries: FieldEntry[] = [
+ { label: 'ID', field: 'id' },
+ { label: 'UID', field: 'uid' },
+ { label: 'Name', field: 'name' },
+ { label: 'Email', field: 'email' },
+ { label: 'Phone', field: 'phone' },
+ { label: 'University', field: 'university' },
+ { label: 'Size Jersey', field: 'sizeJersey' },
+ { label: 'Food Limitation', field: 'foodLimitation' },
+ { label: 'Invitation Code', field: 'invitationCode' },
+ { label: 'Age', field: 'age' },
+ { label: 'Chronic Disease', field: 'chronicDisease' },
+ { label: 'Drug Allergy', field: 'drugAllergy' },
+ { label: 'Status', field: 'status' },
+ { label: 'Graduated Year', field: 'graduatedYear' },
+ { label: 'Faculty', field: 'faculty' },
+ { label: 'Last Entered', field: 'lastEntered' },
+ { label: 'Registered At', field: 'registeredAt' },
+ { label: 'Role', field: 'role' },
+ { label: 'Education', field: 'education' },
+ { label: 'Is Acrophobic', field: 'isAcrophobic' },
+ ];
+
+ const handleExportCSV = () => {
+ const headers = fieldEntries.map(f => f.label).join(',');
+
+ const csvContent = [
+ headers,
+ ...data.map(user =>
+ fieldEntries.map(({ field }) => {
+ const value = user[field];
+ // Handle date fields
+ if (field === 'lastEntered' || field === 'registeredAt') {
+ return value && dayjs(value.toString()).isValid()
+ ? dayjs(value.toString()).format('YYYY-MM-DD HH:mm')
+ : '';
+ }
+ // Escape quotes in string fields
+ return typeof value === 'string'
+ ? `"${value.replace(/"/g, '""')}"`
+ : value;
+ }).join(',')
+ )
+ ].join('\n');
+
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', `users_export_${dayjs().format('YYYYMMDD_HHmmss')}.csv`);
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/(refine)/refine/dashboard/_components/pagination.tsx b/src/app/(refine)/refine/dashboard/_components/pagination.tsx
new file mode 100644
index 0000000..784299c
--- /dev/null
+++ b/src/app/(refine)/refine/dashboard/_components/pagination.tsx
@@ -0,0 +1,126 @@
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+
+export const Pagination = ({
+ current,
+ totalPages,
+ pageSize,
+ totalItems,
+ setCurrent,
+ setPageSize,
+}: {
+ current: number;
+ totalPages: number;
+ pageSize: number;
+ totalItems: number;
+ setCurrent: (page: number) => void;
+ setPageSize: (size: number) => void;
+}) => {
+ const hasNext = current < totalPages;
+ const hasPrev = current > 1;
+
+ return (
+
+
+ แสดง {(current - 1) * pageSize + 1} -{' '}
+ {Math.min(current * pageSize, totalItems)} จาก {totalItems}
+
+
+
+
+
+
+ );
+};
+
+const PaginationControls = ({
+ current,
+ totalPages,
+ hasPrev,
+ hasNext,
+ setCurrent,
+}: {
+ current: number;
+ totalPages: number;
+ hasPrev: boolean;
+ hasNext: boolean;
+ setCurrent: (page: number) => void;
+}) => (
+ <>
+
+
+
+
+ {Array.from({ length: totalPages }, (_, i) => (
+
+ ))}
+
+
+
+
+ >
+);
+
+const PageSizeSelect = ({
+ pageSize,
+ setPageSize,
+}: {
+ pageSize: number;
+ setPageSize: (size: number) => void;
+}) => (
+
+ แสดงต่อหน้า:
+
+
+);
\ No newline at end of file
diff --git a/src/app/(refine)/refine/dashboard/_components/searchBar.tsx b/src/app/(refine)/refine/dashboard/_components/searchBar.tsx
new file mode 100644
index 0000000..5ba702b
--- /dev/null
+++ b/src/app/(refine)/refine/dashboard/_components/searchBar.tsx
@@ -0,0 +1,34 @@
+import { Search } from 'lucide-react';
+
+export const SearchBar = ({
+ searchTerm,
+ searchBy,
+ setSearchTerm,
+ setSearchBy,
+}: {
+ searchTerm: string;
+ searchBy: 'name' | 'uid';
+ setSearchTerm: (value: string) => void;
+ setSearchBy: (value: 'name' | 'uid') => void;
+}) => (
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+);
\ No newline at end of file
diff --git a/src/app/(refine)/refine/dashboard/_components/userTable.tsx b/src/app/(refine)/refine/dashboard/_components/userTable.tsx
new file mode 100644
index 0000000..b034881
--- /dev/null
+++ b/src/app/(refine)/refine/dashboard/_components/userTable.tsx
@@ -0,0 +1,20 @@
+export const UserTable = ({ children }: { children: React.ReactNode }) => (
+
+
+
+
+ | UID |
+ ชื่อ |
+ อีเมล |
+ สถานะ |
+ คณะ |
+ ขนาดเสื้อ |
+ เข้างานล่าสุด |
+
+
+
+ {children}
+
+
+
+ );
\ No newline at end of file
diff --git a/src/app/(refine)/refine/dashboard/_components/userTableRow.tsx b/src/app/(refine)/refine/dashboard/_components/userTableRow.tsx
new file mode 100644
index 0000000..09439c7
--- /dev/null
+++ b/src/app/(refine)/refine/dashboard/_components/userTableRow.tsx
@@ -0,0 +1,49 @@
+import dayjs from 'dayjs';
+import { User } from '../../libs/interface';
+
+export const UserTableRow = ({ user, onClick }: { user: User, onClick: () => void }) => {
+ const statusStyles = {
+ chula_student: 'bg-blue-600',
+ alumni: 'bg-pink-600',
+ general_student: 'bg-gray-600',
+ };
+ const style = statusStyles[user.status as keyof typeof statusStyles];
+
+ return (
+
+ |
+ {user.uid}
+ |
+
+ {user.name}
+ |
+
+ {user.email}
+ |
+
+
+ {
+ {
+ chula_student: 'นิสิตปัจจุบัน',
+ alumni: 'ศิษย์เก่า',
+ general_student: 'บุคคลทั่วไป',
+ }[user.status]
+ }
+
+ |
+
+ {user.faculty}
+ |
+
+ {user.sizeJersey}
+ |
+
+ {dayjs(user.lastEntered).isValid()
+ ? dayjs(user.lastEntered).format('DD/MM HH:mm')
+ : 'ยังไม่เข้างาน'}
+ |
+
+ );
+};
diff --git a/src/app/(refine)/refine/dashboard/page.tsx b/src/app/(refine)/refine/dashboard/page.tsx
new file mode 100644
index 0000000..b2203e9
--- /dev/null
+++ b/src/app/(refine)/refine/dashboard/page.tsx
@@ -0,0 +1,94 @@
+'use client';
+import { useTable } from '@refinedev/core';
+import { ChevronLeft } from 'lucide-react';
+import { useState, useMemo } from 'react';
+import Link from 'next/link';
+import { useRouter } from 'next/navigation';
+import { User } from '../libs/interface';
+import { SearchBar } from './_components/searchBar';
+import { UserTable } from './_components/userTable';
+import { UserTableRow } from './_components/userTableRow';
+import { Pagination } from './_components/pagination';
+import { ExportButton } from './_components/exportButton'; // Add this import
+
+const DataTable = () => {
+ const router = useRouter();
+ const [searchTerm, setSearchTerm] = useState('');
+ const [searchBy, setSearchBy] = useState<'name' | 'uid'>('name');
+ const PAGE_SIZE = 15;
+
+ const { tableQuery, current, setCurrent, pageSize, setPageSize } =
+ useTable({
+ resource: 'users',
+ pagination: {
+ mode: 'server',
+ current: 1,
+ pageSize: PAGE_SIZE,
+ },
+ });
+
+ const filteredData = useMemo(() => {
+ if (!tableQuery.data?.data) return [];
+ return tableQuery.data.data.filter(user =>
+ user[searchBy].toLowerCase().includes(searchTerm.toLowerCase()),
+ );
+ }, [tableQuery.data, searchTerm, searchBy]);
+
+ const paginatedData = useMemo(() => {
+ const start = (current - 1) * pageSize;
+ const end = start + pageSize;
+ return filteredData.slice(start, end);
+ }, [filteredData, current, pageSize]);
+
+ const totalItems = filteredData.length;
+ const totalPages = Math.ceil(totalItems / pageSize);
+
+ return (
+
+
+
+
+
+
+ ข้อมูลผู้ลงทะเบียนทั้งหมด
+
+
+
+
+
+ {/* Add ExportButton here */}
+
+
+
+
+ {paginatedData.map(user => (
+ router.push(`/refine/users/${user.id}`)}
+ />
+ ))}
+
+
+
+
+
+ );
+};
+
+export default DataTable;
\ No newline at end of file
diff --git a/src/app/(refine)/refine/libs/interface.ts b/src/app/(refine)/refine/libs/interface.ts
new file mode 100644
index 0000000..c022c02
--- /dev/null
+++ b/src/app/(refine)/refine/libs/interface.ts
@@ -0,0 +1,28 @@
+export interface User {
+ id: string;
+ uid: string;
+ name: string;
+ email: string;
+ phone: string;
+ university: string;
+ sizeJersey: string;
+ foodLimitation: string;
+ invitationCode: string;
+ age: string;
+ chronicDisease: string;
+ drugAllergy: string;
+ status: string;
+ graduatedYear: string;
+ faculty: string;
+ imageUrl: string;
+ lastEntered: string;
+ registeredAt: string;
+ role: string;
+ education: string;
+ isAcrophobic: boolean;
+}
+
+export type FieldEntry = {
+ label: string;
+ field: keyof User;
+};
diff --git a/src/app/(refine)/refine/libs/stat.ts b/src/app/(refine)/refine/libs/stat.ts
new file mode 100644
index 0000000..5b71ece
--- /dev/null
+++ b/src/app/(refine)/refine/libs/stat.ts
@@ -0,0 +1,48 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+// lib/stats.ts
+import dayjs from 'dayjs';
+import { User } from '../libs/interface';
+
+export const processStats = (data: User[]) => {
+ const today = dayjs().format('YYYY-MM-DD');
+
+ const statusStatistics = {
+ currentStudent: data.filter(u => u.status === 'chula_student').length,
+ alumni: data.filter(u => u.status === 'alumni').length,
+ generalPublic: data.filter(u => u.status === 'general_student').length,
+ otherStudents: data.filter(
+ u => !['chula_student', 'alumni', 'general_student'].includes(u.status),
+ ).length,
+ };
+
+ const dailyRegistrations = data.reduce((acc, user) => {
+ const date = dayjs(user.registeredAt).format('YYYY-MM-DD');
+ if (date) acc[date] = (acc[date] || 0) + 1;
+ return acc;
+ }, {} as Record);
+
+ const todaysCheckIns = data.filter(
+ user => dayjs(user.lastEntered).format('YYYY-MM-DD') === today,
+ );
+
+ return { statusStatistics, dailyRegistrations, todaysCheckIns };
+};
+
+export const chartOptions = {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ position: 'bottom' as const,
+ labels: {
+ font: { size: 14 },
+ usePointStyle: true,
+ },
+ },
+ tooltip: {
+ callbacks: {
+ label: (context: any) => `${context.dataset.label}: ${context.raw} คน`,
+ },
+ },
+ },
+};
\ No newline at end of file
diff --git a/src/app/(refine)/refine/page.tsx b/src/app/(refine)/refine/page.tsx
new file mode 100644
index 0000000..7332889
--- /dev/null
+++ b/src/app/(refine)/refine/page.tsx
@@ -0,0 +1,165 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+// app/dashboard/page.tsx
+'use client';
+import { useTable } from '@refinedev/core';
+import { useState } from 'react';
+import Link from 'next/link';
+import dayjs from 'dayjs';
+import { User } from './libs/interface';
+import { DashboardHeader } from '../refine/_components/header';
+import { LineChart } from '../refine/_components/lineChart';
+import { DoughnutChart } from '../refine/_components/doughnutChart';
+import { CheckInList } from '../refine/_components/checkInList';
+import { processStats, chartOptions } from '../refine/libs/stat';
+import { ScanLine, UserCheck, Users } from 'lucide-react';
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Title,
+ Tooltip,
+ Legend,
+ ArcElement,
+} from 'chart.js';
+
+const CHECK_IN_PAGE_SIZE = 5;
+
+// Register Chart.js components
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Title,
+ Tooltip,
+ Legend,
+ ArcElement
+);
+
+const Dashboard = () => {
+ const [checkInPage, setCheckInPage] = useState(1);
+ const { tableQuery } = useTable({ resource: 'users' });
+ const data = tableQuery?.data?.data || [];
+ const total = tableQuery?.data?.total || 0;
+
+ const { statusStatistics, dailyRegistrations, todaysCheckIns } = processStats(data as User[]);
+
+ // Chart data preparations
+ const doughnutData = {
+ labels: ['นิสิตปัจจุบัน', 'ศิษย์เก่า', 'บุคคลทั่วไป', 'นักศึกษาอื่น'],
+ datasets: [
+ {
+ label: 'จำนวนผู้ลงทะเบียน',
+ data: Object.values(statusStatistics),
+ backgroundColor: ['#708FCE', '#DF729F', '#9BCDEB', '#757575'],
+ borderWidth: 0,
+ },
+ ],
+ };
+
+ const lineData = {
+ labels: Object.keys(dailyRegistrations)
+ .sort()
+ .map(date => dayjs(date).format('DD/MM')),
+ datasets: [
+ {
+ label: 'ผู้ลงทะเบียน',
+ data: Object.values(dailyRegistrations),
+ borderColor: '#DF729F',
+ backgroundColor: '#E9B0CC',
+ tension: 0.4,
+ fill: true,
+ },
+ ],
+ };
+
+ return (
+
+
+
+ ดูข้อมูลทั้งหมด
+
+
+
+
+
+
+
+
+ แนวโน้มการลงทะเบียนรายวัน
+
+
+
+ `${value} คน`,
+ stepSize: 1,
+ color: '#6b7280',
+ },
+ grid: { color: '#f3f4f6' },
+ },
+ },
+ }}
+ />
+
+
+
+
+
+
+
สถิติตามสถานะ
+
+
+
+
+
+
+
+
+
+
+
สถานการณ์วันนี้
+
+
+
+
+ );
+};
+
+export default Dashboard;
\ No newline at end of file
diff --git a/src/app/(refine)/refine/users/[id]/_components/infoRow.tsx b/src/app/(refine)/refine/users/[id]/_components/infoRow.tsx
new file mode 100644
index 0000000..f903013
--- /dev/null
+++ b/src/app/(refine)/refine/users/[id]/_components/infoRow.tsx
@@ -0,0 +1,18 @@
+export const InfoRow = ({
+ label,
+ value,
+ onEdit,
+}: {
+ label: string;
+ value: string;
+ onEdit?: () => void;
+}) => (
+
+ {label}
+ {value}
+
+);
\ No newline at end of file
diff --git a/src/app/(refine)/refine/users/[id]/_components/userDetails.tsx b/src/app/(refine)/refine/users/[id]/_components/userDetails.tsx
new file mode 100644
index 0000000..5e7c53d
--- /dev/null
+++ b/src/app/(refine)/refine/users/[id]/_components/userDetails.tsx
@@ -0,0 +1,233 @@
+/* eslint-disable @next/next/no-img-element */
+'use client';
+import { useOne } from '@refinedev/core';
+import { User, FieldEntry } from '../../../libs/interface';
+import { useEffect, useState } from 'react';
+import { useRouter } from 'next/navigation';
+
+const formatThaiDate = (dateString: string) => {
+ const date = new Date(dateString);
+ date.setFullYear(date.getFullYear());
+ return date.toLocaleDateString('th-TH', {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric',
+ });
+};
+
+export default function UserDetails({ id }: { id: string }) {
+ const router = useRouter();
+ const [isImageOpen, setIsImageOpen] = useState(false);
+ const [image, setImage] = useState(null);
+ const [imageError, setImageError] = useState('');
+ const { data, isError, isLoading } = useOne({
+ resource: 'users',
+ id: id,
+ });
+
+ const { data: imageData, isError: isImageError } = useOne({
+ resource: 'users/image',
+ id: id,
+ });
+
+ const user = data?.data;
+
+ useEffect(() => {
+ if (isImageError) {
+ setImageError('คุณไม่มีสิทธิ์เข้าถึงภาพนี้');
+ setImage(null);
+ } else if (imageData?.data?.url) {
+ setImage(imageData.data.url);
+ setImageError('');
+ }
+ }, [imageData, isImageError]);
+
+ const createField = (label: string, field: keyof User): FieldEntry => ({
+ label,
+ field,
+ });
+
+ const renderSection = (title: string, fields: FieldEntry[]) => (
+
+
{title}
+
+ {fields.map(({ label, field }) => {
+ const value = user?.[field];
+ let displayValue = value || 'N/A';
+
+ if (field === 'registeredAt') {
+ displayValue = formatThaiDate(value as string);
+ } else if (field === 'graduatedYear') {
+ displayValue = (parseInt(value as string) + 543).toString();
+ } else if (field === 'status') {
+ displayValue = value as string;
+ } else if (field === 'isAcrophobic') {
+ displayValue = value ? 'ใช่' : 'ไม่';
+ }
+
+ return (
+
+ {label}
+ {displayValue}
+
+ );
+ })}
+
+
+ );
+
+ if (isLoading) return กำลังโหลด...
;
+ if (isError)
+ return (
+
+ เกิดข้อผิดพลาดในการโหลดข้อมูลผู้ใช้
+
+ );
+ if (!user) return ไม่พบผู้ใช้
;
+
+ return (
+
+ {/* Header */}
+
+
+
+
+ {/* Image Preview */}
+
+ setIsImageOpen(true)}
+ className="relative aspect-[4/3] w-full overflow-hidden rounded-2xl bg-gray-100 shadow-lg"
+ >
+ {imageError ? (
+
+ ) : (
+ image && (
+

+ )
+ )}
+
+
+
+ {/* Profile Information */}
+
+
+
{user.name}
+
+
+ {user.role}
+
+ {user.uid}
+
+
+
+
+ {renderSection('ข้อมูลส่วนตัว', [
+ createField('อีเมล', 'email'),
+ createField('เบอร์โทรศัพท์', 'phone'),
+ createField('สถานะ', 'status'),
+ createField('วันที่ลงทะเบียน', 'registeredAt'),
+ ])}
+
+ {renderSection('ข้อมูลการศึกษา', [
+ createField('มหาวิทยาลัย', 'university'),
+ createField('คณะ', 'faculty'),
+ createField('ระดับการศึกษา', 'education'),
+ createField('ปีที่จบการศึกษา', 'graduatedYear'),
+ ])}
+
+ {renderSection('ข้อมูลเพิ่มเติม', [
+ createField('ขนาดเสื้อ', 'sizeJersey'),
+ createField('ข้อจำกัดด้านอาหาร', 'foodLimitation'),
+ createField('กลัวความสูงไหม', 'isAcrophobic'),
+ ...(user.chronicDisease
+ ? [createField('โรคประจำตัว', 'chronicDisease')]
+ : []),
+ ...(user.drugAllergy
+ ? [createField('อาการแพ้ยา', 'drugAllergy')]
+ : []),
+ ])}
+
+
+
+ {/* Image Modal */}
+ {isImageOpen && (
+
setIsImageOpen(false)}
+ >
+
+
+
+ {image ? (
+

+ ) : (
+
+ {imageError}
+
+ )}
+
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/(refine)/refine/users/[id]/page.tsx b/src/app/(refine)/refine/users/[id]/page.tsx
new file mode 100644
index 0000000..936cb8e
--- /dev/null
+++ b/src/app/(refine)/refine/users/[id]/page.tsx
@@ -0,0 +1,9 @@
+// This is the server component that handles routing
+import UserDetails from './_components/userDetails';
+
+type tParams = Promise<{ id: string }>;
+
+export default async function Page(props: { params: tParams }) {
+ const { id } = await props.params;
+ return ;
+}
diff --git a/src/app/staff/qr/page.tsx b/src/app/staff/qr/page.tsx
index dba0abd..78e93bb 100644
--- a/src/app/staff/qr/page.tsx
+++ b/src/app/staff/qr/page.tsx
@@ -3,6 +3,7 @@ import TopPart from '@/components/staff/qr/toppart';
import Edit from '@/components/staff/qr/editbutton';
import QrButton from '@/components/staff/qr/qrbutton';
import Protect from '@/components/protect';
+import Refine from '@/components/staff/qr/refineButton';
export default function Home() {
return (
@@ -12,6 +13,7 @@ export default function Home() {
+
diff --git a/src/components/staff/qr/refineButton.tsx b/src/components/staff/qr/refineButton.tsx
new file mode 100644
index 0000000..d013616
--- /dev/null
+++ b/src/components/staff/qr/refineButton.tsx
@@ -0,0 +1,22 @@
+import { getImageURL } from '@/utils/image';
+import Image from 'next/image';
+import Link from 'next/link';
+
+export default function Refine() {
+ return (
+
+
+
+ );
+}