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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Algoria

![](https://img.shields.io/badge/Versão-1.2.0-black?style=for-the-badge)
![](https://img.shields.io/badge/Versão-1.3.0-black?style=for-the-badge)

Plataforma em português para estudar **algoritmos e decisões em código** através de leitura guiada: catálogo de problemas com várias soluções (brute-force, óptima, alternativa), **code player** linha-a-linha com três níveis de explicação, mini-guias em **Conceitos**, **curso modular** com avaliações locais, hub de **inglês técnico para entrevistas** (conteúdo em inglês) e guias de **engenharia aplicada** (front, back, DevOps).

Expand All @@ -11,7 +11,7 @@ Plataforma em português para estudar **algoritmos e decisões em código** atra
## Funcionalidades

- 📚 **Catálogo de problemas** — enunciados em Markdown, tags, dificuldade e várias implementações lado a lado quando existirem
- 🎯 **Code player** — navegação linha-a-linha, destaque sintaxe (Shiki), painel com níveis Resumo / Detalhado / Deep dive e atalhos de teclado
- 🎯 **Code player** — navegação linha-a-linha, destaque sintaxe (Shiki), painel com níveis Resumo / Detalhado / Deep dive e atalhos de teclado, visualização de estruturas de dados e execução de código linha a linha.
- 🧠 **Conceitos** — páginas longas (fundamentos, estruturas, padrões) carregadas do repositório em Markdown
- 🎓 **Curso guiado** — trilha modular com exemplos, MCQs e certificado por capítulo (progresso no browser)
- 🌍 **Interview English** — hub `/interview-en` com vocabulário e scripts 100% em inglês para entrevistas
Expand Down
22 changes: 20 additions & 2 deletions app/admin/content/_components/dashboard/dashboard-filters.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { EDITORIAL_TYPES, SYSTEM_TYPE_OPTIONS, STATUS_FILTERS } from "./dashboard-types";
import { EDITORIAL_TYPES, SYSTEM_TYPE_OPTIONS, STATUS_FILTERS, CATEGORY_OPTIONS } from "./dashboard-types";

interface DashboardFiltersProps {
tab: 'editorial' | 'sistema';
Expand All @@ -10,6 +10,8 @@ interface DashboardFiltersProps {
onTypeFilterChange: (v: string) => void;
statusFilter: string;
onStatusFilterChange: (v: string) => void;
categoryFilter: string;
onCategoryFilterChange: (v: string) => void;
onClearFilters: () => void;
}

Expand All @@ -21,6 +23,8 @@ export function DashboardFilters({
onTypeFilterChange,
statusFilter,
onStatusFilterChange,
categoryFilter,
onCategoryFilterChange,
onClearFilters,
}: DashboardFiltersProps) {
const typeOptions = tab === 'editorial' ? EDITORIAL_TYPES : SYSTEM_TYPE_OPTIONS;
Expand Down Expand Up @@ -63,6 +67,20 @@ export function DashboardFilters({
))}
</select>

{typeOptions === EDITORIAL_TYPES && (
<select
value={categoryFilter}
onChange={(e) => onCategoryFilterChange(e.target.value)}
className="h-10 rounded-md border border-input bg-background px-3 text-sm outline-none focus:ring-2 focus:ring-primary/20"
>
{CATEGORY_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
)}

<select
value={statusFilter}
onChange={(e) => onStatusFilterChange(e.target.value)}
Expand All @@ -75,7 +93,7 @@ export function DashboardFilters({
))}
</select>

{(search || typeFilter || statusFilter) && (
{(search || typeFilter || statusFilter || categoryFilter) && (
<button
onClick={onClearFilters}
className="h-10 rounded-md px-3 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
Expand Down
22 changes: 22 additions & 0 deletions app/admin/content/_components/dashboard/dashboard-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,25 @@ export const STATUS_FILTERS = [
{ value: "PUBLISHED", label: "Publicado" },
{ value: "REJECTED", label: "Rejeitado" },
];

export const CATEGORY_OPTIONS = [
{ value: "", label: "Todas as categorias" },
{ value: "arrays", label: "Arrays" },
{ value: "hash-tables", label: "Hash Tables" },
{ value: "two-pointers", label: "Two Pointers" },
{ value: "sliding-window", label: "Sliding Window" },
{ value: "binary-search", label: "Binary Search" },
{ value: "linked-list", label: "Linked List" },
{ value: "trees", label: "Trees" },
{ value: "graphs", label: "Graphs" },
{ value: "dynamic-programming", label: "Dynamic Programming" },
{ value: "greedy", label: "Greedy" },
{ value: "backtracking", label: "Backtracking" },
{ value: "bit-manipulation", label: "Bit Manipulation" },
{ value: "math", label: "Math" },
{ value: "strings", label: "Strings" },
{ value: "stacks", label: "Stacks" },
{ value: "queues", label: "Queues" },
{ value: "recursion", label: "Recursion" },
{ value: "sorting", label: "Sorting" },
];
13 changes: 7 additions & 6 deletions app/admin/content/_components/forms/problem-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,15 @@ export function ProblemForm({
...meta,
difficulty: importedMeta.difficulty || meta.difficulty,
access: importedMeta.access || meta.access,
hasBespokeVisualizer: parsed.hasBespokeVisualizer ?? importedMeta.hasBespokeVisualizer ?? meta.hasBespokeVisualizer,
estimatedMinutes: importedMeta.estimatedMinutes || meta.estimatedMinutes,
recommendedOrder: importedMeta.recommendedOrder || meta.recommendedOrder,
categories: importedMeta.categories || meta.categories,
constraints: importedMeta.constraints || meta.constraints,
solutions: importedMeta.solutions || meta.solutions,
examples: importedMeta.examples || meta.examples,
tags: importedMeta.tags || meta.tags,
prerequisites: importedMeta.prerequisites || meta.prerequisites,
categories: importedMeta.categories || parsed.categories || meta.categories,
constraints: importedMeta.constraints || parsed.constraints || meta.constraints,
solutions: importedMeta.solutions || parsed.solutions || meta.solutions,
examples: importedMeta.examples || parsed.examples || meta.examples,
tags: importedMeta.tags || parsed.tags || meta.tags,
prerequisites: importedMeta.prerequisites || parsed.prerequisites || meta.prerequisites,
});

setIsImportOpen(false);
Expand Down
18 changes: 18 additions & 0 deletions app/admin/content/_components/solutions-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,24 @@ export function SolutionsList({
/>
</FormField>

<FormField
label="Simulator Code (Javascript)"
hint="Função (input) => steps[] para gerar a simulação visual."
>
<textarea
value={sol.meta.simulatorCode || ""}
onChange={(e) =>
updateSolution(idx, {
...sol,
meta: { ...sol.meta, simulatorCode: e.target.value },
})
}
className="w-full rounded-lg border border-border bg-[#080808] text-emerald-400 p-3 font-mono text-xs focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/30"
rows={8}
placeholder="(s) => { const steps = []; ... return steps; }"
/>
</FormField>

<div className="space-y-4 pt-4 border-t border-border">
<h4 className="text-xs font-black uppercase tracking-widest text-primary">
Implementações (Multi-Language)
Expand Down
8 changes: 7 additions & 1 deletion app/admin/content/_components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,20 @@ export interface EditorSolution {
name: string;
kind: string;
language: string;
entryFunction?: string;
simulatorCode?: string;
complexity: {
time: string;
space: string;
rationale: string;
};
entryFunction?: string;
};
codeByLanguage: Record<string, string>;
introMd: string;
annotations: unknown[];
executionTrace?: {
steps: unknown[];
};
}

export type ContentStatus =
Expand Down Expand Up @@ -121,6 +126,7 @@ export const DEFAULT_META: Record<string, Record<string, unknown>> = {
problem: {
difficulty: "easy",
categories: [],
hasBespokeVisualizer: false,
estimatedMinutes: 15,
access: "pro",
recommendedOrder: 1,
Expand Down
21 changes: 17 additions & 4 deletions app/admin/content/content-dashboard-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default function ContentDashboardClient({ isAdmin }: { isAdmin: boolean }
const [search, setSearch] = useState(searchParams.get('search') ?? '');
const [typeFilter, setTypeFilter] = useState(searchParams.get('type') ?? '');
const [statusFilter, setStatusFilter] = useState(searchParams.get('status') ?? '');
const [categoryFilter, setCategoryFilter] = useState(searchParams.get('category') ?? '');
const [accessFilter, setAccessFilter] = useState(searchParams.get('access') ?? '');
const [feedback, setFeedback] = useState<{ type: 'success' | 'error'; msg: string } | null>(null);

Expand All @@ -46,6 +47,7 @@ export default function ContentDashboardClient({ isAdmin }: { isAdmin: boolean }
const urlType = searchParams.get('type') ?? '';
const urlTab = searchParams.get('tab') ?? '';
const urlStatus = searchParams.get('status') ?? '';
const urlCategory = searchParams.get('category') ?? '';
const urlSearch = searchParams.get('search') ?? '';
const urlAccess = searchParams.get('access') ?? '';
const urlPage = parseInt(searchParams.get('page') || '1');
Expand All @@ -65,6 +67,7 @@ export default function ContentDashboardClient({ isAdmin }: { isAdmin: boolean }

setTypeFilter(urlType);
setStatusFilter(urlStatus);
setCategoryFilter(urlCategory);
setSearch(urlSearch);
setAccessFilter(urlAccess);
setPage(urlPage);
Expand All @@ -77,6 +80,7 @@ export default function ContentDashboardClient({ isAdmin }: { isAdmin: boolean }
search: s,
type: typeFilter || undefined,
status: statusFilter || undefined,
category: categoryFilter || undefined,
tab,
access: accessFilter || undefined,
});
Expand All @@ -85,27 +89,29 @@ export default function ContentDashboardClient({ isAdmin }: { isAdmin: boolean }
setRows(result.contents as unknown as ContentRow[]);
setTotal(result.total);
}
}, [typeFilter, statusFilter, tab, accessFilter]);
}, [typeFilter, statusFilter, categoryFilter, tab, accessFilter]);

useEffect(() => {
const timer = setTimeout(() => {
load(page, search);
}, 300);
return () => clearTimeout(timer);
}, [page, typeFilter, statusFilter, search, tab, accessFilter, load]);
}, [page, typeFilter, statusFilter, categoryFilter, search, tab, accessFilter, load]);

function handleTabChange(newTab: 'editorial' | 'sistema') {
setTab(newTab);
setPage(1);
setTypeFilter('');
setStatusFilter('');
setCategoryFilter('');
setSearch('');

// Update URL
const params = new URLSearchParams(searchParams.toString());
params.set('tab', newTab);
params.delete('type');
params.delete('status');
params.delete('category');
params.delete('search');
params.set('page', '1');
window.history.pushState(null, '', `?${params.toString()}`);
Expand Down Expand Up @@ -141,6 +147,7 @@ export default function ContentDashboardClient({ isAdmin }: { isAdmin: boolean }
setSearch('');
setTypeFilter('');
setStatusFilter('');
setCategoryFilter('');
setPage(1);
}

Expand Down Expand Up @@ -182,7 +189,7 @@ export default function ContentDashboardClient({ isAdmin }: { isAdmin: boolean }
)}

<DashboardFilters
tab={tab}
tab={accessFilter === 'pro' ? 'editorial' : tab}
search={search}
onSearchChange={(v) => {
setSearch(v);
Expand All @@ -201,9 +208,15 @@ export default function ContentDashboardClient({ isAdmin }: { isAdmin: boolean }
setPage(1);
updateUrl({ status: v, page: '1' });
}}
categoryFilter={categoryFilter}
onCategoryFilterChange={(v) => {
setCategoryFilter(v);
setPage(1);
updateUrl({ category: v, page: '1' });
}}
onClearFilters={() => {
clearFilters();
updateUrl({ type: null, status: null, search: null, page: '1' });
updateUrl({ type: null, status: null, category: null, search: null, page: '1' });
}}
/>

Expand Down
Loading
Loading