From fbc8e0b16ab28ddd0fb8995d31ac43f5265e55ee Mon Sep 17 00:00:00 2001 From: Hafid Nur <73023445+hafidnrzs@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:42:45 +0700 Subject: [PATCH] feat(backoffice): update hackathon team management page - add modal for manage team, add new team, and view project submission - reorganize the table column and data table --- .../_components/modal-team-detail-new.tsx | 651 ++++++++++++++ .../_components/submission-modal.tsx | 218 +++++ .../_components/team-banner-placeholder.tsx | 82 ++ .../app/(protected)/hackathon-teams/page.tsx | 832 ++++++++++++++---- 4 files changed, 1607 insertions(+), 176 deletions(-) create mode 100644 apps/backoffice/src/app/(protected)/hackathon-teams/_components/modal-team-detail-new.tsx create mode 100644 apps/backoffice/src/app/(protected)/hackathon-teams/_components/submission-modal.tsx create mode 100644 apps/backoffice/src/app/(protected)/hackathon-teams/_components/team-banner-placeholder.tsx diff --git a/apps/backoffice/src/app/(protected)/hackathon-teams/_components/modal-team-detail-new.tsx b/apps/backoffice/src/app/(protected)/hackathon-teams/_components/modal-team-detail-new.tsx new file mode 100644 index 0000000..1173250 --- /dev/null +++ b/apps/backoffice/src/app/(protected)/hackathon-teams/_components/modal-team-detail-new.tsx @@ -0,0 +1,651 @@ +import { FC, useState, useEffect, useMemo } from 'react'; +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { cn } from '@imphnen-frontend-service/utils'; +import TeamBannerPlaceholder from './team-banner-placeholder'; +import { + TeamOutlined, + CalendarOutlined, + SaveOutlined, + CloseOutlined, + ExclamationOutlined, + UserOutlined, + DeleteOutlined, + EyeOutlined, + EyeInvisibleOutlined, + CrownOutlined, + CheckCircleOutlined, + ClockCircleOutlined, + CloseCircleOutlined, +} from '@ant-design/icons'; + +interface TeamMember { + id: string; + joined_at: string; + role: 'leader' | 'member'; + status: 'pending' | 'accepted' | 'rejected'; + team_id: string; + user: { + avatar?: string; + bio?: string; + created_at: string; + email: string; + fullname: string; + id: string; + is_active: boolean; + location: string; + phone_number?: string; + skills: string[]; + updated_at: string; + }; + user_id: string; +} + +interface TeamType { + id: string; + name: string; + description?: string; + city: string; + banner?: string; + logo?: string; + visibility: 'public' | 'private'; + member_count: number; + has_submission: boolean; + created_at: string; + updated_at: string; + leader_id: string; + members: TeamMember[]; +} + +interface ModalProps { + isOpen: boolean; + onClose: () => void; + team: TeamType | null; +} + +const ModalTeamDetail: FC = ({ isOpen, onClose, team }) => { + const [formData, setFormData] = useState(null); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [activeTab, setActiveTab] = useState<'details' | 'members'>('details'); + + // Initialize form data when modal opens + useEffect(() => { + if (isOpen) { + if (team) { + // Edit existing team + setFormData({ ...team }); + } else { + // Create new team + setFormData({ + id: '', // Will be generated by backend + name: '', + description: '', + city: '', + banner: undefined, + logo: undefined, + visibility: 'public', + member_count: 1, + has_submission: false, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + leader_id: '', + members: [], + }); + } + } + }, [isOpen, team]); + + // Check if form has changes + const hasChanges = useMemo(() => { + if (!formData) return false; + if (!team) return true; // New team always has changes + return ( + formData.name !== team.name || + formData.description !== team.description || + formData.city !== team.city || + formData.visibility !== team.visibility + ); + }, [formData, team]); + + // Check if required fields are filled + const isFormValid = useMemo(() => { + if (!formData) return false; + return formData.name.trim() !== '' && formData.city.trim() !== ''; + }, [formData]); + + const canSave = hasChanges && isFormValid; + + // Get leader and other members + const leader = team?.members.find((m) => m.role === 'leader'); + const acceptedMembers = + team?.members.filter((m) => m.status === 'accepted') || []; + const pendingMembers = + team?.members.filter((m) => m.status === 'pending') || []; + + if (!isOpen || !formData) return null; + + const handleInputChange = ( + field: keyof TeamType, + value: string | boolean | 'public' | 'private' + ) => { + setFormData((prev) => (prev ? { ...prev, [field]: value } : null)); + }; + + const handleSave = () => { + if (!formData) return; + + console.log('Saving team:', formData); + onClose(); + }; + + const handleDelete = () => { + if (!team) return; + + console.log('Deleting team:', team.id); + setShowDeleteConfirm(false); + onClose(); + }; + + const cities = ['Jakarta', 'Bandung', 'Surabaya', 'Medan', 'Yogyakarta']; + + return ( +
+
+ {/* Header */} +
+
+
+ +
+
+

+ {team ? 'Team Details' : 'Create New Team'} +

+

+ {team + ? 'View and manage team information' + : 'Add a new team to the hackathon'} +

+
+
+ +
+ + {/* Content */} +
+ {/* Banner Section (for existing teams) */} + {team && ( +
+ + +
+ )} + + {/* Team Name */} +
+ + handleInputChange('name', e.target.value)} + /> +
+ + {/* Team Description */} +
+ +