diff --git a/.gitignore b/.gitignore index 30bc162..68d0906 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,27 @@ -/node_modules \ No newline at end of file +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Temporary files +/tmp \ No newline at end of file diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 0000000..4fa5f08 --- /dev/null +++ b/dist/index.html @@ -0,0 +1,14 @@ + + + + + + + Wokay Assignment + + + + +
+ + diff --git a/package-lock.json b/package-lock.json index b88dfc5..18187c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,14 @@ { - "name": "clickup-dashboard-clone", - "version": "0.0.0", + "name": "wokay-assignment", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "clickup-dashboard-clone", - "version": "0.0.0", + "name": "wokay-assignment", + "version": "0.0.1", "dependencies": { + "framer-motion": "^12.23.0", "lucide-react": "^0.344.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -2622,6 +2623,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.23.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.0.tgz", + "integrity": "sha512-xf6NxTGAyf7zR4r2KlnhFmsRfKIbjqeBupEDBAaEtVIBJX96sAon00kMlsKButSIRwPSHjbRrAPnYdJJ9kyhbA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.22.0", + "motion-utils": "^12.19.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3085,6 +3113,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/motion-dom": { + "version": "12.22.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.22.0.tgz", + "integrity": "sha512-ooH7+/BPw9gOsL9VtPhEJHE2m4ltnhMlcGMhEqA0YGNhKof7jdaszvsyThXI6LVIKshJUZ9/CP6HNqQhJfV7kw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.19.0" + } + }, + "node_modules/motion-utils": { + "version": "12.19.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.19.0.tgz", + "integrity": "sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4050,6 +4093,12 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 9ac5706..45dcdc5 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "framer-motion": "^12.23.0", "lucide-react": "^0.344.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/App.tsx b/src/App.tsx index 2e126ce..a1e0860 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,17 +9,17 @@ import TaskCharts from "./components/TaskCharts"; function App() { return ( -
+
-
+
-
+
-
+
diff --git a/src/components/AIExecutiveSummary.tsx b/src/components/AIExecutiveSummary.tsx index ef296a9..46cd49e 100644 --- a/src/components/AIExecutiveSummary.tsx +++ b/src/components/AIExecutiveSummary.tsx @@ -1,44 +1,192 @@ -import { Sparkles, Expand, RefreshCw } from "lucide-react"; +import { useState, useEffect } from "react"; +import { Sparkles, Expand, RefreshCw, TrendingUp, AlertTriangle, CheckCircle } from "lucide-react"; +import { motion, AnimatePresence } from "framer-motion"; const AIExecutiveSummary = () => { + const [isGenerating, setIsGenerating] = useState(false); + const [currentInsight, setCurrentInsight] = useState(0); + const [isExpanded, setIsExpanded] = useState(false); + + const insights = [ + { + title: "Team Productivity Surge", + content: "Your team has completed 73% more tasks this week compared to last week. The Development team is leading with 18 completed tasks.", + type: "positive", + icon: TrendingUp, + color: "text-green-600", + bgColor: "bg-green-50", + borderColor: "border-green-200" + }, + { + title: "Bottleneck Alert", + content: "The Review process is experiencing delays with 8 pending tasks. Consider allocating additional reviewers to maintain workflow.", + type: "warning", + icon: AlertTriangle, + color: "text-amber-600", + bgColor: "bg-amber-50", + borderColor: "border-amber-200" + }, + { + title: "Sprint Goal Achievement", + content: "You're on track to exceed your sprint goal by 15%. Current completion rate suggests finishing 2 days ahead of schedule.", + type: "success", + icon: CheckCircle, + color: "text-blue-600", + bgColor: "bg-blue-50", + borderColor: "border-blue-200" + } + ]; + + const recommendations = [ + "Consider redistributing 3 tasks from Design to Development team", + "Schedule additional code review sessions for Thursday", + "Celebrate team achievements to maintain momentum" + ]; + + const handleRefresh = () => { + setIsGenerating(true); + setTimeout(() => { + setIsGenerating(false); + setCurrentInsight((prev) => (prev + 1) % insights.length); + }, 2000); + }; + + useEffect(() => { + const interval = setInterval(() => { + setCurrentInsight((prev) => (prev + 1) % insights.length); + }, 8000); + return () => clearInterval(interval); + }, [insights.length]); + + const currentData = insights[currentInsight]; + const IconComponent = currentData.icon; + return ( -
+
-
- +
+
+ +
+ {isGenerating && ( + + )}

AI Executive Summary

-

Refreshed 3 mins ago

+

+ {isGenerating ? "Generating insights..." : "Updated just now"} +

- -
-
-
-

Executive Summary

-

- No tasks were updated in the last week. -

-
+ {/* Main Insight Card */} + + +
+
+ +
+
+

+ {currentData.title} +

+

+ {currentData.content} +

+
+
+
+
+ + {/* Insight Indicators */} +
+ {insights.map((_, index) => ( +
+ {/* Recommendations Section */} +
-

- Key Efforts & Initiatives +

+ + AI Recommendations

-

There are no active tasks.

+
+ {recommendations.map((rec, index) => ( + +
+ {index + 1} +
+

{rec}

+
+ ))} +
+ + {isExpanded && ( + +

Detailed Analytics

+
+
+
92%
+
Team Efficiency
+
+
+
+18%
+
Week over Week
+
+
+
+ )}
); diff --git a/src/components/DashboardControls.tsx b/src/components/DashboardControls.tsx index 9c58a18..fb69aff 100644 --- a/src/components/DashboardControls.tsx +++ b/src/components/DashboardControls.tsx @@ -1,14 +1,46 @@ -import { useState } from "react"; -import { RefreshCw, Filter, Plus } from "lucide-react"; +import { useState, useEffect } from "react"; +import { RefreshCw, Filter, Plus, ChevronDown } from "lucide-react"; +import { motion, AnimatePresence } from "framer-motion"; const DashboardControls = () => { const [editMode, setEditMode] = useState(false); const [autoRefresh, setAutoRefresh] = useState(true); + const [isRefreshing, setIsRefreshing] = useState(false); + const [lastRefresh, setLastRefresh] = useState("3 mins ago"); + const [filterCount, setFilterCount] = useState(0); + const [showFilters, setShowFilters] = useState(false); + + const handleRefresh = () => { + setIsRefreshing(true); + setTimeout(() => { + setIsRefreshing(false); + setLastRefresh("just now"); + }, 1500); + }; + + const toggleFilter = () => { + setFilterCount(prev => prev === 0 ? 1 : 0); + }; + + useEffect(() => { + if (autoRefresh) { + const interval = setInterval(() => { + setLastRefresh(prev => { + if (prev === "just now") return "1 min ago"; + if (prev === "1 min ago") return "2 mins ago"; + if (prev === "2 mins ago") return "3 mins ago"; + return "3 mins ago"; + }); + }, 60000); + return () => clearInterval(interval); + } + }, [autoRefresh]); return (
+ {/* Edit Mode Toggle */}
Edit mode: + {editMode && ( + + Active + + )}
+ {/* Refresh Status */}
-
- - Refreshed 3 mins ago -
+ + {/* Auto Refresh Toggle */}
Auto refresh: - On + + {autoRefresh ? 'On' : 'Off'} +
- + {/* Filters */} +
+ + + {/* Filter Dropdown */} + + {showFilters && ( + +

Filter Options

+
+ {['Status', 'Assignee', 'Priority', 'Date'].map((filter) => ( + + ))} +
+ +
+ )} +
+
+ {/* Add Card Button */}
+ + {/* Active filters display */} + + {filterCount > 0 && ( + + Active filters: + + Status: In Progress + + + + )} +
); }; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index c6517b2..3143bef 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -38,7 +38,7 @@ const Sidebar = () => { ]; return ( -
+
diff --git a/src/components/TaskCharts.tsx b/src/components/TaskCharts.tsx index 31cf79c..47f178f 100644 --- a/src/components/TaskCharts.tsx +++ b/src/components/TaskCharts.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import { PieChart, Pie, @@ -7,81 +8,226 @@ import { XAxis, YAxis, ResponsiveContainer, + Tooltip, + LineChart, + Line, } from "recharts"; +import { motion } from "framer-motion"; const TaskCharts = () => { + const [activeIndex, setActiveIndex] = useState(-1); + const [selectedChart, setSelectedChart] = useState(null); + const pieData = [ - { name: "Assigned", value: 20 }, - { name: "Unassigned", value: 80 }, + { name: "Assigned", value: 65, color: "#10B981" }, + { name: "Unassigned", value: 35, color: "#9CA3AF" }, ]; const barData = [ - { name: "User A", value: 7 }, - { name: "User B", value: 6 }, - { name: "User C", value: 5 }, - { name: "User D", value: 4 }, - { name: "User E", value: 3 }, + { name: "Alice", value: 8, avatar: "👩‍💼" }, + { name: "Bob", value: 6, avatar: "👨‍💻" }, + { name: "Carol", value: 5, avatar: "👩‍🎨" }, + { name: "David", value: 4, avatar: "👨‍🔬" }, + { name: "Emma", value: 3, avatar: "👩‍🚀" }, + ]; + + const weeklyData = [ + { day: "Mon", completed: 12, pending: 3 }, + { day: "Tue", completed: 15, pending: 5 }, + { day: "Wed", completed: 8, pending: 7 }, + { day: "Thu", completed: 18, pending: 2 }, + { day: "Fri", completed: 22, pending: 4 }, + { day: "Sat", completed: 5, pending: 1 }, + { day: "Sun", completed: 3, pending: 0 }, ]; - const COLORS = ["#9CA3AF", "#E5E7EB"]; + const onPieEnter = (_: unknown, index: number) => { + setActiveIndex(index); + }; + + const onPieLeave = () => { + setActiveIndex(-1); + }; + + const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: Array<{ value: number; payload: Record }>; label?: string }) => { + if (active && payload && payload.length) { + return ( +
+

{`${label}: ${payload[0].value} tasks`}

+
+ ); + } + return null; + }; + + const PieTooltip = ({ active, payload }: { active?: boolean; payload?: Array<{ name: string; value: number }> }) => { + if (active && payload && payload.length) { + return ( +
+

{`${payload[0].name}: ${payload[0].value}%`}

+
+ ); + } + return null; + }; return ( -
-
+
+ {/* Pie Chart */} + setSelectedChart(selectedChart === 'pie' ? null : 'pie')} + >

Total Tasks by Assignee

-
+
- {pieData.map((entry, index) => ( + {pieData.map((entry) => ( ))} + } />
-
+
+ {pieData.map((entry) => ( +
+
+ {entry.name} ({entry.value}%) +
+ ))} +
+
-
+ {/* Bar Chart */} + setSelectedChart(selectedChart === 'bar' ? null : 'bar')} + >

Open Tasks by Assignee

-
Tasks
-
+
Active Tasks
+
- - - - + + + + } /> +
-
+
+ {barData.slice(0, 3).map((user) => ( +
+
{user.avatar}
+
{user.name}
+
{user.value}
+
+ ))} +
+
-
+ {/* Line Chart */} + setSelectedChart(selectedChart === 'line' ? null : 'line')} + >

- Tasks Completed This Week + Weekly Task Completion

-
-
-
📋
-

No Results

+
Last 7 days
+
+ + + + + } /> + + + + +
+
+
+
+ Completed +
+
+
+ Pending
-
+
); }; diff --git a/src/components/TaskStatusOverview.tsx b/src/components/TaskStatusOverview.tsx index b8980bc..7898b26 100644 --- a/src/components/TaskStatusOverview.tsx +++ b/src/components/TaskStatusOverview.tsx @@ -1,26 +1,123 @@ +import { useState, useEffect } from "react"; +import { Clock, CheckCircle, AlertCircle, TrendingUp } from "lucide-react"; +import { motion } from "framer-motion"; + const TaskStatusOverview = () => { + const [animateNumbers, setAnimateNumbers] = useState(false); + const statusData = [ - { label: "Unassigned", count: 6, color: "bg-gray-100 text-gray-700" }, - { label: "In Progress", count: 1, color: "bg-blue-100 text-blue-700" }, - { label: "Completed", count: 0, color: "bg-green-100 text-green-700" }, + { + label: "Unassigned", + count: 8, + color: "bg-gray-50 border-gray-200", + textColor: "text-gray-700", + numberColor: "text-gray-900", + icon: AlertCircle, + iconColor: "text-gray-500", + trend: "+2 from yesterday", + trendColor: "text-gray-600" + }, + { + label: "In Progress", + count: 12, + color: "bg-blue-50 border-blue-200", + textColor: "text-blue-700", + numberColor: "text-blue-600", + icon: Clock, + iconColor: "text-blue-500", + trend: "+5 from yesterday", + trendColor: "text-blue-600" + }, + { + label: "Completed", + count: 24, + color: "bg-green-50 border-green-200", + textColor: "text-green-700", + numberColor: "text-green-600", + icon: CheckCircle, + iconColor: "text-green-500", + trend: "+8 from yesterday", + trendColor: "text-green-600" + }, ]; + useEffect(() => { + const timer = setTimeout(() => setAnimateNumbers(true), 100); + return () => clearTimeout(timer); + }, []); + + const CountUpAnimation = ({ target, className }: { target: number; className: string }) => { + const [current, setCurrent] = useState(0); + + useEffect(() => { + if (!animateNumbers) return; + + const increment = target / 20; + const timer = setInterval(() => { + setCurrent(prev => { + const next = prev + increment; + if (next >= target) { + clearInterval(timer); + return target; + } + return next; + }); + }, 50); + + return () => clearInterval(timer); + }, [target]); + + return ( +
+ {Math.floor(current)} +
+ ); + }; + return (
- {statusData.map((status, index) => ( -
-

- {status.label} -

-
- {status.count} -
-
tasks
-
- ))} + {statusData.map((status, index) => { + const IconComponent = status.icon; + return ( + +
+

+ {status.label} +

+ +
+ + + +
tasks
+ +
+ + {status.trend} +
+ + {/* Simple progress indicator */} +
+ +
+
+ ); + })}
); }; diff --git a/src/components/WorkloadChart.tsx b/src/components/WorkloadChart.tsx index 9ea1645..a7b0893 100644 --- a/src/components/WorkloadChart.tsx +++ b/src/components/WorkloadChart.tsx @@ -1,29 +1,176 @@ -import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer } from "recharts"; +import { useState } from "react"; +import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer, Tooltip, Cell } from "recharts"; +import { Users, Activity, Target } from "lucide-react"; +import { motion } from "framer-motion"; const WorkloadChart = () => { - const data = [{ name: "Workload", gray: 85, blue: 15 }]; + const [activeBar, setActiveBar] = useState(null); + + const data = [ + { name: "Development", completed: 45, inProgress: 25, pending: 15, total: 85 }, + { name: "Design", completed: 30, inProgress: 20, pending: 10, total: 60 }, + { name: "Testing", completed: 20, inProgress: 15, pending: 5, total: 40 }, + { name: "Review", completed: 35, inProgress: 10, pending: 8, total: 53 }, + ]; + + const teamStats = [ + { label: "Active Members", value: 12, icon: Users, color: "text-gray-600" }, + { label: "Capacity Used", value: "73%", icon: Activity, color: "text-blue-600" }, + { label: "Weekly Goal", value: "85%", icon: Target, color: "text-green-600" }, + ]; + + const CustomTooltip = ({ active, payload, label }: { + active?: boolean; + payload?: Array<{ payload: { completed: number; inProgress: number; pending: number; total: number } }>; + label?: string + }) => { + if (active && payload && payload.length) { + const data = payload[0].payload; + return ( +
+

{label}

+
+
+
+ Completed: {data.completed} +
+
+
+ In Progress: {data.inProgress} +
+
+
+ Pending: {data.pending} +
+
+
+ Total: {data.total} tasks +
+
+ ); + } + return null; + }; return ( -
-

- Workload by Status -

-
+ +
+

+ Team Workload Overview +

+
+
+
+
+
+
+ + {/* Team Stats */} +
+ {teamStats.map((stat, index) => { + const IconComponent = stat.icon; + return ( + + +
{stat.value}
+
{stat.label}
+
+ ); + })} +
+ +
- - - - - + { + if (e && e.activeTooltipIndex !== undefined) { + setActiveBar(e.activeTooltipIndex); + } + }} + onMouseLeave={() => setActiveBar(null)} + > + + + } /> + + {data.map((entry, index) => ( + + ))} + + + {data.map((entry, index) => ( + + ))} + + + {data.map((entry, index) => ( + + ))} +
-
- 0 - Tasks - 100 + + {/* Legend */} +
+
+
+ Completed +
+
+
+ In Progress +
+
+
+ Pending +
-
+
); };