diff --git a/package.json b/package.json index af44072..2c460eb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "my-app", "version": "0.1.0", - "homepage": "/react/", + "homepage": "/", "private": true, "dependencies": { "@ckeditor/ckeditor5-build-classic": "^41.2.0", diff --git a/src/feature-module/projects/createproject.jsx b/src/feature-module/projects/createproject.jsx index fa00dcf..c79c4b1 100644 --- a/src/feature-module/projects/createproject.jsx +++ b/src/feature-module/projects/createproject.jsx @@ -1,14 +1,15 @@ -import React, { useState } from 'react'; -import { Link } from 'react-router-dom'; -import { DatePicker, Select, Input } from 'antd'; +import React, { useState, useEffect } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { DatePicker, Select, Input, message } from 'antd'; import { ArrowLeft, Calendar, Users, DollarSign, Target, - Clock, - FileText + FileText, + CheckCircle, + TrendingUp } from 'feather-icons-react'; import { LoadingButton } from '../../components/Loading'; import dayjs from 'dayjs'; @@ -17,24 +18,114 @@ const { Option } = Select; const { TextArea } = Input; const CreateProject = () => { + const navigate = useNavigate(); const [loading, setLoading] = useState(false); + const [categoriesLoading, setCategoriesLoading] = useState(true); + const [usersLoading, setUsersLoading] = useState(true); + const [formData, setFormData] = useState({ projectName: '', description: '', - category: '', + clientName: '', + categoryId: '', priority: 'medium', status: 'planning', startDate: dayjs(), endDate: dayjs().add(1, 'month'), budget: '', - client: '', - manager: [], + progressPercentage: 0, + managers: [], teamMembers: [], - tags: [], - attachments: [] + tags: [] }); const [errors, setErrors] = useState({}); + const [categories, setCategories] = useState([]); + const [users, setUsers] = useState([]); + + // Load categories from API + const loadCategories = async () => { + try { + const apiBaseUrl = process.env.REACT_APP_API_BASE_URL || ''; + const response = await fetch(`${apiBaseUrl}ProjectCategories`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + if (response.ok) { + const result = await response.json(); + setCategories(result.data || []); + } else { + console.error('Failed to load categories'); + // Fallback to sample data + setCategories([ + { id: 1, name: 'Web Development', color: 'blue' }, + { id: 2, name: 'Mobile App', color: 'green' }, + { id: 3, name: 'Design', color: 'purple' }, + { id: 4, name: 'Marketing', color: 'orange' } + ]); + } + } catch (error) { + console.error('Error loading categories:', error); + // Fallback to sample data + setCategories([ + { id: 1, name: 'Web Development', color: 'blue' }, + { id: 2, name: 'Mobile App', color: 'green' }, + { id: 3, name: 'Design', color: 'purple' }, + { id: 4, name: 'Marketing', color: 'orange' } + ]); + } finally { + setCategoriesLoading(false); + } + }; + + // Load users from API + const loadUsers = async () => { + try { + const apiBaseUrl = process.env.REACT_APP_API_BASE_URL || ''; + const response = await fetch(`${apiBaseUrl}Users`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + if (response.ok) { + const result = await response.json(); + setUsers(result.data || []); + } else { + console.error('Failed to load users'); + // Fallback to sample data + setUsers([ + { id: 1, fullName: 'John Smith', email: 'john@example.com' }, + { id: 2, fullName: 'Sarah Johnson', email: 'sarah@example.com' }, + { id: 3, fullName: 'Mike Wilson', email: 'mike@example.com' }, + { id: 4, fullName: 'Lisa Chen', email: 'lisa@example.com' } + ]); + } + } catch (error) { + console.error('Error loading users:', error); + // Fallback to sample data + setUsers([ + { id: 1, fullName: 'John Smith', email: 'john@example.com' }, + { id: 2, fullName: 'Sarah Johnson', email: 'sarah@example.com' }, + { id: 3, fullName: 'Mike Wilson', email: 'mike@example.com' }, + { id: 4, fullName: 'Lisa Chen', email: 'lisa@example.com' } + ]); + } finally { + setUsersLoading(false); + } + }; + + // Load data on component mount + useEffect(() => { + loadCategories(); + loadUsers(); + }, []); // Avatar component with initials fallback const UserAvatar = ({ initials, name }) => ( @@ -59,30 +150,17 @@ const CreateProject = () => { ); - // Sample data - const categories = [ - { value: 'web-development', label: 'Web Development', color: 'blue' }, - { value: 'mobile-app', label: 'Mobile App', color: 'green' }, - { value: 'design', label: 'Design', color: 'purple' }, - { value: 'marketing', label: 'Marketing', color: 'orange' }, - { value: 'devops', label: 'DevOps', color: 'cyan' }, - { value: 'data-science', label: 'Data Science', color: 'red' } - ]; + // Generate initials from name + const getInitials = (name) => { + return name + .split(' ') + .map(word => word.charAt(0)) + .join('') + .toUpperCase() + .substring(0, 2); + }; - const managers = [ - { value: 'john-smith', label: 'John Smith', initials: 'JS' }, - { value: 'sarah-johnson', label: 'Sarah Johnson', initials: 'SJ' }, - { value: 'mike-wilson', label: 'Mike Wilson', initials: 'MW' }, - { value: 'lisa-chen', label: 'Lisa Chen', initials: 'LC' } - ]; - const teamMembers = [ - { value: 'alex-rodriguez', label: 'Alex Rodriguez', initials: 'AR' }, - { value: 'maria-garcia', label: 'Maria Garcia', initials: 'MG' }, - { value: 'david-brown', label: 'David Brown', initials: 'DB' }, - { value: 'emma-davis', label: 'Emma Davis', initials: 'ED' }, - { value: 'james-miller', label: 'James Miller', initials: 'JM' } - ]; const handleInputChange = (field, value) => { setFormData(prev => ({ @@ -110,20 +188,22 @@ const CreateProject = () => { newErrors.description = 'Project description is required'; } - if (!formData.category) { - newErrors.category = 'Please select a category'; + if (!formData.categoryId) { + newErrors.categoryId = 'Please select a category'; } - if (!formData.client.trim()) { - newErrors.client = 'Client name is required'; + if (!formData.clientName.trim()) { + newErrors.clientName = 'Client name is required'; } if (!formData.budget.trim()) { newErrors.budget = 'Budget is required'; + } else if (isNaN(parseFloat(formData.budget))) { + newErrors.budget = 'Budget must be a valid number'; } - if (formData.manager.length === 0) { - newErrors.manager = 'Please assign at least one manager'; + if (formData.progressPercentage < 0 || formData.progressPercentage > 100) { + newErrors.progressPercentage = 'Progress must be between 0 and 100'; } if (dayjs(formData.endDate).isBefore(dayjs(formData.startDate))) { @@ -136,25 +216,61 @@ const CreateProject = () => { const handleSubmit = async (e) => { e.preventDefault(); - + if (!validateForm()) { return; } setLoading(true); - + try { - // Simulate API call - await new Promise(resolve => setTimeout(resolve, 2000)); - - console.log('Project created:', formData); - - // Reset form or redirect - alert('Project created successfully!'); - + const apiBaseUrl = process.env.REACT_APP_API_BASE_URL || ''; + + // Prepare data for API - Simple format + const projectData = { + projectName: formData.projectName, + description: formData.description, + clientName: formData.clientName, + categoryId: parseInt(formData.categoryId), + priority: formData.priority, + status: formData.status, + startDate: formData.startDate.format('YYYY-MM-DD'), + endDate: formData.endDate.format('YYYY-MM-DD'), + budget: parseFloat(formData.budget), + progressPercentage: parseInt(formData.progressPercentage) + }; + + console.log('Sending project data:', projectData); + + const response = await fetch(`${apiBaseUrl}Projects`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(projectData) + }); + + if (response.ok) { + const result = await response.json(); + console.log('Project created successfully:', result); + + message.success('Project created successfully!'); + + // Redirect to project list + setTimeout(() => { + navigate('/project-tracker'); + }, 1500); + + } else { + const errorData = await response.json(); + console.error('API Error:', errorData); + message.error(errorData.message || 'Failed to create project. Please try again.'); + } + } catch (error) { console.error('Error creating project:', error); - alert('Error creating project. Please try again.'); + message.error('Network error. Please check your connection and try again.'); } finally { setLoading(false); } @@ -215,12 +331,12 @@ const CreateProject = () => { handleInputChange('client', e.target.value)} + className={`form-control ${errors.clientName ? 'is-invalid' : ''}`} + value={formData.clientName} + onChange={(e) => handleInputChange('clientName', e.target.value)} placeholder="Enter client name" /> - {errors.client &&
{errors.client}
} + {errors.clientName &&
{errors.clientName}
} @@ -254,19 +370,20 @@ const CreateProject = () => {
- {errors.category &&
{errors.category}
} + {errors.categoryId &&
{errors.categoryId}
}
@@ -313,17 +430,17 @@ const CreateProject = () => {
- {/* Timeline & Budget */} + {/* Timeline, Budget & Progress */}
- +
-
Timeline & Budget
+
Timeline, Budget & Progress
-
+
{
-
+
{
-
+
@@ -368,6 +485,26 @@ const CreateProject = () => { {errors.budget &&
{errors.budget}
}
+ +
+
+ +
+ handleInputChange('progressPercentage', parseInt(e.target.value) || 0)} + placeholder="0" + min="0" + max="100" + /> + % +
+ {errors.progressPercentage &&
{errors.progressPercentage}
} + Enter progress from 0 to 100 +
+
@@ -383,25 +520,30 @@ const CreateProject = () => {
- + - {errors.manager &&
{errors.manager}
} + You can assign managers later
@@ -413,18 +555,24 @@ const CreateProject = () => { value={formData.teamMembers} onChange={(value) => handleInputChange('teamMembers', value)} className="project-select" - placeholder="Select team members" + placeholder="Select team members (Optional)" optionLabelProp="label" + loading={usersLoading} + allowClear > - {teamMembers.map(member => ( - ))} + You can assign team members later
@@ -440,6 +588,7 @@ const CreateProject = () => { loading={loading} loadingText="Creating Project..." className="create-project-btn" + icon={} > Create Project diff --git a/src/style/scss/components/_theme-customizer.scss b/src/style/scss/components/_theme-customizer.scss index 7358be9..f7525ab 100644 --- a/src/style/scss/components/_theme-customizer.scss +++ b/src/style/scss/components/_theme-customizer.scss @@ -1016,6 +1016,105 @@ } } +// ===== PROJECT FORM ENHANCEMENTS ===== +.create-project-btn { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border: none; + border-radius: 8px; + padding: 12px 24px; + font-weight: 600; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); + } +} + +.project-select { + .ant-select-selector { + border-radius: 8px; + border: 1px solid #e9ecef; + transition: all 0.3s ease; + + &:hover { + border-color: #667eea; + } + } + + &.ant-select-focused .ant-select-selector { + border-color: #667eea; + box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1); + } +} + +.project-date-picker { + border-radius: 8px; + border: 1px solid #e9ecef; + transition: all 0.3s ease; + + &:hover { + border-color: #667eea; + } + + &:focus { + border-color: #667eea; + box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1); + } +} + +.form-group-header { + display: flex; + align-items: center; + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 2px solid #f8f9fa; + + .form-group-icon { + width: 40px; + height: 40px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 15px; + color: white; + } + + h5 { + margin: 0; + color: #2c3e50; + font-weight: 600; + font-size: 18px; + } +} + +// Progress percentage field styling +.input-group { + .form-control[type="number"] { + text-align: center; + font-weight: 600; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + &[type="number"] { + -moz-appearance: textfield; + } + } + + .input-group-text { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + font-weight: 600; + } +} + // ===== ADVANCED MICRO-INTERACTIONS ===== // Floating button position adjustment when panel is open