pmtool / src /components /employee-projects /EmployeeProjectForm.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
10.6 kB
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;