Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect } from 'react'; | |
| import { EmployeeProjectDetail } from '../../types'; | |
| import { Button } from '../ui/button'; | |
| import { Input } from '../ui/input'; | |
| import { Textarea } from '../ui/textarea'; | |
| import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; | |
| import { Label } from '../ui/label'; | |
| import { format, parse } from 'date-fns'; | |
| import { toast } from 'sonner'; | |
| import { SlateRichTextEditor } from '@/components/custom/SlateRichTextEditor'; | |
| interface EmployeeProjectFormProps { | |
| project?: EmployeeProjectDetail; | |
| employeeId: number; | |
| onSubmit: (data: Partial<EmployeeProjectDetail>) => Promise<void>; | |
| onCancel: () => void; | |
| } | |
| const EmployeeProjectForm: React.FC<EmployeeProjectFormProps> = ({ | |
| project, | |
| employeeId, | |
| onSubmit, | |
| onCancel | |
| }) => { | |
| const isEditing = !!project; | |
| const [formData, setFormData] = useState<Partial<EmployeeProjectDetail>>({ | |
| employeeId: employeeId, | |
| projectName: '', | |
| clientCompany: '', | |
| startDate: format(new Date(), 'yyyy-MM-dd'), | |
| endDate: format(new Date(), 'yyyy-MM-dd'), | |
| technologiesUsed: '', | |
| role: '', | |
| responsibilities: '', | |
| projectDetails: '', | |
| sector: '' | |
| }); | |
| // Track rich text values separately to ensure proper re-rendering | |
| const [responsibilitiesValue, setResponsibilitiesValue] = useState(''); | |
| const [projectDetailsValue, setProjectDetailsValue] = useState(''); | |
| const [loading, setLoading] = useState(false); | |
| const [errors, setErrors] = useState<Record<string, string>>({}); | |
| useEffect(() => { | |
| if (project) { | |
| setFormData({ | |
| ...project, | |
| startDate: project.startDate ? project.startDate.substring(0, 10) : format(new Date(), 'yyyy-MM-dd'), | |
| endDate: project.endDate ? project.endDate.substring(0, 10) : format(new Date(), 'yyyy-MM-dd') | |
| }); | |
| // Set rich text field values | |
| setResponsibilitiesValue(project.responsibilities || ''); | |
| setProjectDetailsValue(project.projectDetails || ''); | |
| console.log('Loaded project for editing:', { | |
| responsibilities: project.responsibilities, | |
| projectDetails: project.projectDetails | |
| }); | |
| } | |
| }, [project]); | |
| const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { | |
| const { name, value } = e.target; | |
| setFormData(prev => ({ ...prev, [name]: value })); | |
| // Clear error when field is edited | |
| if (errors[name]) { | |
| setErrors(prev => ({ ...prev, [name]: '' })); | |
| } | |
| }; | |
| const handleRichTextChange = (field: string, value: string) => { | |
| // Update form data | |
| setFormData(prev => ({ ...prev, [field]: value })); | |
| // Also update the specific field state for re-rendering | |
| if (field === 'responsibilities') { | |
| setResponsibilitiesValue(value); | |
| } else if (field === 'projectDetails') { | |
| setProjectDetailsValue(value); | |
| } | |
| // Clear error if present | |
| if (errors[field]) { | |
| setErrors(prev => ({ ...prev, [field]: '' })); | |
| } | |
| }; | |
| const validateForm = (): boolean => { | |
| const newErrors: Record<string, string> = {}; | |
| if (!formData.projectName?.trim()) { | |
| newErrors.projectName = 'Project name is required'; | |
| } | |
| if (!formData.clientCompany?.trim()) { | |
| newErrors.clientCompany = 'Client company is required'; | |
| } | |
| if (!formData.role?.trim()) { | |
| newErrors.role = 'Role is required'; | |
| } | |
| if (!formData.startDate) { | |
| newErrors.startDate = 'Start date is required'; | |
| } | |
| if (!formData.endDate) { | |
| newErrors.endDate = 'End date is required'; | |
| } | |
| if (formData.startDate && formData.endDate && new Date(formData.startDate) > new Date(formData.endDate)) { | |
| newErrors.endDate = 'End date must be after start date'; | |
| } | |
| setErrors(newErrors); | |
| return Object.keys(newErrors).length === 0; | |
| }; | |
| // Format a date string to UTC ISO format required by PostgreSQL | |
| const formatDateToUTC = (dateString: string): string => { | |
| // Parse the date in format 'yyyy-MM-dd' to a Date object | |
| const [year, month, day] = dateString.split('-').map(Number); | |
| // Create a new Date object with UTC time | |
| const date = new Date(Date.UTC(year, month - 1, day)); | |
| // Return ISO string with time component set to midnight UTC | |
| return date.toISOString(); | |
| }; | |
| const handleSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| if (!validateForm()) { | |
| toast.error('Please correct the errors in the form'); | |
| return; | |
| } | |
| setLoading(true); | |
| try { | |
| // Create a copy of form data with properly formatted dates | |
| const formattedData = { | |
| ...formData, | |
| // Format dates as UTC ISO strings for PostgreSQL timestamp with time zone | |
| startDate: formData.startDate ? formatDateToUTC(formData.startDate as string) : undefined, | |
| endDate: formData.endDate ? formatDateToUTC(formData.endDate as string) : undefined | |
| }; | |
| await onSubmit(formattedData); | |
| toast.success(`Project ${isEditing ? 'updated' : 'created'} successfully`); | |
| } catch (error) { | |
| console.error('Error submitting form:', error); | |
| toast.error(`Failed to ${isEditing ? 'update' : 'create'} project`); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| return ( | |
| <Card className="w-full"> | |
| <CardHeader> | |
| <CardTitle>{isEditing ? 'Edit Project' : 'Add New Project'}</CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <form onSubmit={handleSubmit} className="space-y-4"> | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="projectName">Project Name*</Label> | |
| <Input | |
| id="projectName" | |
| name="projectName" | |
| value={formData.projectName} | |
| onChange={handleChange} | |
| required | |
| /> | |
| {errors.projectName && <p className="text-red-500 text-sm">{errors.projectName}</p>} | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="clientCompany">Client Company*</Label> | |
| <Input | |
| id="clientCompany" | |
| name="clientCompany" | |
| value={formData.clientCompany} | |
| onChange={handleChange} | |
| required | |
| /> | |
| {errors.clientCompany && <p className="text-red-500 text-sm">{errors.clientCompany}</p>} | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="startDate">Start Date*</Label> | |
| <Input | |
| id="startDate" | |
| name="startDate" | |
| type="date" | |
| value={formData.startDate} | |
| onChange={handleChange} | |
| required | |
| /> | |
| {errors.startDate && <p className="text-red-500 text-sm">{errors.startDate}</p>} | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="endDate">End Date*</Label> | |
| <Input | |
| id="endDate" | |
| name="endDate" | |
| type="date" | |
| value={formData.endDate} | |
| onChange={handleChange} | |
| required | |
| /> | |
| {errors.endDate && <p className="text-red-500 text-sm">{errors.endDate}</p>} | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="role">Role*</Label> | |
| <Input | |
| id="role" | |
| name="role" | |
| value={formData.role} | |
| onChange={handleChange} | |
| required | |
| /> | |
| {errors.role && <p className="text-red-500 text-sm">{errors.role}</p>} | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="sector">Sector</Label> | |
| <Input | |
| id="sector" | |
| name="sector" | |
| value={formData.sector} | |
| onChange={handleChange} | |
| /> | |
| </div> | |
| <div className="space-y-2 md:col-span-2"> | |
| <Label htmlFor="technologiesUsed">Technologies Used</Label> | |
| <Input | |
| id="technologiesUsed" | |
| name="technologiesUsed" | |
| value={formData.technologiesUsed} | |
| onChange={handleChange} | |
| placeholder="e.g. React, Node.js, MongoDB" | |
| /> | |
| </div> | |
| <div className="space-y-2 md:col-span-2"> | |
| <Label htmlFor="projectDetails">Project Details</Label> | |
| <div className="border rounded-md"> | |
| <SlateRichTextEditor | |
| key={`project-details-${isEditing ? 'edit' : 'new'}-${projectDetailsValue ? 'has-content' : 'empty'}`} | |
| initialValue={projectDetailsValue} | |
| onChange={(value) => handleRichTextChange('projectDetails', value)} | |
| placeholder="Describe project details, objectives, and achievements" | |
| minHeight="150px" | |
| /> | |
| </div> | |
| </div> | |
| <div className="space-y-2 md:col-span-2"> | |
| <Label htmlFor="responsibilities">Responsibilities</Label> | |
| <div className="border rounded-md"> | |
| <SlateRichTextEditor | |
| key={`responsibilities-${isEditing ? 'edit' : 'new'}-${responsibilitiesValue ? 'has-content' : 'empty'}`} | |
| initialValue={responsibilitiesValue} | |
| onChange={(value) => handleRichTextChange('responsibilities', value)} | |
| placeholder="Describe your responsibilities in this project" | |
| minHeight="150px" | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="flex justify-end space-x-2 pt-4"> | |
| <Button type="button" variant="outline" onClick={onCancel} disabled={loading}> | |
| Cancel | |
| </Button> | |
| <Button type="submit" disabled={loading}> | |
| {loading ? 'Saving...' : isEditing ? 'Update Project' : 'Add Project'} | |
| </Button> | |
| </div> | |
| </form> | |
| </CardContent> | |
| </Card> | |
| ); | |
| }; | |
| export default EmployeeProjectForm; |