From 93df14169c3a00d1e6afe4b03b089627069a7984 Mon Sep 17 00:00:00 2001 From: Hafid Nur <73023445+hafidnrzs@users.noreply.github.com> Date: Tue, 9 Dec 2025 22:20:41 +0700 Subject: [PATCH] feat(backoffice): teams page integration - Get teams data from API - Hide filter that doesn't exists in back-end - Simplify modal according to the back-end --- .../_components/modal-team-detail-new.tsx | 592 +++++------------- .../app/(protected)/hackathon-teams/page.tsx | 563 +++++------------ 2 files changed, 314 insertions(+), 841 deletions(-) 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 index 68d119a..b2f7201 100644 --- 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 @@ -1,63 +1,24 @@ import { FC, useState, useEffect, useMemo, useRef } from 'react'; import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { cn } from '@imphnen-frontend-service/utils'; -import TeamBannerPlaceholder from './team-banner-placeholder'; import { CityFilterSelect } from '../../../../components/city-filter-select'; +import TeamBannerPlaceholder from './team-banner-placeholder'; +import { cn } from '@imphnen-frontend-service/utils'; +import { TAdminTeamItem } from '@imphnen-frontend-service/service'; import { TeamOutlined, - CalendarOutlined, - SaveOutlined, CloseOutlined, - ExclamationOutlined, - UserOutlined, DeleteOutlined, + SaveOutlined, + CalendarOutlined, + CrownOutlined, + ExclamationOutlined, + UploadOutlined, + CameraOutlined, EyeOutlined, EyeInvisibleOutlined, - CrownOutlined, - CheckCircleOutlined, - ClockCircleOutlined, - CloseCircleOutlined, - CameraOutlined, - UploadOutlined, } 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[]; -} +type TeamType = TAdminTeamItem; interface ModalProps { isOpen: boolean; @@ -68,7 +29,6 @@ interface ModalProps { const ModalTeamDetail: FC = ({ isOpen, onClose, team }) => { const [formData, setFormData] = useState(null); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); - const [activeTab, setActiveTab] = useState<'details' | 'members'>('details'); const [showLogoMenu, setShowLogoMenu] = useState(false); const logoInputRef = useRef(null); const bannerInputRef = useRef(null); @@ -77,24 +37,19 @@ const ModalTeamDetail: FC = ({ isOpen, onClose, team }) => { useEffect(() => { if (isOpen) { if (team) { - // Edit existing team setFormData({ ...team }); } else { - // Create new team setFormData({ - id: '', // Will be generated by backend + id: '', name: '', description: '', city: '', - banner: undefined, - logo: undefined, + banner: null, + logo: null, visibility: 'public', - member_count: 1, - has_submission: false, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), leader_id: '', - members: [], }); } } @@ -102,8 +57,7 @@ const ModalTeamDetail: FC = ({ isOpen, onClose, team }) => { // Check if form has changes const hasChanges = useMemo(() => { - if (!formData) return false; - if (!team) return true; // New team always has changes + if (!formData || !team) return !!formData; return ( formData.name !== team.name || formData.description !== team.description || @@ -117,37 +71,28 @@ const ModalTeamDetail: FC = ({ isOpen, onClose, team }) => { // Check if required fields are filled const isFormValid = useMemo(() => { if (!formData) return false; - return formData.name.trim() !== '' && formData.city.trim() !== ''; + return ( + formData.name.trim() !== '' && + formData.city.trim() !== '' && + formData.description.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' | undefined - ) => { + const handleInputChange = (field: keyof TeamType, value: string | null) => { 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(); @@ -155,56 +100,45 @@ const ModalTeamDetail: FC = ({ isOpen, onClose, team }) => { const handleLogoUpload = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; - if (file) { - if (!file.type.startsWith('image/')) { - alert('Please select an image file'); - return; - } - if (file.size > 5 * 1024 * 1024) { - alert('Image size must be less than 5MB'); - return; - } - const reader = new FileReader(); - reader.onload = (e) => { - const logoUrl = e.target?.result as string; - handleInputChange('logo', logoUrl); - setShowLogoMenu(false); - }; - reader.readAsDataURL(file); + if (!file) return; + + if (!file.type.startsWith('image/')) { + alert('Please select an image file'); + return; } + if (file.size > 5 * 1024 * 1024) { + alert('Image size must be less than 5MB'); + return; + } + + const reader = new FileReader(); + reader.onload = (e) => { + const logoUrl = e.target?.result as string; + handleInputChange('logo', logoUrl); + setShowLogoMenu(false); + }; + reader.readAsDataURL(file); }; const handleBannerUpload = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; - if (file) { - if (!file.type.startsWith('image/')) { - alert('Please select an image file'); - return; - } - if (file.size > 5 * 1024 * 1024) { - alert('Image size must be less than 5MB'); - return; - } - const reader = new FileReader(); - reader.onload = (e) => { - const bannerUrl = e.target?.result as string; - handleInputChange('banner', bannerUrl); - }; - reader.readAsDataURL(file); + if (!file) return; + + if (!file.type.startsWith('image/')) { + alert('Please select an image file'); + return; + } + if (file.size > 5 * 1024 * 1024) { + alert('Image size must be less than 5MB'); + return; } - }; - const handleRemoveLogo = () => { - handleInputChange('logo', undefined); - setShowLogoMenu(false); - }; - - const handleRemoveBanner = () => { - handleInputChange('banner', undefined); - }; - - const triggerLogoUpload = () => { - logoInputRef.current?.click(); + const reader = new FileReader(); + reader.onload = (e) => { + const bannerUrl = e.target?.result as string; + handleInputChange('banner', bannerUrl); + }; + reader.readAsDataURL(file); }; return ( @@ -240,7 +174,6 @@ const ModalTeamDetail: FC = ({ isOpen, onClose, team }) => { {/* Content */}
setShowLogoMenu(false)}> - {/* Hidden File Inputs */} = ({ isOpen, onClose, team }) => { className="hidden" /> - {/* Interactive Banner Section */} + {/* Banner Section */}
- {/* Banner Action Buttons */}
)}
- {/* Team Logo & Name Row */} + {/* Logo & Name */}
- {/* Interactive Team Logo */}
- {/* Logo Hover Overlay - Full circle */} - - {/* Logo Menu Dropdown */} {showLogoMenu && (
- {/* Team Name */}
- {/* Team Description */} + {/* Description */}