feat(patterns): pattern taxonomy + is_optimal

This commit is contained in:
2025-09-08 16:03:14 +01:00
parent 06d99a7f04
commit fe59de3392
28 changed files with 1434 additions and 26 deletions

View File

@@ -1,10 +1,39 @@
import { getPatterns } from "@/lib/api";
import Link from "next/link";
import type { Pattern } from "@/types";
export const dynamic = "force-dynamic";
const PATTERN_TYPE_ORDER = ["algorithm", "technique", "data_structure"];
const PATTERN_TYPE_LABELS: Record<string, string> = {
algorithm: "Algorithmic Patterns",
technique: "Techniques",
data_structure: "Data Structure Patterns",
};
function PatternCard({ pattern }: { pattern: Pattern }) {
return (
<Link
href={`/patterns/${pattern.slug}`}
className="p-6 rounded-lg border border-[var(--border)] bg-[var(--card)] hover:border-[var(--primary)] transition-colors"
>
<div className="flex items-center justify-between mb-2">
<h3 className="font-semibold">{pattern.name}</h3>
<span className="text-sm text-[var(--muted-foreground)]">
{pattern.question_count} questions
</span>
</div>
{pattern.description && (
<p className="text-sm text-[var(--muted-foreground)] mb-3">
{pattern.description}
</p>
)}
</Link>
);
}
export default async function PatternsPage() {
let patterns;
let patterns: Pattern[];
try {
const response = await getPatterns();
patterns = response.items;
@@ -19,6 +48,29 @@ export default async function PatternsPage() {
);
}
// Group patterns by type
const groupedPatterns = patterns.reduce(
(acc, pattern) => {
const type = pattern.pattern_type || "other";
if (!acc[type]) acc[type] = [];
acc[type].push(pattern);
return acc;
},
{} as Record<string, Pattern[]>
);
// Sort each group by display_order
for (const type of Object.keys(groupedPatterns)) {
groupedPatterns[type].sort(
(a, b) => (a.display_order ?? 999) - (b.display_order ?? 999)
);
}
// Check if we have typed patterns to display grouped
const hasTypedPatterns = PATTERN_TYPE_ORDER.some(
(type) => groupedPatterns[type]?.length > 0
);
return (
<div className="space-y-6">
<h1 className="text-3xl font-bold">Algorithmic Patterns</h1>
@@ -27,27 +79,41 @@ export default async function PatternsPage() {
interviews.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{patterns.map((pattern) => (
<Link
key={pattern.id}
href={`/patterns/${pattern.slug}`}
className="p-6 rounded-lg border border-[var(--border)] bg-[var(--card)] hover:border-[var(--primary)] transition-colors"
>
<div className="flex items-center justify-between mb-2">
<h3 className="font-semibold">{pattern.name}</h3>
<span className="text-sm text-[var(--muted-foreground)]">
{pattern.question_count} questions
</span>
</div>
{pattern.description && (
<p className="text-sm text-[var(--muted-foreground)] mb-3">
{pattern.description}
</p>
)}
</Link>
))}
</div>
{hasTypedPatterns ? (
<>
{PATTERN_TYPE_ORDER.map(
(type) =>
groupedPatterns[type]?.length > 0 && (
<section key={type} className="space-y-4">
<h2 className="text-xl font-semibold">
{PATTERN_TYPE_LABELS[type]}
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{groupedPatterns[type].map((pattern) => (
<PatternCard key={pattern.id} pattern={pattern} />
))}
</div>
</section>
)
)}
{groupedPatterns["other"]?.length > 0 && (
<section className="space-y-4">
<h2 className="text-xl font-semibold">Other Patterns</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{groupedPatterns["other"].map((pattern) => (
<PatternCard key={pattern.id} pattern={pattern} />
))}
</div>
</section>
)}
</>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{patterns.map((pattern) => (
<PatternCard key={pattern.id} pattern={pattern} />
))}
</div>
)}
</div>
);
}

View File

@@ -39,6 +39,7 @@ export function QuestionCard({ question }: QuestionCardProps) {
{question.patterns.map((p) => (
<Badge key={p.id} variant="pattern">
{p.name}
{p.is_optimal && <span className="ml-1 text-green-500"></span>}
</Badge>
))}
{question.leetcode_id && (

View File

@@ -10,6 +10,7 @@ export interface PatternBrief {
id: string;
name: string;
slug: string;
is_optimal?: boolean;
}
export interface Category extends CategoryBrief {
@@ -21,6 +22,8 @@ export interface Pattern extends PatternBrief {
description: string | null;
when_to_use: string | null;
question_count: number;
pattern_type?: string;
display_order?: number;
}
// Pattern Tutorial Types