feat: use tasks/supports/meetings endpoints instead of issues

This commit is contained in:
Zhi
2026-03-12 22:09:45 +00:00
parent 0f525effb8
commit 75802ba4dd

View File

@@ -1,15 +1,32 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom' import { useParams, useNavigate } from 'react-router-dom'
import api from '@/services/api' import api from '@/services/api'
import type { Milestone, MilestoneProgress, MilestoneItems, Issue } from '@/types' import type { Milestone, MilestoneProgress, Issue } from '@/types'
import dayjs from 'dayjs' import dayjs from 'dayjs'
interface Task {
id: number
title: string
description?: string
status: string
task_code?: string
task_status?: string
estimated_effort?: number
estimated_working_time?: string
started_on?: string
finished_on?: string
assignee_id?: number
created_at: string
}
export default function MilestoneDetailPage() { export default function MilestoneDetailPage() {
const { id } = useParams() const { id } = useParams()
const navigate = useNavigate() const navigate = useNavigate()
const [milestone, setMilestone] = useState<Milestone | null>(null) const [milestone, setMilestone] = useState<Milestone | null>(null)
const [progress, setProgress] = useState<MilestoneProgress | null>(null) const [progress, setProgress] = useState<MilestoneProgress | null>(null)
const [items, setItems] = useState<MilestoneItems | null>(null) const [tasks, setTasks] = useState<Task[]>([])
const [supports, setSupports] = useState<Issue[]>([])
const [meetings, setMeetings] = useState<Issue[]>([])
const [activeTab, setActiveTab] = useState<'tasks' | 'supports' | 'meetings'>('tasks') const [activeTab, setActiveTab] = useState<'tasks' | 'supports' | 'meetings'>('tasks')
const [showCreateTask, setShowCreateTask] = useState(false) const [showCreateTask, setShowCreateTask] = useState(false)
const [showCreateSupport, setShowCreateSupport] = useState(false) const [showCreateSupport, setShowCreateSupport] = useState(false)
@@ -18,44 +35,56 @@ export default function MilestoneDetailPage() {
const [newDesc, setNewDesc] = useState('') const [newDesc, setNewDesc] = useState('')
const [newEffort, setNewEffort] = useState(5) const [newEffort, setNewEffort] = useState(5)
const [newTime, setNewTime] = useState('09:00') const [newTime, setNewTime] = useState('09:00')
const [projectCode, setProjectCode] = useState('')
useEffect(() => { useEffect(() => {
api.get<Milestone>(`/milestones/${id}`).then(({ data }) => setMilestone(data)) api.get<Milestone>(`/milestones/${id}`).then(({ data }) => {
setMilestone(data)
// Get project_code from project
if (data.project_id) {
api.get(`/projects/${data.project_id}`).then(({ data: proj }) => {
setProjectCode(proj.project_code || '')
})
}
})
api.get<MilestoneProgress>(`/milestones/${id}/progress`).then(({ data }) => setProgress(data)).catch(() => {}) api.get<MilestoneProgress>(`/milestones/${id}/progress`).then(({ data }) => setProgress(data)).catch(() => {})
api.get<MilestoneItems>(`/milestones/${id}/items`).then(({ data }) => setItems(data)).catch(() => {})
}, [id]) }, [id])
useEffect(() => {
if (!projectCode || !id) return
api.get<Task[]>(`/tasks/${projectCode}/${id}`).then(({ data }) => setTasks(data)).catch(() => {})
api.get<Issue[]>(`/supports/${projectCode}/${id}`).then(({ data }) => setSupports(data)).catch(() => {})
api.get<Issue[]>(`/meetings/${projectCode}/${id}`).then(({ data }) => setMeetings(data)).catch(() => {})
}, [projectCode, id])
const createItem = async (type: 'tasks' | 'supports' | 'meetings') => { const createItem = async (type: 'tasks' | 'supports' | 'meetings') => {
if (!newTitle.trim()) return if (!newTitle.trim() || !projectCode) return
const payload: any = { const payload: any = {
title: newTitle, title: newTitle,
description: newDesc || null, description: newDesc || null,
status: 'open',
priority: 'medium'
} }
if (type === 'tasks') { if (type === 'tasks') {
payload.estimated_effort = newEffort payload.estimated_effort = newEffort
payload.estimated_working_time = newTime payload.estimated_working_time = newTime
} }
await api.post(`/milestones/${id}/${type}`, payload) await api.post(`/${type}/${projectCode}/${id}`, payload)
setNewTitle('') setNewTitle('')
setNewDesc('') setNewDesc('')
setShowCreateTask(false) setShowCreateTask(false)
setShowCreateSupport(false) setShowCreateSupport(false)
setShowCreateMeeting(false) setShowCreateMeeting(false)
api.get<MilestoneItems>(`/milestones/${id}/items`).then(({ data }) => setItems(data)) // Refresh
api.get<Task[]>(`/tasks/${projectCode}/${id}`).then(({ data }) => setTasks(data))
api.get<Issue[]>(`/supports/${projectCode}/${id}`).then(({ data }) => setSupports(data))
api.get<Issue[]>(`/meetings/${projectCode}/${id}`).then(({ data }) => setMeetings(data))
} }
const isProgressing = milestone?.status === 'progressing' const isProgressing = milestone?.status === 'progressing'
if (!milestone) return <div className="loading">Loading...</div> if (!milestone) return <div className="loading">Loading...</div>
const tasks = items?.tasks || [] const renderTaskRow = (t: Task) => (
const supports = items?.supports || [] <tr key={t.id} className="clickable" onClick={() => navigate(`/tasks/${projectCode}/${id}/${t.id}`)}>
const meetings = items?.meetings || []
const renderTaskRow = (t: any) => (
<tr key={t.id} className="clickable" onClick={() => navigate(`/issues/${t.id}`)}>
<td>{t.task_code || t.id}</td> <td>{t.task_code || t.id}</td>
<td className="issue-title">{t.title}</td> <td className="issue-title">{t.title}</td>
<td><span className={`badge status-${t.task_status || t.status}`}>{t.task_status || t.status}</span></td> <td><span className={`badge status-${t.task_status || t.status}`}>{t.task_status || t.status}</span></td>
@@ -175,8 +204,8 @@ export default function MilestoneDetailPage() {
<table> <table>
<thead><tr><th>#</th><th>Title</th><th>Status</th><th>Priority</th></tr></thead> <thead><tr><th>#</th><th>Title</th><th>Status</th><th>Priority</th></tr></thead>
<tbody> <tbody>
{supports.map((i: any) => ( {supports.map((i) => (
<tr key={i.id} className="clickable" onClick={() => navigate(`/issues/${i.id}`)}> <tr key={i.id} className="clickable" onClick={() => navigate(`/supports/${projectCode}/${id}/${i.id}`)}>
<td>{i.id}</td> <td>{i.id}</td>
<td className="issue-title">{i.title}</td> <td className="issue-title">{i.title}</td>
<td><span className={`badge status-${i.status}`}>{i.status}</span></td> <td><span className={`badge status-${i.status}`}>{i.status}</span></td>
@@ -192,8 +221,8 @@ export default function MilestoneDetailPage() {
<table> <table>
<thead><tr><th>#</th><th>Title</th><th>Status</th><th>Priority</th></tr></thead> <thead><tr><th>#</th><th>Title</th><th>Status</th><th>Priority</th></tr></thead>
<tbody> <tbody>
{meetings.map((i: any) => ( {meetings.map((i) => (
<tr key={i.id} className="clickable" onClick={() => navigate(`/issues/${i.id}`)}> <tr key={i.id} className="clickable" onClick={() => navigate(`/meetings/${projectCode}/${id}/${i.id}`)}>
<td>{i.id}</td> <td>{i.id}</td>
<td className="issue-title">{i.title}</td> <td className="issue-title">{i.title}</td>
<td><span className={`badge status-${i.status}`}>{i.status}</span></td> <td><span className={`badge status-${i.status}`}>{i.status}</span></td>