Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 1 addition & 50 deletions src/data/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1756,13 +1756,6 @@ export function reorderDistractionTemplates(orderedIds: string[]): void {
// from #32/#33/#34 (pass-back, configurable milestone outcomes), which
// hasn't landed yet.

export interface SkillWorkedCount {
checklistItemId: string;
title: string;
phase: Phase;
count: number;
}

export interface DogActivitySummary {
dog: Dog;
lastWorkedDate: string | null;
Expand All @@ -1788,12 +1781,6 @@ export interface TrainerHistoryStats {
activeDogs: number;
graduatedDogs: number;
releasedDogs: number;
totalLogs: number;
logsThisWeek: number;
logsThisMonth: number;
milestonesCompleted: number;
skillsWorkedOnTotal: number;
mostWorkedSkills: SkillWorkedCount[];
recentlyWorkedDogs: DogActivitySummary[];
dogsNeedingAttention: DogActivitySummary[];
successRateOverall: SuccessRate;
Expand All @@ -1808,12 +1795,6 @@ export interface TrainerHistoryStats {
graduatedDogsList: Dog[];
}

function daysAgoLocalDate(days: number): string {
const d = new Date();
d.setDate(d.getDate() - days);
return localSessionDate(d);
}

// Dogs still in progress (neither graduated nor released) never count toward
// either side of this ratio — only a decided outcome moves the needle.
function computeSuccessRate(dogs: Dog[]): SuccessRate {
Expand All @@ -1832,7 +1813,7 @@ export function useTrainerHistoryStats(): TrainerHistoryStats {
const today = useCurrentLocalDate();

return useMemo(() => {
const { dogs, reports, checklistItems, dogMilestoneCompletions, milestoneTemplates, milestoneOutcomeAttempts } =
const { dogs, reports, dogMilestoneCompletions, milestoneTemplates, milestoneOutcomeAttempts } =
state;

const graduatedDogs = dogs.filter((d) => d.graduated).length;
Expand Down Expand Up @@ -1883,30 +1864,6 @@ export function useTrainerHistoryStats(): TrainerHistoryStats {
.filter((d) => d.graduated)
.sort((a, b) => (b.graduatedDate ?? '').localeCompare(a.graduatedDate ?? ''));

const weekAgo = daysAgoLocalDate(7);
const monthAgo = daysAgoLocalDate(30);
const logsThisWeek = reports.filter((r) => r.sessionDate >= weekAgo).length;
const logsThisMonth = reports.filter((r) => r.sessionDate >= monthAgo).length;

const milestonesCompleted = dogMilestoneCompletions.filter((c) => c.completed).length;

const skillCounts = new Map<string, number>();
let skillsWorkedOnTotal = 0;
reports.forEach((r) => {
r.skillIds.forEach((id) => {
skillCounts.set(id, (skillCounts.get(id) ?? 0) + 1);
skillsWorkedOnTotal += 1;
});
});
const mostWorkedSkills: SkillWorkedCount[] = [...skillCounts.entries()]
.map(([checklistItemId, count]) => {
const item = checklistItems.find((i) => i.id === checklistItemId);
return item ? { checklistItemId, title: item.title, phase: item.phase, count } : null;
})
.filter((x): x is SkillWorkedCount => x !== null)
.sort((a, b) => b.count - a.count)
.slice(0, 5);

const lastWorkedByDog = new Map<string, string>();
reports.forEach((r) => {
const existing = lastWorkedByDog.get(r.dogId);
Expand Down Expand Up @@ -1934,12 +1891,6 @@ export function useTrainerHistoryStats(): TrainerHistoryStats {
activeDogs,
graduatedDogs,
releasedDogs,
totalLogs: reports.length,
logsThisWeek,
logsThisMonth,
milestonesCompleted,
skillsWorkedOnTotal,
mostWorkedSkills,
recentlyWorkedDogs,
dogsNeedingAttention,
successRateOverall,
Expand Down
163 changes: 64 additions & 99 deletions src/pages/TrainerHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ function FinalOutcomeBar({ counts }: { counts: FinalOutcomeCounts }) {
export function TrainerHistory() {
const stats = useTrainerHistoryStats();
const session = useSession();
const [refinedRate, setRefinedRate] = useState(false);
const [refinedRate, setRefinedRate] = useState(true);
const [showGraduatedList, setShowGraduatedList] = useState(false);
const pinnedFolderId = usePinnedFolderId();
const pinnedFolder = useFolder(pinnedFolderId);
Expand Down Expand Up @@ -170,10 +170,64 @@ export function TrainerHistory() {
</div>

<p className="text-sm text-gray-500">
A look back at your training career — every dog you've handled on this account. Other
instructors' data isn't included here.
Your assigned dogs and what needs attention today. Other instructors' data isn't included
here.
</p>

<section className="space-y-2">
<h2 className="text-sm font-medium uppercase tracking-wide text-gray-500">
Needs Attention
</h2>
{stats.dogsNeedingAttention.length === 0 && (
<p className="text-sm text-gray-400">
{pinnedFolderId ? 'Every currently assigned pinned dog was worked yesterday.' : 'Pin a folder to track which assigned dogs were not worked yesterday.'}
</p>
)}
<ul className="space-y-1">
{stats.dogsNeedingAttention.map(({ dog, lastWorkedDate }) => (
<li key={dog.id}>
<Link
to={`/dog/${dog.id}`}
className="flex items-center justify-between rounded-xl border border-amber-200 dark:border-amber-900 p-3 text-sm hover:border-amber-400"
>
<span className="flex items-center gap-2 font-medium text-gray-900 dark:text-gray-100">
{dog.name}
<DailyWorkBadge count={dailySessionCounts[dog.id] ?? 0} />
</span>
<span className="text-xs text-amber-600 dark:text-amber-400">
{formatLastWorked(lastWorkedDate)}
</span>
</Link>
</li>
))}
</ul>
</section>

<section className="space-y-2">
<h2 className="text-sm font-medium uppercase tracking-wide text-gray-500">
Recently Worked
</h2>
{stats.recentlyWorkedDogs.length === 0 && (
<p className="text-sm text-gray-400">No recent logs for currently assigned dogs.</p>
)}
<ul className="space-y-1">
{stats.recentlyWorkedDogs.map(({ dog, lastWorkedDate }) => (
<li key={dog.id}>
<Link
to={`/dog/${dog.id}`}
className={`flex items-center justify-between rounded-xl border p-3 text-sm hover:border-sky-400 ${dailyWorkSurfaceClass(dailySessionCounts[dog.id] ?? 0)}`}
>
<span className="flex items-center gap-2 font-medium text-gray-900 dark:text-gray-100">
{dog.name}
<DailyWorkBadge count={dailySessionCounts[dog.id] ?? 0} />
</span>
<span className="text-xs text-gray-500">{formatLastWorked(lastWorkedDate)}</span>
</Link>
</li>
))}
</ul>
</section>

{pinnedFolderId && pinnedFolder && (
<section className="space-y-2">
<div className="flex items-center justify-between gap-2">
Expand Down Expand Up @@ -258,19 +312,6 @@ export function TrainerHistory() {
)}
</section>

<section className="space-y-2">
<h2 className="text-sm font-medium uppercase tracking-wide text-gray-500">
Career Highlights
</h2>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
<StatTile icon="📝" label="Training logs" value={stats.totalLogs} />
<StatTile icon="📅" label="Logs this week" value={stats.logsThisWeek} />
<StatTile icon="🗓️" label="Logs this month" value={stats.logsThisMonth} />
<StatTile icon="🏁" label="Milestones completed" value={stats.milestonesCompleted} />
<StatTile icon="⭐" label="Skills worked on (total)" value={stats.skillsWorkedOnTotal} />
</div>
</section>

<section className="space-y-2">
<div className="flex items-center justify-between gap-2">
<h2 className="text-sm font-medium uppercase tracking-wide text-gray-500">
Expand All @@ -279,26 +320,26 @@ export function TrainerHistory() {
<div className="flex gap-1 text-xs">
<button
type="button"
onClick={() => setRefinedRate(false)}
onClick={() => setRefinedRate(true)}
title="Omits dogs marked Excluded from Stats (pass-backs, health releases, etc.)"
className={
!refinedRate
refinedRate
? 'rounded-md bg-sky-500 px-2 py-1 font-medium text-white'
: 'rounded-md border border-gray-300 dark:border-gray-600 px-2 py-1 hover:bg-gray-50 dark:hover:bg-gray-800'
}
>
Overall
Refined
</button>
<button
type="button"
onClick={() => setRefinedRate(true)}
title="Omits dogs marked Excluded from Stats (pass-backs, health releases, etc.)"
onClick={() => setRefinedRate(false)}
className={
refinedRate
!refinedRate
? 'rounded-md bg-sky-500 px-2 py-1 font-medium text-white'
: 'rounded-md border border-gray-300 dark:border-gray-600 px-2 py-1 hover:bg-gray-50 dark:hover:bg-gray-800'
}
>
Refined
Overall
</button>
</div>
</div>
Expand Down Expand Up @@ -326,82 +367,6 @@ export function TrainerHistory() {
</p>
)}
</section>

<section className="space-y-2">
<h2 className="text-sm font-medium uppercase tracking-wide text-gray-500">
Skills You've Worked Most
</h2>
{stats.mostWorkedSkills.length === 0 && (
<p className="text-sm text-gray-400">No skills logged as worked on yet.</p>
)}
<ul className="space-y-1">
{stats.mostWorkedSkills.map((s) => (
<li
key={s.checklistItemId}
className="flex items-center justify-between rounded-xl border border-gray-200 dark:border-gray-700 p-3 text-sm"
>
<span className="text-gray-800 dark:text-gray-200">
{s.title} <span className="text-xs text-gray-400">· {s.phase}</span>
</span>
<span className="text-xs text-gray-500">{s.count}×</span>
</li>
))}
</ul>
</section>

<section className="space-y-2">
<h2 className="text-sm font-medium uppercase tracking-wide text-gray-500">
Recently Worked
</h2>
{stats.recentlyWorkedDogs.length === 0 && (
<p className="text-sm text-gray-400">No recent logs for currently assigned dogs.</p>
)}
<ul className="space-y-1">
{stats.recentlyWorkedDogs.map(({ dog, lastWorkedDate }) => (
<li key={dog.id}>
<Link
to={`/dog/${dog.id}`}
className={`flex items-center justify-between rounded-xl border p-3 text-sm hover:border-sky-400 ${dailyWorkSurfaceClass(dailySessionCounts[dog.id] ?? 0)}`}
>
<span className="flex items-center gap-2 font-medium text-gray-900 dark:text-gray-100">
{dog.name}
<DailyWorkBadge count={dailySessionCounts[dog.id] ?? 0} />
</span>
<span className="text-xs text-gray-500">{formatLastWorked(lastWorkedDate)}</span>
</Link>
</li>
))}
</ul>
</section>

<section className="space-y-2">
<h2 className="text-sm font-medium uppercase tracking-wide text-gray-500">
Needs Attention
</h2>
{stats.dogsNeedingAttention.length === 0 && (
<p className="text-sm text-gray-400">
{pinnedFolderId ? 'Every currently assigned pinned dog was worked yesterday.' : 'Pin a folder to track which assigned dogs were not worked yesterday.'}
</p>
)}
<ul className="space-y-1">
{stats.dogsNeedingAttention.map(({ dog, lastWorkedDate }) => (
<li key={dog.id}>
<Link
to={`/dog/${dog.id}`}
className="flex items-center justify-between rounded-xl border border-amber-200 dark:border-amber-900 p-3 text-sm hover:border-amber-400"
>
<span className="flex items-center gap-2 font-medium text-gray-900 dark:text-gray-100">
{dog.name}
<DailyWorkBadge count={dailySessionCounts[dog.id] ?? 0} />
</span>
<span className="text-xs text-amber-600 dark:text-amber-400">
{formatLastWorked(lastWorkedDate)}
</span>
</Link>
</li>
))}
</ul>
</section>
</div>
);
}
Loading