feat: refactor job filtering and enhance UI components for better user - #55
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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-reacticon usage withreact-iconsand 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
keywordsis currently derived from the rawjob.palavrastring. Ifpalavracan 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/splittingpalavrainto individual tokens before building thekeywordslist (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.
| 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; |
There was a problem hiding this comment.
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).
| 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("|"); |
There was a problem hiding this comment.
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.
| 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("|"); |
| {paginatedJobs.map((job, index) => { | ||
| const jobId = getJobId(job, index); |
There was a problem hiding this comment.
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.
| {paginatedJobs.map((job, index) => { | |
| const jobId = getJobId(job, index); | |
| {paginatedJobs.map((job) => { | |
| const stableJobIndex = filteredJobs.indexOf(job); | |
| const jobId = getJobId(job, stableJobIndex); |
| {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"> |
There was a problem hiding this comment.
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.
| <div key={`${jobId}-${keyword}`} className="flex items-center"> | |
| <div key={`${jobId}-${keyword}-${keywordIndex}`} className="flex items-center"> |
| <TableHead>Local</TableHead> | ||
| <TableHead>Link</TableHead> | ||
| <TableHead>Palavra-chave</TableHead> | ||
| <TableHead>Palavras chaves</TableHead> |
There was a problem hiding this comment.
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.
| <TableHead>Palavras chaves</TableHead> | |
| <TableHead>Palavras-chave</TableHead> |
| value={keywordFilter[keywordFilter.length - 1] ?? "all"} | ||
| onChange={(event) => handleKeywordSelect(event.target.value)} | ||
| > | ||
| <option value="all">Todas as palavras chaves</option> |
There was a problem hiding this comment.
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.
| <option value="all">Todas as palavras chaves</option> | |
| <option value="all">Todas as palavras-chave</option> |
No description provided.