Skip to content

feat: refactor job filtering and enhance UI components for better user - #55

Merged
Benevanio merged 1 commit into
developfrom
feature/change-log-name
Apr 4, 2026
Merged

Benevanio merged 1 commit into
developfrom
feature/change-log-name

Conversation

@Benevanio

Copy link
Copy Markdown
Collaborator

No description provided.

Copilot AI review requested due to automatic review settings April 4, 2026 19:39
@vercel

vercel Bot commented Apr 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
painel-vagas Ready Ready Preview, Comment Apr 4, 2026 7:39pm

@Benevanio
Benevanio merged commit ab8c279 into develop Apr 4, 2026
3 of 4 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in JobAtlas – Kanban Apr 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the frontend job filtering flow to support selecting multiple keywords at once, and refreshes the UI for filtering, table display, and actions (including new status toggles and updated icons).

Changes:

  • Switch keyword filtering from a single value ("all" / string) to a multi-select model (string[]) and update related hooks/tests.
  • Revamp filter and table UI (selected filter chips, keyword tag rendering, spam/read toggles, updated styling).
  • Replace some lucide-react icon usage with react-icons and add the dependency.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
package-lock.json Adds react-icons dependency lock entries.
frontend/package.json Adds react-icons to frontend dependencies.
frontend/src/hooks/useJobsFiltering.ts Updates filtering state to string[] and adjusts keyword filtering logic.
frontend/src/components/JobsFiltersCard.tsx Refactors filter UI for multi-select keywords + selected filter chips + manage/clear actions.
frontend/src/components/JobsTableCard.tsx Splits keyword display into tags and adds spam/read toggle controls and new table columns.
frontend/src/components/KeywordsModal.tsx Renames/retitles modal copy to “filtros” and updates placeholder/button text.
frontend/src/App.tsx Updates keyword filter callback typing and replaces refresh icon with react-icons.
frontend/tests/unit/utils/useJobsFiltering.test.ts Adds test coverage for multi-keyword selection.
frontend/tests/unit/pages/App.test.tsx Updates mocks to match new hook API (scraping, triggerScraper, keywordFilter array).
frontend/tests/unit/components/JobsTableCard.test.tsx Updates fixture data and adds tests for keyword tags + spam/read toggles.
frontend/tests/unit/components/JobsFiltersCard.test.tsx Updates props for keywordFilter array and adds test for managing/clearing filters.
backend/src/db/environment.json Updates default KEYWORDS seed list.
Comments suppressed due to low confidence (1)

frontend/src/hooks/useJobsFiltering.ts:11

  • keywords is currently derived from the raw job.palavra string. If palavra can contain multiple tags (e.g. "JavaScript, UX-UI"), this will surface the whole comma-separated value as a single selectable keyword, making it impossible to filter by individual tags. Consider normalizing/splitting palavra into individual tokens before building the keywords list (and sorting/uniquing those tokens).
  const keywords = useMemo(() => {
    const values = Array.from(new Set(jobs.map((job) => String(job.palavra || "").trim()).filter(Boolean)));
    return values.sort((a, b) => a.localeCompare(b));
  }, [jobs]);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 16 to 20
return jobs.filter((job) => {
const byKeyword = keywordFilter === "all" || String(job.palavra || "") === keywordFilter;
const currentKeyword = String(job.palavra || "").trim();
const byKeyword = keywordFilter.length === 0 || keywordFilter.includes(currentKeyword);
if (!byKeyword) {
return false;

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

byKeyword compares the selected filters against the full job.palavra string (keywordFilter.includes(currentKeyword)). When job.palavra contains multiple comma/semicolon-separated keywords, selecting one keyword won't match the job. Consider splitting job.palavra into tokens and checking for intersection with keywordFilter (e.g., any token in selected filters).

Copilot uses AI. Check for mistakes.
const PAGE_SIZE_OPTIONS = [15, 25, 50, 100];

function getJobId(job: Job, index: number) {
return job.link?.trim() || [job.titulo, job.empresa, job.local, String(index)].filter(Boolean).join("|");

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getJobId returns job.link as-is when present. If two rows share the same link, this produces duplicate React keys and also aliases the spam/read state between different jobs. Consider always including a disambiguator (e.g., append index or another stable field) even when link exists.

Suggested change
return job.link?.trim() || [job.titulo, job.empresa, job.local, String(index)].filter(Boolean).join("|");
const baseId = job.link?.trim() || [job.titulo, job.empresa, job.local].filter(Boolean).join("|");
return [baseId, String(index)].filter(Boolean).join("|");

Copilot uses AI. Check for mistakes.
Comment on lines +151 to +152
{paginatedJobs.map((job, index) => {
const jobId = getJobId(job, index);

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getJobId(job, index) is called with the index from paginatedJobs.map. For jobs without a link, this makes the row key (and spam/read marks) unstable across pagination/page-size changes because the per-page index changes. Consider deriving the ID from stable job fields only, or using a stable index from the original (unpaginated) list.

Suggested change
{paginatedJobs.map((job, index) => {
const jobId = getJobId(job, index);
{paginatedJobs.map((job) => {
const stableJobIndex = filteredJobs.indexOf(job);
const jobId = getJobId(job, stableJobIndex);

Copilot uses AI. Check for mistakes.
{keywords.length > 0 ? (
<div className="flex flex-wrap gap-1 text-sm text-foreground/85">
{keywords.map((keyword, keywordIndex) => (
<div key={`${jobId}-${keyword}`} className="flex items-center">

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside the keywords map, the key is built from ${jobId}-${keyword}. If the same keyword appears more than once in the split list (or jobId is duplicated), React keys will collide. Use keywordIndex (already available) as part of the key to guarantee uniqueness.

Suggested change
<div key={`${jobId}-${keyword}`} className="flex items-center">
<div key={`${jobId}-${keyword}-${keywordIndex}`} className="flex items-center">

Copilot uses AI. Check for mistakes.
<TableHead>Local</TableHead>
<TableHead>Link</TableHead>
<TableHead>Palavra-chave</TableHead>
<TableHead>Palavras chaves</TableHead>

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI text has a couple of Portuguese spelling/orthography issues: "Palavras chaves" is typically written as "Palavras-chave" (with hyphen). Aligning the label improves consistency and readability.

Suggested change
<TableHead>Palavras chaves</TableHead>
<TableHead>Palavras-chave</TableHead>

Copilot uses AI. Check for mistakes.
value={keywordFilter[keywordFilter.length - 1] ?? "all"}
onChange={(event) => handleKeywordSelect(event.target.value)}
>
<option value="all">Todas as palavras chaves</option>

Copilot AI Apr 4, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI text: "Todas as palavras chaves" should be "Todas as palavras-chave" (hyphen). This also matches the phrasing used elsewhere in the UI/test descriptions.

Suggested change
<option value="all">Todas as palavras chaves</option>
<option value="all">Todas as palavras-chave</option>

Copilot uses AI. Check for mistakes.

This branch was previously deployed

1 inactive deployment
Preview — 82a01718 Deployed Apr 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants