Spaces:
Build error
Build error
| import { useState, useEffect } from 'react' | |
| import { motion, AnimatePresence } from 'framer-motion' | |
| import { | |
| FaFolder, FaFolderOpen, FaFile, FaFileCode, FaFileAlt, FaImage, | |
| FaVideo, FaMusic, FaArchive, FaDatabase, FaCloud, FaGitAlt, | |
| FaPlus, FaMinus, FaEdit, FaTrash, FaCopy, FaMove, FaDownload, | |
| FaUpload, FaSearch, FaFilter, FaSort, FaSortAmountDown, | |
| FaSortAmountUp, FaEye, FaEyeSlash, FaLock, FaUnlock, | |
| FaShare, FaLink, FaStar, FaRegStar, FaClock, FaCalendar, | |
| FaUser, FaUsers, FaTag, FaTags, FaFlag, FaBookmark, | |
| FaCheck, FaTimes, FaInfo, FaWarning, FaExclamationTriangle, | |
| FaCheckCircle, FaTimesCircle, FaPlay, FaPause, FaStop, | |
| FaRedo, FaUndo, FaSave, FaPrint, FaSync, FaRefresh, | |
| FaSpinner, FaCog, Fa wrench, FaTools, FaMagic, FaRocket, | |
| FaCode, FaDatabase, FaServer, FaCloud, FaShieldAlt, | |
| FaChartLine, FaProjectDiagram, FaBug, FaTest, FaFlask, | |
| FaBook, FaGraduationCap, FaLightbulb, FaLightbulbAlt, | |
| FaFire, FaBolt, FaZap, FaAtom, FaDna, FaMicroscope, | |
| FaTelescope, FaSatellite, FaRocket, FaSpace, FaGlobe, | |
| FaMap, FaMapMarked, FaMapMarkedAlt, FaCompass, FaRoute, | |
| FaNavigator, FaSailboat, FaShip, FaAnchor, FaBuoy, | |
| FaWater, FaWind, FaCloudRain, FaCloudSun, FaSun, | |
| FaMoon, FaStar, FaRegStar, FaStarHalf, FaStarAndCrescent, | |
| FaMeteor, FaComet, FaPlanet, FaRing, FaOrbital, | |
| FaBlackHole, FaGalaxy, FaNebula, FaConstellation, FaAstronomy | |
| } from 'react-icons/fa' | |
| import toast from 'react-hot-toast' | |
| export default function ProjectManager({ language, theme }) { | |
| const [projects, setProjects] = useState([]) | |
| const [selectedProject, setSelectedProject] = useState(null) | |
| const [files, setFiles] = useState([]) | |
| const [viewMode, setViewMode] = useState('grid') | |
| const [sortBy, setSortBy] = useState('name') | |
| const [sortOrder, setSortOrder] = useState('asc') | |
| const [searchQuery, setSearchQuery] = useState('') | |
| const [showHidden, setShowHidden] = useState(false) | |
| const [selectedFiles, setSelectedFiles] = useState(new Set()) | |
| useEffect(() => { | |
| // Initialize with sample projects | |
| setProjects([ | |
| { | |
| id: 1, | |
| name: 'E-Commerce Platform', | |
| type: 'web', | |
| status: 'active', | |
| progress: 75, | |
| lastModified: new Date(), | |
| size: '125 MB', | |
| files: 234, | |
| stars: 5, | |
| tags: ['react', 'nodejs', 'mongodb'], | |
| description: 'Full-stack e-commerce solution' | |
| }, | |
| { | |
| id: 2, | |
| name: 'Mobile Banking App', | |
| type: 'mobile', | |
| status: 'development', | |
| progress: 45, | |
| lastModified: new Date(), | |
| size: '89 MB', | |
| files: 156, | |
| stars: 4, | |
| tags: ['react-native', 'firebase', 'typescript'], | |
| description: 'Secure mobile banking application' | |
| }, | |
| { | |
| id: 3, | |
| name: 'AI Data Pipeline', | |
| type: 'data', | |
| status: 'testing', | |
| progress: 90, | |
| lastModified: new Date(), | |
| size: '2.3 GB', | |
| files: 412, | |
| stars: 5, | |
| tags: ['python', 'tensorflow', 'aws'], | |
| description: 'Machine learning data processing pipeline' | |
| } | |
| ]) | |
| }, []) | |
| const createProject = () => { | |
| const newProject = { | |
| id: Date.now(), | |
| name: `New Project ${projects.length + 1}`, | |
| type: 'web', | |
| status: 'planning', | |
| progress: 0, | |
| lastModified: new Date(), | |
| size: '0 MB', | |
| files: 0, | |
| stars: 0, | |
| tags: [], | |
| description: 'New project description' | |
| } | |
| setProjects([...projects, newProject]) | |
| setSelectedProject(newProject) | |
| toast.success(language === 'fa' ? 'پروژه ایجاد شد' : 'Project created') | |
| } | |
| const deleteProject = (id) => { | |
| setProjects(projects.filter(p => p.id !== id)) | |
| if (selectedProject?.id === id) { | |
| setSelectedProject(null) | |
| } | |
| toast.success(language === 'fa' ? 'پروژه حذف شد' : 'Project deleted') | |
| } | |
| const duplicateProject = (project) => { | |
| const duplicate = { | |
| ...project, | |
| id: Date.now(), | |
| name: `${project.name} (Copy)`, | |
| lastModified: new Date() | |
| } | |
| setProjects([...projects, duplicate]) | |
| toast.success(language === 'fa' ? 'پروژه کپی شد' : 'Project duplicated') | |
| } | |
| const getFileIcon = (fileName) => { | |
| const ext = fileName.split('.').pop().toLowerCase() | |
| if (['js', 'jsx', 'ts', 'tsx', 'py', 'java', 'cpp', 'c'].includes(ext)) return FaFileCode | |
| if (['txt', 'md', 'doc', 'docx', 'pdf'].includes(ext)) return FaFileAlt | |
| if (['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'].includes(ext)) return FaImage | |
| if (['mp4', 'avi', 'mov', 'webm'].includes(ext)) return FaVideo | |
| if (['mp3', 'wav', 'ogg', 'm4a'].includes(ext)) return FaMusic | |
| if (['zip', 'rar', '7z', 'tar', 'gz'].includes(ext)) return FaArchive | |
| if (['sql', 'db', 'sqlite'].includes(ext)) return FaDatabase | |
| return FaFile | |
| } | |
| const getStatusColor = (status) => { | |
| switch (status) { | |
| case 'active': return 'text-green-400' | |
| case 'development': return 'text-blue-400' | |
| case 'testing': return 'text-yellow-400' | |
| case 'planning': return 'text-purple-400' | |
| case 'completed': return 'text-emerald-400' | |
| default: return 'text-gray-400' | |
| } | |
| } | |
| const sortProjects = (projects) => { | |
| return [...projects].sort((a, b) => { | |
| let comparison = 0 | |
| switch (sortBy) { | |
| case 'name': | |
| comparison = a.name.localeCompare(b.name) | |
| break | |
| case 'date': | |
| comparison = a.lastModified - b.lastModified | |
| break | |
| case 'size': | |
| comparison = parseFloat(a.size) - parseFloat(b.size) | |
| break | |
| case 'progress': | |
| comparison = a.progress - b.progress | |
| break | |
| case 'files': | |
| comparison = a.files - b.files | |
| break | |
| default: | |
| comparison = 0 | |
| } | |
| return sortOrder === 'asc' ? comparison : -comparison | |
| }) | |
| } | |
| const filteredProjects = sortProjects(projects).filter(project => | |
| project.name.toLowerCase().includes(searchQuery.toLowerCase()) || | |
| project.description.toLowerCase().includes(searchQuery.toLowerCase()) || | |
| project.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())) | |
| ) | |
| return ( | |
| <div className="flex flex-col h-full space-y-4"> | |
| {/* Header */} | |
| <div className="flex items-center justify-between p-4 bg-gray-800 rounded-lg"> | |
| <div className="flex items-center space-x-reverse space-x-3"> | |
| <FaProjectDiagram className="w-6 h-6 text-primary-400" /> | |
| <h2 className="text-lg font-semibold">Project Manager</h2> | |
| </div> | |
| <div className="flex items-center space-x-reverse space-x-2"> | |
| <button | |
| onClick={createProject} | |
| className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-all flex items-center space-x-reverse space-x-2" | |
| > | |
| <FaPlus className="w-4 h-4" /> | |
| <span>{language === 'fa' ? 'پروژه جدید' : 'New Project'}</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div className="flex-1 flex space-x-reverse space-x-4 min-h-0"> | |
| {/* Projects List */} | |
| <div className="w-96 bg-gray-800 rounded-lg p-4 space-y-4 overflow-y-auto"> | |
| {/* Search and Filter */} | |
| <div className="space-y-3"> | |
| <div className="relative"> | |
| <FaSearch className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" /> | |
| <input | |
| type="text" | |
| value={searchQuery} | |
| onChange={(e) => setSearchQuery(e.target.value)} | |
| placeholder="Search projects..." | |
| className="w-full pr-10 px-3 py-2 bg-gray-700 text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500" | |
| /> | |
| </div> | |
| <div className="flex items-center space-x-reverse space-x-2"> | |
| <select | |
| value={sortBy} | |
| onChange={(e) => setSortBy(e.target.value)} | |
| className="flex-1 px-3 py-2 bg-gray-700 text-white rounded-lg focus:outline-none" | |
| > | |
| <option value="name">Name</option> | |
| <option value="date">Date</option> | |
| <option value="size">Size</option> | |
| <option value="progress">Progress</option> | |
| <option value="files">Files</option> | |
| </select> | |
| <button | |
| onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')} | |
| className="p-2 bg-gray-700 text-white rounded-lg hover:bg-gray-600 transition-all" | |
| > | |
| {sortOrder === 'asc' ? <FaSortAmountUp className="w-4 h-4" /> : <FaSortAmountDown className="w-4 h-4" />} | |
| </button> | |
| </div> | |
| </div> | |
| {/* Projects Grid */} | |
| <div className="space-y-2"> | |
| <AnimatePresence> | |
| {filteredProjects.map(project => ( | |
| <motion.div | |
| key={project.id} | |
| initial={{ opacity: 0, x: -20 }} | |
| animate={{ opacity: 1, x: 0 }} | |
| exit={{ opacity: 0, x: -20 }} | |
| onClick={() => setSelectedProject(project)} | |
| className={`p-3 rounded-lg cursor-pointer transition-all ${ | |
| selectedProject?.id === project.id | |
| ? 'bg-primary-600 text-white' | |
| : 'bg-gray-700 text-gray-300 hover:bg-gray-600' | |
| }`} | |
| > | |
| <div className="flex items-start justify-between"> | |
| <div className="flex-1"> | |
| <div className="flex items-center space-x-reverse space-x-2"> | |
| <FaFolder className="w-4 h-4" /> | |
| <h3 className="font-medium text-sm">{project.name}</h3> | |
| </div> | |
| <p className="text-xs opacity-75 mt-1">{project.description}</p> | |
| <div className=" |