slatesync-weekly-wonders / weekly_report.html
yinshouping's picture
### 周报内容结构 (report_content 解析后的JSON结构)
34f7734 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weekly Project Report | SlateSync</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<style>
.progress-ring__circle {
transition: stroke-dashoffset 0.5s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.badge {
@apply px-2 py-1 text-xs rounded-full;
}
.status-completed {
@apply bg-green-100 text-green-800;
}
.status-inprogress {
@apply bg-blue-100 text-blue-800;
}
.level-critical {
@apply bg-red-100 text-red-800;
}
.level-high {
@apply bg-orange-100 text-orange-800;
}
.level-medium {
@apply bg-yellow-100 text-yellow-800;
}
.level-low {
@apply bg-gray-100 text-gray-800;
}
</style>
</head>
<body class="bg-primary-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<!-- Report Header -->
<header class="mb-10">
<div class="flex justify-between items-center mb-6">
<div class="flex items-center">
<div class="w-10 h-10 rounded-full bg-secondary-500 flex items-center justify-center mr-3">
<i data-feather="file-text" class="text-white"></i>
</div>
<h1 class="text-3xl font-bold text-primary-800">Weekly Project Report</h1>
</div>
<div class="flex space-x-4">
<button class="bg-white hover:bg-primary-50 border border-primary-200 px-4 py-2 rounded-lg flex items-center transition">
<i data-feather="download" class="mr-2"></i>
Export PDF
</button>
<button class="bg-secondary-500 hover:bg-secondary-600 text-white px-4 py-2 rounded-lg flex items-center transition">
<i data-feather="share-2" class="mr-2"></i>
Share Report
</button>
</div>
</div>
<div class="bg-white rounded-xl shadow-md p-6">
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<div>
<h3 class="text-sm font-medium text-primary-400 mb-1">Report Period</h3>
<p class="text-primary-800 font-medium" id="report-period">Jun 5 - Jun 11, 2023</p>
</div>
<div>
<h3 class="text-sm font-medium text-primary-400 mb-1">Project</h3>
<p class="text-primary-800 font-medium" id="project-name">Website Redesign</p>
</div>
<div>
<h3 class="text-sm font-medium text-primary-400 mb-1">Team Members</h3>
<p class="text-primary-800 font-medium" id="team-members">8 (6 active)</p>
</div>
<div>
<h3 class="text-sm font-medium text-primary-400 mb-1">Generated On</h3>
<p class="text-primary-800 font-medium" id="generate-time">Jun 8, 2023 10:00 AM</p>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Main Content -->
<div class="lg:col-span-2">
<!-- Summary Stats -->
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
<h2 class="text-xl font-bold text-primary-800 mb-6">Weekly Summary</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="bg-primary-50 rounded-lg p-4">
<h3 class="text-sm font-medium text-primary-500 mb-1">Tasks Completed</h3>
<div class="flex items-end justify-between">
<span class="text-2xl font-bold text-primary-800">8</span>
<span class="text-sm text-green-500">+2 from last week</span>
</div>
</div>
<div class="bg-primary-50 rounded-lg p-4">
<h3 class="text-sm font-medium text-primary-500 mb-1">Value Points</h3>
<div class="flex items-end justify-between">
<span class="text-2xl font-bold text-primary-800">120</span>
<span class="text-sm text-green-500">80% achieved</span>
</div>
</div>
<div class="bg-primary-50 rounded-lg p-4">
<h3 class="text-sm font-medium text-primary-500 mb-1">Defects Fixed</h3>
<div class="flex items-end justify-between">
<span class="text-2xl font-bold text-primary-800">2</span>
<span class="text-sm text-yellow-500">4 remaining</span>
</div>
</div>
<div class="bg-primary-50 rounded-lg p-4">
<h3 class="text-sm font-medium text-primary-500 mb-1">Avg Fix Time</h3>
<div class="flex items-end justify-between">
<span class="text-2xl font-bold text-primary-800">1.5d</span>
<span class="text-sm text-green-500">Improved 0.5d</span>
</div>
</div>
</div>
</div>
<!-- Completed Tasks -->
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-bold text-primary-800">Completed Tasks</h2>
<span class="text-sm text-primary-500">8 tasks completed (64h planned, 72.5h actual)</span>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="text-left text-primary-500 border-b border-primary-100">
<tr>
<th class="pb-3 font-medium">Task</th>
<th class="pb-3 font-medium">Assignee</th>
<th class="pb-3 font-medium">Hours</th>
<th class="pb-3 font-medium">Value</th>
<th class="pb-3 font-medium">Status</th>
</tr>
</thead>
<tbody class="divide-y divide-primary-100" id="completed-tasks">
<!-- Will be populated by JavaScript -->
</tbody>
</table>
</div>
</div>
<!-- Defects -->
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-bold text-primary-800">Defects Tracking</h2>
<div class="flex space-x-2">
<span class="text-sm text-green-500">2 fixed</span>
<span class="text-sm text-primary-400">|</span>
<span class="text-sm text-red-500">3 new</span>
<span class="text-sm text-primary-400">|</span>
<span class="text-sm text-yellow-500">4 open</span>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="text-left text-primary-500 border-b border-primary-100">
<tr>
<th class="pb-3 font-medium">Defect</th>
<th class="pb-3 font-medium">Assignee</th>
<th class="pb-3 font-medium">Level</th>
<th class="pb-3 font-medium">Status</th>
<th class="pb-3 font-medium">Fix Time</th>
</tr>
</thead>
<tbody class="divide-y divide-primary-100" id="defects-list">
<!-- Will be populated by JavaScript -->
</tbody>
</table>
</div>
</div>
<!-- Events -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-xl font-bold text-primary-800 mb-6">Key Events</h2>
<div class="space-y-4" id="events-list">
<!-- Will be populated by JavaScript -->
</div>
</div>
</div>
<!-- Sidebar -->
<div>
<!-- Progress Overview -->
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
<h2 class="text-xl font-bold text-primary-800 mb-6">Progress Overview</h2>
<div class="flex flex-col items-center">
<div class="relative w-40 h-40 mb-4">
<svg class="w-full h-full" viewBox="0 0 100 100">
<circle class="text-primary-100" stroke-width="8" stroke="currentColor" fill="transparent" r="40" cx="50" cy="50" />
<circle class="text-secondary-500" stroke-width="8" stroke-dasharray="251.2" stroke-dashoffset="50.24" stroke-linecap="round" stroke="currentColor" fill="transparent" r="40" cx="50" cy="50" />
</svg>
<div class="absolute inset-0 flex items-center justify-center">
<span class="text-3xl font-bold text-primary-800">80%</span>
</div>
</div>
<div class="grid grid-cols-2 gap-4 w-full">
<div class="text-center">
<p class="text-sm text-primary-500">Planned</p>
<p class="font-bold text-primary-800">80.5h</p>
</div>
<div class="text-center">
<p class="text-sm text-primary-500">Actual</p>
<p class="font-bold text-primary-800">72.5h</p>
</div>
</div>
</div>
</div>
<!-- Team Contributions -->
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
<h2 class="text-xl font-bold text-primary-800 mb-6">Team Contributions</h2>
<div class="space-y-4" id="team-contributions">
<!-- Will be populated by JavaScript -->
</div>
</div>
<!-- Next Week Plan -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-xl font-bold text-primary-800 mb-6">Next Week Plan</h2>
<div class="mb-4">
<p class="text-sm text-primary-500 mb-1">Planned Tasks</p>
<p class="font-bold text-primary-800">12 tasks (96h, 180 points)</p>
</div>
<div class="space-y-3" id="next-week-tasks">
<!-- Will be populated by JavaScript -->
</div>
</div>
</div>
</div>
</div>
<script>
feather.replace();
// Sample data - in a real app this would come from an API
const reportData = {
projectId: "PRJ-2023-001",
reportPeriod: {
startDate: "2024-01-01",
endDate: "2024-01-07"
},
thisWeek: {
statistics: {
plannedTasks: { count: 10, totalBaselineHours: 80.5, totalValuePoints: 150 },
completedTasks: { count: 8, totalBaselineHours: 64.0, totalActualHours: 72.5, totalValuePoints: 120 },
inProgressTasks: { count: 5, totalBaselineHours: 40.0, totalValuePoints: 75 },
newDefects: { count: 3 },
fixedDefects: { count: 2, avgFixDays: 1.5 },
unfixedDefects: { count: 4 },
newEvents: { count: 2, totalValuePoints: 30 },
projectMembers: { totalCount: 8, activeCount: 6 }
},
taskList: [
{
id: "TASK-001",
taskNo: "TASK-001",
content: "Implement user authentication",
assigneeName: "Sarah Johnson",
planCompletionDate: "2024-01-05T00:00:00",
actualCompletionDate: "2024-01-04T00:00:00",
statusName: "Completed",
baselineHours: 8.0,
actualHours: 9.5,
complexityName: "Medium",
valuePoints: 15
},
{
id: "TASK-002",
taskNo: "TASK-002",
content: "Design homepage mockups",
assigneeName: "Michael Chen",
planCompletionDate: "2024-01-03T00:00:00",
actualCompletionDate: "2024-01-03T00:00:00",
statusName: "Completed",
baselineHours: 12.0,
actualHours: 10.0,
complexityName: "High",
valuePoints: 25
}
],
defectList: [
{
id: "BUG-001",
defectNo: "BUG-001",
title: "Login fails on mobile",
description: "Users unable to login on mobile devices",
reporterName: "Alex Rodriguez",
assigneeName: "Sarah Johnson",
levelName: "Critical",
statusName: "Fixed",
reportTime: "2024-01-02T10:00:00",
fixTime: "2024-01-03T15:30:00",
fixDays: 1
},
{
id: "BUG-002",
defectNo: "BUG-002",
title: "Dashboard loading slow",
description: "Dashboard takes >5s to load",
reporterName: "Emma Wilson",
assigneeName: "David Kim",
levelName: "High",
statusName: "Open",
reportTime: "2024-01-04T14:00:00",
fixTime: null,
fixDays: null
}
],
eventList: [
{
id: "EVT-001",
eventTypeName: "Team Meeting",
eventDate: "2024-01-03T00:00:00",
content: "Weekly sprint planning",
memberName: "All",
valuePoints: 20,
hoursSpent: 2.0
}
]
},
nextWeek: {
statistics: {
plannedTasks: { count: 12, totalBaselineHours: 96.0, totalValuePoints: 180 },
inProgressTasks: { count: 3, totalBaselineHours: 24.0, totalValuePoints: 45 }
},
taskList: [
{
id: "TASK-101",
taskNo: "TASK-101",
content: "Implement payment gateway",
assigneeName: "Sarah Johnson",
planCompletionDate: "2024-01-12T00:00:00",
statusName: "Planned",
baselineHours: 16.0,
complexityName: "High",
valuePoints: 30
}
]
},
generateTime: "2024-01-08T10:00:00"
};
// Format dates
function formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
}
function formatDateTime(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
// Populate the report with data
document.addEventListener('DOMContentLoaded', function() {
// Header information
document.getElementById('report-period').textContent =
`${formatDate(reportData.reportPeriod.startDate)} - ${formatDate(reportData.reportPeriod.endDate)}`;
document.getElementById('team-members').textContent =
`${reportData.thisWeek.statistics.projectMembers.totalCount} (${reportData.thisWeek.statistics.projectMembers.activeCount} active)`;
document.getElementById('generate-time').textContent =
formatDateTime(reportData.generateTime);
// Completed tasks
const completedTasksContainer = document.getElementById('completed-tasks');
reportData.thisWeek.taskList.forEach(task => {
const row = document.createElement('tr');
row.className = 'hover:bg-primary-50';
row.innerHTML = `
<td class="py-3">
<div class="font-medium text-primary-700">${task.taskNo}</div>
<div class="text-sm text-primary-500">${task.content}</div>
</td>
<td class="py-3">${task.assigneeName}</td>
<td class="py-3">
<div class="text-sm text-primary-500">${task.baselineHours}h planned</div>
<div class="font-medium">${task.actualHours}h actual</div>
</td>
<td class="py-3">${task.valuePoints}</td>
<td class="py-3">
<span class="badge status-completed">${task.statusName}</span>
</td>
`;
completedTasksContainer.appendChild(row);
});
// Defects
const defectsContainer = document.getElementById('defects-list');
reportData.thisWeek.defectList.forEach(defect => {
const row = document.createElement('tr');
row.className = 'hover:bg-primary-50';
row.innerHTML = `
<td class="py-3">
<div class="font-medium text-primary-700">${defect.defectNo}</div>
<div class="text-sm text-primary-500">${defect.title}</div>
</td>
<td class="py-3">${defect.assigneeName || 'Unassigned'}</td>
<td class="py-3">
<span class="badge level-${defect.levelName.toLowerCase()}">${defect.levelName}</span>
</td>
<td class="py-3">
<span class="badge ${defect.statusName === 'Fixed' ? 'status-completed' : 'status-inprogress'}">
${defect.statusName}
</span>
</td>
<td class="py-3">
${defect.fixTime ? `${defect.fixDays}d` : '-'}
</td>
`;
defectsContainer.appendChild(row);
});
// Events
const eventsContainer = document.getElementById('events-list');
reportData.thisWeek.eventList.forEach(event => {
const eventElement = document.createElement('div');
eventElement.className = 'flex items-start';
eventElement.innerHTML = `
<div class="mr-4 mt-1">
<div class="w-8 h-8 rounded-lg bg-secondary-100 flex items-center justify-center">
<i data-feather="calendar" class="text-secondary-500"></i>
</div>
</div>
<div>
<h4 class="font-medium text-primary-800">${event.eventTypeName}</h4>
<p class="text-sm text-primary-500 mt-1">${event.content}</p>
<div class="flex items-center mt-2 text-sm text-primary-400">
<i data-feather="user" class="w-3 h-3 mr-1"></i>
<span class="mr-3">${event.memberName}</span>
<i data-feather="clock" class="w-3 h-3 mr-1"></i>
<span>${event.hoursSpent}h (${event.valuePoints}pts)</span>
</div>
</div>
`;
eventsContainer.appendChild(eventElement);
});
// Team Contributions
const teamContainer = document.getElementById('team-contributions');
const teamMembers = [
{ name: "Sarah Johnson", tasks: 8, value: 120 },
{ name: "Michael Chen", tasks: 5, value: 85 },
{ name: "Alex Rodriguez", tasks: 3, value: 45 }
];
teamMembers.forEach(member => {
const memberElement = document.createElement('div');
memberElement.className = 'flex items-center';
memberElement.innerHTML = `
<div class="w-10 h-10 rounded-full bg-secondary-100 flex items-center justify-center mr-3">
<i data-feather="user" class="text-secondary-500"></i>
</div>
<div class="flex-1">
<h4 class="font-medium text-primary-800">${member.name}</h4>
<div class="flex justify-between text-sm text-primary-500">
<span>${member.tasks} tasks</span>
<span>${member.value} points</span>
</div>
</div>
`;
teamContainer.appendChild(memberElement);
});
// Next Week Tasks
const nextWeekContainer = document.getElementById('next-week-tasks');
reportData.nextWeek.taskList.forEach(task => {
const taskElement = document.createElement('div');
taskElement.className = 'flex items-start';
taskElement.innerHTML = `
<div class="mr-3 mt-1">
<div class="w-5 h-5 rounded-full bg-secondary-100 flex items-center justify-center">
<i data-feather="flag" class="text-secondary-500 w-3 h-3"></i>
</div>
</div>
<div>
<p class="text-primary-700">${task.content}</p>
<div class="text-xs text-primary-400 mt-1">
${task.assigneeName}${task.baselineHours}h • ${task.valuePoints}pts
</div>
</div>
`;
nextWeekContainer.appendChild(taskElement);
});
// Replace feather icons
feather.replace();
});
// Animation for report sections
document.addEventListener('DOMContentLoaded', function() {
const sections = document.querySelectorAll('.bg-white');
sections.forEach((section, index) => {
section.style.opacity = '0';
section.style.transform = 'translateY(20px)';
section.style.transition = 'all 0.5s ease ' + (index * 0.1) + 's';
setTimeout(() => {
section.style.opacity = '1';
section.style.transform = 'translateY(0)';
}, 100);
});
});
</script>
</body>
</html>