From 77433aee66c9f03ccb5a3da46784e9d60c3a7f76 Mon Sep 17 00:00:00 2001 From: raineer Date: Thu, 23 Oct 2025 00:43:02 +0800 Subject: [PATCH 01/10] =?UTF-8?q?ChecklistCreateModal=20opens=20=E2=86=92?= =?UTF-8?q?=20Select=20member=20=E2=86=92=20Fill=20form=20=E2=86=92=20Crea?= =?UTF-8?q?tepagination=20and=20monthly=20report?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/TeamDetailView.tsx | 67 +++- .../_components/ChecklistCreateModal.tsx | 360 ++++++++++++++++++ .../my-team/_components/MemberChecklist.tsx | 333 ++++++++-------- .../my-team/_components/MemberDetailModal.tsx | 2 +- apps/codebility/app/home/my-team/actions.ts | 320 ++++++++++++++++ apps/codebility/tree_output.txt | 18 +- 6 files changed, 925 insertions(+), 175 deletions(-) create mode 100644 apps/codebility/app/home/my-team/_components/ChecklistCreateModal.tsx create mode 100644 apps/codebility/app/home/my-team/actions.ts diff --git a/apps/codebility/app/home/my-team/[projectId]/_components/TeamDetailView.tsx b/apps/codebility/app/home/my-team/[projectId]/_components/TeamDetailView.tsx index 49a80e895..76e3ac190 100644 --- a/apps/codebility/app/home/my-team/[projectId]/_components/TeamDetailView.tsx +++ b/apps/codebility/app/home/my-team/[projectId]/_components/TeamDetailView.tsx @@ -10,8 +10,9 @@ import { } from "@/app/home/projects/actions"; import { Codev } from "@/types/home/codev"; import { Button } from "@/components/ui/button"; -import { Users, UserPlus, Table, Calendar, TrendingUp, Save, CalendarDays, Clock } from "lucide-react"; +import { Users, UserPlus, Table, Calendar, TrendingUp, Save, CalendarDays, Clock, CheckSquare } from "lucide-react"; import AddMembersModal from "../../AddMembersModal"; +import ChecklistCreateModal from "../../_components/ChecklistCreateModal"; // NEW import AttendanceGrid from "./AttendanceGrid"; import MeetingBasedAttendance from "./MeetingBasedAttendance"; import CompactMemberGrid from "./CompactMemberGrid"; @@ -41,10 +42,11 @@ interface TeamDetailViewProps { const TeamDetailView = ({ projectData }: TeamDetailViewProps) => { const [project, setProject] = useState(projectData); const [showAddModal, setShowAddModal] = useState(false); + const [showChecklistModal, setShowChecklistModal] = useState(false); // NEW const [isLoadingMembers, setIsLoadingMembers] = useState(false); const [viewMode, setViewMode] = useState<"team" | "attendance">("team"); const [hasAttendanceChanges, setHasAttendanceChanges] = useState(false); - const [useMeetingBasedAttendance, setUseMeetingBasedAttendance] = useState(true); // Use meeting-based attendance by default + const [useMeetingBasedAttendance, setUseMeetingBasedAttendance] = useState(true); const [showScheduleMeetingModal, setShowScheduleMeetingModal] = useState(false); const [currentSchedule, setCurrentSchedule] = useState<{ selectedDays: string[]; time: string } | null>(null); const [monthlyAttendancePoints, setMonthlyAttendancePoints] = useState<{ totalPoints: number; presentDays: number }>({ totalPoints: 0, presentDays: 0 }); @@ -85,7 +87,6 @@ const TeamDetailView = ({ projectData }: TeamDetailViewProps) => { } }; - // Only load on initial mount or when switching to team view if (viewMode === 'team') { loadAttendancePoints(); } @@ -121,11 +122,24 @@ const TeamDetailView = ({ projectData }: TeamDetailViewProps) => { setShowAddModal(false); }; + // NEW: Handle checklist modal + const handleOpenChecklistModal = () => { + setShowChecklistModal(true); + }; + + const handleCloseChecklistModal = () => { + setShowChecklistModal(false); + }; + + const handleChecklistSuccess = () => { + toast.success("Task created successfully!"); + handleCloseChecklistModal(); + }; + const handleSaveAttendance = useCallback(async () => { if (attendanceGridRef.current?.saveAllAttendance) { await attendanceGridRef.current.saveAllAttendance(); - // Refresh attendance points after saving const currentDate = new Date(); const result = await getTeamMonthlyAttendancePoints( projectInfo.id, @@ -146,7 +160,6 @@ const TeamDetailView = ({ projectData }: TeamDetailViewProps) => { try { setIsLoadingMembers(true); - // Get current team lead const teamLeadResult = await getTeamLead(projectInfo.id); const teamLead = teamLeadResult.data; @@ -154,7 +167,6 @@ const TeamDetailView = ({ projectData }: TeamDetailViewProps) => { throw new Error('Team leader not found'); } - // Prepare updated members array const updatedMembers = [ { ...teamLead, @@ -170,7 +182,6 @@ const TeamDetailView = ({ projectData }: TeamDetailViewProps) => { })), ]; - // Update project members const result = await updateProjectMembers( projectInfo.id, updatedMembers, @@ -180,7 +191,6 @@ const TeamDetailView = ({ projectData }: TeamDetailViewProps) => { if (result.success) { toast.success("Project members updated successfully."); - // Update local state const updatedProjectMembers: SimpleMemberData[] = selectedMembers .filter(member => member.id !== teamLead.id) .map(member => ({ @@ -212,10 +222,8 @@ const TeamDetailView = ({ projectData }: TeamDetailViewProps) => { }; const handleScheduleUpdate = async () => { - // Close modal and reload schedule setShowScheduleMeetingModal(false); - // Reload the schedule after a short delay to ensure the save is complete setTimeout(async () => { const result = await getMeetingSchedule(projectInfo.id); if (result.success && result.schedule) { @@ -229,7 +237,6 @@ const TeamDetailView = ({ projectData }: TeamDetailViewProps) => {
{/* Attendance Warning Banner */} {viewMode === "attendance" && ( - {
)} - {/* Header */} + {/* Header with Tab Buttons */}
+ {/* LEFT: View Mode Tabs + Checklist Button */}
+ {(isTeamLead || !currentUserId) && ( )} + + {/* NEW: Checklist Button - Only for team leads */} + {isTeamLead && ( + + )}
+ + {/* RIGHT: Action Buttons */}
{viewMode === "attendance" && ( <> @@ -408,7 +429,6 @@ const TeamDetailView = ({ projectData }: TeamDetailViewProps) => { members={members?.data || []} teamLead={teamLead?.data || null} projectId={projectInfo.id || null} - /> ) : (
@@ -480,6 +500,21 @@ const TeamDetailView = ({ projectData }: TeamDetailViewProps) => { teamLead={teamLead?.data || null} currentSchedule={currentSchedule} /> + + {/* NEW: Checklist Create Modal */} + {isTeamLead && ( + + )} ); }; diff --git a/apps/codebility/app/home/my-team/_components/ChecklistCreateModal.tsx b/apps/codebility/app/home/my-team/_components/ChecklistCreateModal.tsx new file mode 100644 index 000000000..d40ecaa4a --- /dev/null +++ b/apps/codebility/app/home/my-team/_components/ChecklistCreateModal.tsx @@ -0,0 +1,360 @@ +"use client"; + +import { useState, FormEvent } from "react"; +import { X, Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { SimpleMemberData } from "@/app/home/projects/actions"; +import { createChecklistItem } from "../actions"; +import toast from "react-hot-toast"; +import Image from "next/image"; +import DefaultAvatar from "@/components/DefaultAvatar"; + +// ✅ CORRECTED IMPORTS - Using existing project components +// Input component from forms directory +import Input from "@/components/ui/forms/input"; + +// Textarea component - using textarea-home.tsx +import { Textarea } from "@/components/ui/textarea-home"; + +// Label component - creating inline since it doesn't exist +const Label = ({ + children, + htmlFor, + className = "" +}: { + children: React.ReactNode; + htmlFor?: string; + className?: string; +}) => ( + +); + +interface ChecklistCreateModalProps { + isOpen: boolean; + projectId: string; + projectName: string; + teamMembers: SimpleMemberData[]; + onClose: () => void; + onSuccess: () => void; +} + +const PRIORITY_LEVELS = ["low", "medium", "high"] as const; + +const ChecklistCreateModal = ({ + isOpen, + projectId, + projectName, + teamMembers, + onClose, + onSuccess +}: ChecklistCreateModalProps) => { + const [formData, setFormData] = useState({ + memberId: "", + title: "", + description: "", + priority: "medium" as typeof PRIORITY_LEVELS[number], + due_date: "" + }); + const [isLoading, setIsLoading] = useState(false); + + // Format member name for display + const formatName = (firstName: string, lastName: string): string => + `${firstName.charAt(0).toUpperCase()}${firstName.slice(1).toLowerCase()} ${lastName.charAt(0).toUpperCase()}${lastName.slice(1).toLowerCase()}`; + + // Get selected member details + const selectedMember = teamMembers.find(m => m.id === formData.memberId); + + // Reset form when modal opens/closes + const handleClose = () => { + setFormData({ + memberId: "", + title: "", + description: "", + priority: "medium", + due_date: "" + }); + onClose(); + }; + + // Handle form submission + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + + // Validation + if (!formData.memberId) { + toast.error("Please select a team member"); + return; + } + + if (!formData.title.trim()) { + toast.error("Please enter a task title"); + return; + } + + setIsLoading(true); + + try { + // Create FormData object for server action + const submitData = new FormData(); + submitData.append("member_id", formData.memberId); + submitData.append("project_id", projectId); + submitData.append("title", formData.title.trim()); + submitData.append("description", formData.description.trim()); + submitData.append("priority", formData.priority); + if (formData.due_date) { + submitData.append("due_date", formData.due_date); + } + + // Call server action + const result = await createChecklistItem(submitData); + + if (result.success) { + toast.success(`Task created for ${selectedMember ? formatName(selectedMember.first_name, selectedMember.last_name) : 'team member'}!`); + onSuccess(); + handleClose(); + } else { + toast.error(result.error || "Failed to create task"); + } + } catch (error) { + console.error("Error creating checklist item:", error); + toast.error("Failed to create task"); + } finally { + setIsLoading(false); + } + }; + + return ( + + +
+ + + ✨ Create Task for Team Member + + + + {/* Project Info Banner */} +
+

+ Project: {projectName} +

+
+ +
+ {/* Member Selection */} +
+ + + + {/* Selected Member Preview */} + {selectedMember && ( +
+
+
+ {selectedMember.image_url ? ( + {formatName(selectedMember.first_name, + ) : ( + + )} +
+
+

+ Creating task for: {formatName(selectedMember.first_name, selectedMember.last_name)} +

+ {selectedMember.display_position && ( +

+ {selectedMember.display_position} +

+ )} +
+
+
+ )} +
+ + {/* Task Title */} +
+ + setFormData({ ...formData, title: e.target.value })} + placeholder="e.g., Complete onboarding documentation" + className="w-full bg-light-900 dark:bg-dark-200 dark:text-light-900 border border-gray-300 focus:border-blue-500 rounded-lg px-3 py-2" + required + disabled={isLoading} + /> +
+ + {/* Description */} +
+ +