Skip to content

Commit 000ed4a

Browse files
authored
Merge pull request #35 from ankurrera/copilot/fix-synchronization-issues
[WIP] Fix and enforce synchronization between Skills and Core Metrics
2 parents cf85b94 + 19c9c25 commit 000ed4a

6 files changed

Lines changed: 696 additions & 42 deletions

File tree

src/components/skills/EditSkillDialog.tsx

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { useState } from "react";
1+
import { useState, useEffect } from "react";
22
import { Skill, useSkills } from "@/hooks/useSkills";
33
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
44
import { Button } from "@/components/ui/button";
55
import { Input } from "@/components/ui/input";
66
import { Textarea } from "@/components/ui/textarea";
7+
import { getSkillMetricContributions } from "@/lib/coreMetricCalculation";
8+
import { getDefaultMapping } from "@/lib/coreMetrics";
79

810
interface EditSkillDialogProps {
911
skill: Skill;
@@ -17,7 +19,14 @@ const EditSkillDialog = ({ skill, open, onOpenChange }: EditSkillDialogProps) =>
1719
const [description, setDescription] = useState(skill.description || "");
1820
const [area, setArea] = useState(skill.area || "");
1921
const [coverImage, setCoverImage] = useState(skill.cover_image || "");
20-
const [xp, setXP] = useState(skill.xp.toString());
22+
23+
// Reset form when skill changes
24+
useEffect(() => {
25+
setName(skill.name);
26+
setDescription(skill.description || "");
27+
setArea(skill.area || "");
28+
setCoverImage(skill.cover_image || "");
29+
}, [skill]);
2130

2231
const handleSubmit = (e: React.FormEvent) => {
2332
e.preventDefault();
@@ -30,7 +39,7 @@ const EditSkillDialog = ({ skill, open, onOpenChange }: EditSkillDialogProps) =>
3039
description: description.trim() || null,
3140
area: area.trim() || null,
3241
cover_image: coverImage.trim() || null,
33-
xp: parseInt(xp) || 0,
42+
// XP is computed from attendance records - not manually editable
3443
},
3544
{
3645
onSuccess: () => {
@@ -40,6 +49,16 @@ const EditSkillDialog = ({ skill, open, onOpenChange }: EditSkillDialogProps) =>
4049
);
4150
};
4251

52+
// Calculate which metrics this skill affects
53+
const skillContributionData = {
54+
id: skill.id,
55+
name: skill.name,
56+
xp: skill.xp,
57+
area: skill.area,
58+
contributesTo: skill.contributes_to || undefined,
59+
};
60+
const metricContributions = getSkillMetricContributions(skillContributionData);
61+
4362
return (
4463
<Dialog open={open} onOpenChange={onOpenChange}>
4564
<DialogContent className="system-panel max-w-2xl">
@@ -93,36 +112,53 @@ const EditSkillDialog = ({ skill, open, onOpenChange }: EditSkillDialogProps) =>
93112
/>
94113
</div>
95114

96-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
97-
{/* Cover Image URL */}
98-
<div>
99-
<label className="text-xs text-muted-foreground uppercase tracking-wider mb-2 block">
100-
Cover Image URL
101-
</label>
102-
<Input
103-
value={coverImage}
104-
onChange={(e) => setCoverImage(e.target.value)}
105-
placeholder="https://..."
106-
className="bg-input border-border"
107-
/>
108-
</div>
115+
{/* Cover Image URL */}
116+
<div>
117+
<label className="text-xs text-muted-foreground uppercase tracking-wider mb-2 block">
118+
Cover Image URL
119+
</label>
120+
<Input
121+
value={coverImage}
122+
onChange={(e) => setCoverImage(e.target.value)}
123+
placeholder="https://..."
124+
className="bg-input border-border"
125+
/>
126+
</div>
109127

110-
{/* XP */}
111-
<div>
112-
<label className="text-xs text-muted-foreground uppercase tracking-wider mb-2 block">
113-
XP
114-
</label>
115-
<Input
116-
type="number"
117-
value={xp}
118-
onChange={(e) => setXP(e.target.value)}
119-
placeholder="0"
120-
className="bg-input border-border"
121-
min="0"
122-
/>
128+
{/* XP Display (Read-only) - Shows computed value */}
129+
<div className="border-t border-border/30 pt-4">
130+
<div className="flex items-center justify-between mb-3">
131+
<span className="text-xs text-muted-foreground uppercase tracking-wider">
132+
Current XP (computed from attendance)
133+
</span>
134+
<span className="text-lg font-medium text-foreground">
135+
{skill.xp} XP
136+
</span>
123137
</div>
124138
</div>
125139

140+
{/* Metric Contributions Display - Bi-directional debugging */}
141+
{metricContributions.length > 0 && (
142+
<div className="border-t border-border/30 pt-4">
143+
<h4 className="text-xs text-muted-foreground uppercase tracking-wider mb-3">
144+
Core Metrics Affected
145+
</h4>
146+
<div className="grid grid-cols-2 gap-2">
147+
{metricContributions.map((contribution) => (
148+
<div
149+
key={contribution.metricName}
150+
className="flex items-center justify-between p-2 bg-muted/50 rounded text-sm"
151+
>
152+
<span className="text-foreground">{contribution.metricName}</span>
153+
<span className="text-muted-foreground">
154+
+{contribution.contributedXp} XP ({Math.round(contribution.weight * 100)}%)
155+
</span>
156+
</div>
157+
))}
158+
</div>
159+
</div>
160+
)}
161+
126162
{/* Actions */}
127163
<div className="flex gap-3 pt-4">
128164
<Button

src/components/skills/SkillCard.tsx

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@ import { useState } from "react";
22
import { Skill, useSkills } from "@/hooks/useSkills";
33
import { Button } from "@/components/ui/button";
44
import { calculateLevelProgress } from "@/lib/levelCalculation";
5-
import { Edit2, Trash2, Star, Power, PowerOff, Calendar as CalendarIcon } from "lucide-react";
5+
import { Edit2, Trash2, Star, Power, PowerOff, Calendar as CalendarIcon, Target } from "lucide-react";
66
import { getConsistencyStatusMessage } from "@/lib/consistencyCalculations";
7+
import { getSkillMetricContributions } from "@/lib/coreMetricCalculation";
78
import EditSkillDialog from "./EditSkillDialog";
89
import ConfirmDialog from "./ConfirmDialog";
910
import SkillCalendar from "./SkillCalendar";
1011
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
12+
import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from "@/components/ui/tooltip";
1113

1214
interface SkillCardProps {
1315
skill: Skill;
1416
}
1517

18+
/** Maximum number of metric contributions to display inline on a skill card */
19+
const MAX_DISPLAYED_CONTRIBUTIONS = 3;
20+
1621
const SkillCard = ({ skill }: SkillCardProps) => {
1722
const { updateSkill, deleteSkill } = useSkills();
1823
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
@@ -21,6 +26,16 @@ const SkillCard = ({ skill }: SkillCardProps) => {
2126

2227
const progress = calculateLevelProgress(skill.xp);
2328

29+
// Calculate which metrics this skill affects (bi-directional debugging)
30+
const skillContributionData = {
31+
id: skill.id,
32+
name: skill.name,
33+
xp: skill.xp,
34+
area: skill.area,
35+
contributesTo: skill.contributes_to || undefined,
36+
};
37+
const metricContributions = getSkillMetricContributions(skillContributionData);
38+
2439
const handleToggleActive = () => {
2540
updateSkill.mutate({
2641
id: skill.id,
@@ -106,6 +121,34 @@ const SkillCard = ({ skill }: SkillCardProps) => {
106121
</div>
107122
</div>
108123

124+
{/* Metric Contributions - shows which Core Metrics this skill affects */}
125+
{metricContributions.length > 0 && (
126+
<TooltipProvider>
127+
<div className="flex items-center gap-1 text-xs flex-wrap">
128+
<Target className="w-3 h-3 text-muted-foreground" />
129+
{metricContributions.slice(0, MAX_DISPLAYED_CONTRIBUTIONS).map((contribution) => (
130+
<Tooltip key={contribution.metricName}>
131+
<TooltipTrigger asChild>
132+
<span className="text-muted-foreground bg-muted/50 px-1.5 py-0.5 rounded cursor-help">
133+
{contribution.metricName}
134+
</span>
135+
</TooltipTrigger>
136+
<TooltipContent>
137+
<p className="text-xs">
138+
+{contribution.contributedXp} XP ({Math.round(contribution.weight * 100)}% weight)
139+
</p>
140+
</TooltipContent>
141+
</Tooltip>
142+
))}
143+
{metricContributions.length > MAX_DISPLAYED_CONTRIBUTIONS && (
144+
<span className="text-muted-foreground">
145+
+{metricContributions.length - MAX_DISPLAYED_CONTRIBUTIONS} more
146+
</span>
147+
)}
148+
</div>
149+
</TooltipProvider>
150+
)}
151+
109152
{/* Streak and Consistency */}
110153
<div className="flex items-center justify-between text-xs border-t border-border/30 pt-2">
111154
<div className="flex items-center gap-2">

0 commit comments

Comments
 (0)