import type { Project } from "@vtit-agent-coding/shared"; import { GitBranch, Info, Loader2, Plus, Settings, X } from "lucide-react"; import { useEffect, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { toast } from "sonner"; import { Button } from "../components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "../components/ui/dialog"; import { Input } from "../components/ui/input"; import { Label } from "../components/ui/label"; import { Textarea } from "../components/ui/textarea"; const DEFAULT_MODULES = ["NGUOI-DUNG", "KHOA-HOC", "BAI-HOC", "BAI-TAP", "DON-HANG", "THANH-TOAN", "BAO-CAO", "THONG-BAO", "HE-THONG", "TICH-HOP"]; export function ProjectsPage() { const [projects, setProjects] = useState<(Project & { board_id?: string })[]>([]); const [isLoading, setIsLoading] = useState(true); const [search, setSearch] = useState(""); const [isOpen, setIsOpen] = useState(false); // New project form state const [name, setName] = useState(""); const [code, setCode] = useState(""); const [isCodeModified, setIsCodeModified] = useState(false); const [domain, setDomain] = useState(""); const [description, setDescription] = useState(""); // Repository integration const [repoUrl, setRepoUrl] = useState(""); const [githubUrl, setGithubUrl] = useState(""); const [gitlabUrl, setGitlabUrl] = useState(""); const [gitlabProjectId, setGitlabProjectId] = useState(""); const [gitlabAccessToken, setGitlabAccessToken] = useState(""); const [gitlabWebhookSecret, setGitlabWebhookSecret] = useState(""); // Show detailed GitLab config if gitlab URL is typed const [showGitLabDetails, setShowGitLabDetails] = useState(false); // Modules list const [modules, setModules] = useState(DEFAULT_MODULES); const [newModuleInput, setNewModuleInput] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const navigate = useNavigate(); useEffect(() => { fetchProjects(); }, []); async function fetchProjects() { setIsLoading(true); try { const res = await fetch("/api/projects"); if (res.ok) { const data = await res.json(); setProjects(Array.isArray(data) ? data : []); } } catch (err) { console.error("Failed to fetch projects", err); } finally { setIsLoading(false); } } // Handle Project Name input changes & auto-slug Project Code const handleNameChange = (val: string) => { setName(val); if (!isCodeModified) { const slug = val .toUpperCase() .replace(/[^A-Z0-9\s-]/g, "") // remove special characters except spaces/hyphens .trim() .replace(/[\s_]+/g, "-"); // replace space/underscore with hyphens setCode(slug); } }; // Handle Project Code manual input const handleCodeChange = (val: string) => { setCode(val.toUpperCase().replace(/[\s_]+/g, "-")); setIsCodeModified(true); }; // Auto-fill project name/code when Repository URL is pasted/entered const handleRepoUrlChange = (val: string) => { setRepoUrl(val); const trimmedVal = val.trim(); let extractedName = ""; if (trimmedVal.includes("github.com")) { setGithubUrl(trimmedVal); setGitlabUrl(""); setShowGitLabDetails(false); const match = trimmedVal.match(/github\.com\/([^/]+)\/([^/.]+)/); if (match?.[2]) { extractedName = match[2]; } } else if (trimmedVal.includes("gitlab")) { setGitlabUrl(trimmedVal); setGithubUrl(""); setShowGitLabDetails(true); const match = trimmedVal.match(/gitlab(?:[^/]*)\/([^/]+)\/([^/.]+)/); if (match?.[2]) { extractedName = match[2]; } } else { setGithubUrl(""); setGitlabUrl(""); setShowGitLabDetails(false); } if (extractedName && !name) { const parsedName = extractedName .split(/[-_]+/) .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" "); setName(parsedName); if (!isCodeModified) { setCode(extractedName.toUpperCase().replace(/[^A-Z0-9-]/g, "-")); } } }; // Add a module chip const handleAddModule = () => { const trimmed = newModuleInput .trim() .toUpperCase() .replace(/[\s_]+/g, "-"); if (trimmed && !modules.includes(trimmed)) { setModules([...modules, trimmed]); setNewModuleInput(""); } }; // Delete a module chip const handleRemoveModule = (modToRemove: string) => { setModules(modules.filter((m) => m !== modToRemove)); }; async function handleCreateProject(e: React.FormEvent) { e.preventDefault(); const trimmedCode = code.trim().toUpperCase(); const trimmedName = name.trim(); if (!trimmedCode) { toast.error("Mã dự án không được để trống."); return; } if (!trimmedName) { toast.error("Tên dự án không được để trống."); return; } setIsSubmitting(true); try { const res = await fetch("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ code: trimmedCode, name: trimmedName, domain: domain.trim() || null, github_url: githubUrl.trim() || null, gitlab_url: gitlabUrl.trim() || null, gitlab_project_id: gitlabProjectId.trim() || null, gitlab_access_token: gitlabAccessToken.trim() || null, gitlab_webhook_secret: gitlabWebhookSecret.trim() || null, description: description.trim() || null, modules: modules, }), }); if (res.ok) { const newProj = (await res.json()) as Project & { board_id?: string }; toast.success(`Khởi tạo dự án ${newProj.name} thành công!`); setIsOpen(false); // Reset form state setName(""); setCode(""); setIsCodeModified(false); setDomain(""); setDescription(""); setRepoUrl(""); setGithubUrl(""); setGitlabUrl(""); setGitlabProjectId(""); setGitlabAccessToken(""); setGitlabWebhookSecret(""); setShowGitLabDetails(false); setModules(DEFAULT_MODULES); const targetUrl = newProj.board_id ? `/boards/${newProj.board_id}` : `/projects/${newProj.id}`; navigate(targetUrl); } else { const data = (await res.json().catch(() => ({}))) as Record; const msg = data.error?.message || data.message || "Tạo dự án thất bại. Vui lòng kiểm tra lại thông tin."; toast.error(msg); } } catch (err) { console.error("Failed to create project", err); toast.error("Không thể kết nối tới máy chủ. Vui lòng thử lại sau."); } finally { setIsSubmitting(false); } } const safeProjects = Array.isArray(projects) ? projects : []; const filteredProjects = safeProjects.filter( (p) => p?.name && (p.name.toLowerCase().includes(search.toLowerCase()) || p.code?.toLowerCase().includes(search.toLowerCase())), ); return (

Dự Án Doanh Nghiệp

Quản lý tập trung Master Plan, Kanban, Rules, Tài liệu & Tích hợp GitLab/GitHub Enterprise

Khởi Tạo Dự Án Mới
{/* Section 1: Basic Info */}
Thông tin cơ bản
handleNameChange(e.target.value)} required className="mt-1" />
handleCodeChange(e.target.value)} required className="mt-1 font-mono uppercase" />
setDomain(e.target.value)} className="mt-1" />
{/* Section 2: Git Repo Integration */}
Tích hợp Repository
handleRepoUrlChange(e.target.value)} className="mt-1" />

Hệ thống tự động phân tích tên và mã dự án khi paste link repository hợp lệ.

{showGitLabDetails && (
Cấu hình GitLab Enterprise
setGitlabProjectId(e.target.value)} className="mt-1" />
setGitlabAccessToken(e.target.value)} className="mt-1" />
setGitlabWebhookSecret(e.target.value)} className="mt-1" />
)}
{/* Section 3: Modules Configuration */}
{modules.map((mod) => ( {mod} ))} {modules.length === 0 && Chưa có phân hệ nào.}
setNewModuleInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && (e.preventDefault(), handleAddModule())} className="text-xs" />
{/* Section 4: Description */}