feat(patterns): pattern taxonomy + is_optimal
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user