feat: implement soft delete, task editing fixes, and UI improvements
Backend: - Add soft delete for spaces and projects (is_active flag) - Add status_id and assignee_id to TaskUpdate schema - Fix task PATCH endpoint to update status and assignee - Add validation for assignee_id and status_id in task updates - Fix health service to count tasks with "Blocked" status as blockers - Filter out deleted spaces/projects from health dashboard - Add workload cache invalidation on assignee changes Frontend: - Add delete confirmation dialogs for spaces and projects - Fix UserSelect to display selected user name (valueName prop) - Fix task detail modal to refresh data after save - Enforce 2-level subtask depth limit in UI - Fix timezone bug in date formatting (use local timezone) - Convert NotificationBell from Tailwind to inline styles - Add i18n translations for health, workload, settings pages - Add parent_task_id to Task interface across components OpenSpec: - Archive add-delete-capability change 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ProjectHealthItem, RiskLevel, ScheduleStatus, ResourceStatus } from '../services/projectHealth'
|
||||
|
||||
interface ProjectHealthCardProps {
|
||||
@@ -13,31 +14,32 @@ function getHealthScoreColor(score: number): string {
|
||||
return '#f44336' // Red
|
||||
}
|
||||
|
||||
// Risk level colors and labels
|
||||
const riskLevelConfig: Record<RiskLevel, { color: string; bgColor: string; label: string }> = {
|
||||
low: { color: '#2e7d32', bgColor: '#e8f5e9', label: 'Low Risk' },
|
||||
medium: { color: '#f57c00', bgColor: '#fff3e0', label: 'Medium Risk' },
|
||||
high: { color: '#d84315', bgColor: '#fbe9e7', label: 'High Risk' },
|
||||
critical: { color: '#c62828', bgColor: '#ffebee', label: 'Critical' },
|
||||
// Risk level colors
|
||||
const riskLevelColors: Record<RiskLevel, { color: string; bgColor: string }> = {
|
||||
low: { color: '#2e7d32', bgColor: '#e8f5e9' },
|
||||
medium: { color: '#f57c00', bgColor: '#fff3e0' },
|
||||
high: { color: '#d84315', bgColor: '#fbe9e7' },
|
||||
critical: { color: '#c62828', bgColor: '#ffebee' },
|
||||
}
|
||||
|
||||
// Schedule status labels
|
||||
const scheduleStatusLabels: Record<ScheduleStatus, string> = {
|
||||
on_track: 'On Track',
|
||||
at_risk: 'At Risk',
|
||||
delayed: 'Delayed',
|
||||
// Schedule status translation keys
|
||||
const scheduleStatusKeys: Record<ScheduleStatus, string> = {
|
||||
on_track: 'status.onTrack',
|
||||
at_risk: 'status.atRisk',
|
||||
delayed: 'status.delayed',
|
||||
}
|
||||
|
||||
// Resource status labels
|
||||
const resourceStatusLabels: Record<ResourceStatus, string> = {
|
||||
adequate: 'Adequate',
|
||||
constrained: 'Constrained',
|
||||
overloaded: 'Overloaded',
|
||||
// Resource status translation keys
|
||||
const resourceStatusKeys: Record<ResourceStatus, string> = {
|
||||
adequate: 'resourceStatus.adequate',
|
||||
constrained: 'resourceStatus.constrained',
|
||||
overloaded: 'resourceStatus.overloaded',
|
||||
}
|
||||
|
||||
export function ProjectHealthCard({ project, onClick }: ProjectHealthCardProps) {
|
||||
const { t } = useTranslation('health')
|
||||
const healthColor = getHealthScoreColor(project.health_score)
|
||||
const riskConfig = riskLevelConfig[project.risk_level]
|
||||
const riskColors = riskLevelColors[project.risk_level]
|
||||
const progressPercent = project.task_count > 0
|
||||
? Math.round((project.completed_task_count / project.task_count) * 100)
|
||||
: 0
|
||||
@@ -72,11 +74,11 @@ export function ProjectHealthCard({ project, onClick }: ProjectHealthCardProps)
|
||||
<div
|
||||
style={{
|
||||
...styles.riskBadge,
|
||||
color: riskConfig.color,
|
||||
backgroundColor: riskConfig.bgColor,
|
||||
color: riskColors.color,
|
||||
backgroundColor: riskColors.bgColor,
|
||||
}}
|
||||
>
|
||||
{riskConfig.label}
|
||||
{t(`riskLevel.${project.risk_level}`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -110,25 +112,25 @@ export function ProjectHealthCard({ project, onClick }: ProjectHealthCardProps)
|
||||
<span style={{ ...styles.scoreValue, color: healthColor }}>
|
||||
{project.health_score}
|
||||
</span>
|
||||
<span style={styles.scoreLabel}>Health</span>
|
||||
<span style={styles.scoreLabel}>{t('card.health')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={styles.statusSection}>
|
||||
<div style={styles.statusItem}>
|
||||
<span style={styles.statusLabel}>Schedule</span>
|
||||
<span style={styles.statusLabel}>{t('card.schedule')}</span>
|
||||
<span style={styles.statusValue}>
|
||||
{scheduleStatusLabels[project.schedule_status]}
|
||||
{t(scheduleStatusKeys[project.schedule_status])}
|
||||
</span>
|
||||
</div>
|
||||
<div style={styles.statusItem}>
|
||||
<span style={styles.statusLabel}>Resources</span>
|
||||
<span style={styles.statusLabel}>{t('card.resources')}</span>
|
||||
<span style={styles.statusValue}>
|
||||
{resourceStatusLabels[project.resource_status]}
|
||||
{t(resourceStatusKeys[project.resource_status])}
|
||||
</span>
|
||||
</div>
|
||||
{project.owner_name && (
|
||||
<div style={styles.statusItem}>
|
||||
<span style={styles.statusLabel}>Owner</span>
|
||||
<span style={styles.statusLabel}>{t('card.owner')}</span>
|
||||
<span style={styles.statusValue}>{project.owner_name}</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -138,7 +140,7 @@ export function ProjectHealthCard({ project, onClick }: ProjectHealthCardProps)
|
||||
{/* Task Progress */}
|
||||
<div style={styles.progressSection}>
|
||||
<div style={styles.progressHeader}>
|
||||
<span style={styles.progressLabel}>Task Progress</span>
|
||||
<span style={styles.progressLabel}>{t('card.taskProgress')}</span>
|
||||
<span style={styles.progressValue}>
|
||||
{project.completed_task_count} / {project.task_count}
|
||||
</span>
|
||||
@@ -158,17 +160,17 @@ export function ProjectHealthCard({ project, onClick }: ProjectHealthCardProps)
|
||||
<div style={styles.metricsSection}>
|
||||
<div style={styles.metricItem}>
|
||||
<span style={styles.metricValue}>{project.blocker_count}</span>
|
||||
<span style={styles.metricLabel}>Blockers</span>
|
||||
<span style={styles.metricLabel}>{t('card.blockers')}</span>
|
||||
</div>
|
||||
<div style={styles.metricItem}>
|
||||
<span style={{ ...styles.metricValue, color: project.overdue_task_count > 0 ? '#f44336' : 'inherit' }}>
|
||||
{project.overdue_task_count}
|
||||
</span>
|
||||
<span style={styles.metricLabel}>Overdue</span>
|
||||
<span style={styles.metricLabel}>{t('card.overdue')}</span>
|
||||
</div>
|
||||
<div style={styles.metricItem}>
|
||||
<span style={styles.metricValue}>{progressPercent}%</span>
|
||||
<span style={styles.metricLabel}>Complete</span>
|
||||
<span style={styles.metricLabel}>{t('card.complete')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user