Skip to content

Commit 54a8985

Browse files
authored
Develop (#166)
Pr aberta pelo @GiovanniDonati para correção dos endpoints do card.
2 parents 1251175 + 8f9ee09 commit 54a8985

18 files changed

Lines changed: 378 additions & 573 deletions

File tree

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,6 @@ Usuários:
247247

248248
Jobs:
249249

250-
- GET /jobs
251-
- GET /jobs/files
252250
- GET /jobs/search
253251

254252
Keywords:

docker-compose.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
services:
32
scraper-go:
43
build:

docker/node.Dockerfile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ COPY --from=frontend /app/frontend/dist /usr/share/nginx/html
4040

4141
FROM deps AS front-admin
4242

43+
ARG VITE_API_URL
44+
ARG VITE_APP_ENV=production
45+
46+
ENV VITE_API_URL=$VITE_API_URL
47+
ENV VITE_APP_ENV=$VITE_APP_ENV
48+
4349
COPY front_admin ./front_admin
4450

4551
WORKDIR /app/front_admin
@@ -50,4 +56,4 @@ RUN npm run build
5056

5157
FROM nginx:1.27-alpine AS front-admin-runtime
5258

53-
COPY --from=front-admin /app/front_admin/dist /usr/share/nginx/html
59+
COPY --from=front-admin /app/front_admin/dist /usr/share/nginx/html

frontend/src/domains/jobs/application/useJobsData.ts

Lines changed: 37 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,44 @@
11
import {
2-
fetchJobFiles,
3-
fetchJobsByFile,
2+
fetchJobsByAPI,
43
runScraperRequest,
54
} from "@/domains/jobs/infrastructure/jobsApi";
6-
import type { Job, JobFile, JobsMeta } from "@/domains/jobs/domain/job.types";
5+
import type { Job, JobsResponse } from "@/domains/jobs/domain/job.types";
76
import { useCallback, useEffect, useState } from "react";
87

9-
const EMPTY_META: JobsMeta = { file: "", modifiedAt: null, total: 0 };
8+
export type JobsPaginationMeta = Omit<JobsResponse, "jobs">;
109

11-
export function useJobsData(initialSelectedFile = "") {
12-
const [files, setFiles] = useState<JobFile[]>([]);
13-
const [selectedFile, setSelectedFile] = useState(initialSelectedFile);
10+
const EMPTY_META: JobsPaginationMeta = {
11+
total: 0,
12+
hasNext: false,
13+
hasPrev: false,
14+
page: 0,
15+
limit: 0,
16+
totalPages: 0,
17+
};
18+
19+
export function useJobsData(page: number = 1, limit: number = 5) {
1420
const [jobs, setJobs] = useState<Job[]>([]);
15-
const [meta, setMeta] = useState<JobsMeta>(EMPTY_META);
21+
const [meta, setMeta] = useState<JobsPaginationMeta>(EMPTY_META);
1622
const [loading, setLoading] = useState(false);
1723
const [scraping, setScraping] = useState(false);
1824
const [error, setError] = useState("");
1925

20-
const loadFiles = useCallback(async () => {
21-
const foundFiles = await fetchJobFiles();
22-
setFiles(foundFiles);
23-
return foundFiles;
24-
}, []);
25-
26-
const loadJobs = useCallback(async (fileName: string) => {
26+
const loadJobs = useCallback(async () => {
2727
setLoading(true);
2828
setError("");
2929

3030
try {
31-
const data = await fetchJobsByFile(fileName);
32-
setJobs(data.jobs);
31+
const data = await fetchJobsByAPI(page, limit);
32+
setJobs(data.jobs ?? []);
33+
3334
setMeta({
34-
file: data.file,
35-
modifiedAt: data.modifiedAt,
36-
total: data.total,
35+
total: data.total ?? 0,
36+
hasNext: data.hasNext ?? false,
37+
hasPrev: data.hasPrev ?? false,
38+
page: data.page ?? page,
39+
limit: data.limit ?? limit,
40+
totalPages: data.totalPages ?? 0,
3741
});
38-
39-
if (data.file && data.file !== fileName) {
40-
setSelectedFile(data.file);
41-
}
4242
} catch (err: unknown) {
4343
setJobs([]);
4444
setMeta(EMPTY_META);
@@ -50,57 +50,30 @@ export function useJobsData(initialSelectedFile = "") {
5050
} finally {
5151
setLoading(false);
5252
}
53-
}, []);
53+
}, [page, limit]);
5454

5555
useEffect(() => {
56-
async function initializeFiles() {
57-
const foundFiles = await loadFiles();
58-
if (foundFiles[0]?.file) {
59-
setSelectedFile((current) => current || foundFiles[0].file);
60-
}
61-
}
56+
let isMounted = true;
6257

63-
initializeFiles().catch(() => {
64-
setError("Nao foi possivel listar arquivos .xlsx da pasta output.");
65-
});
66-
}, [loadFiles]);
58+
const initialize = async () => {
59+
if (!isMounted) return;
60+
await loadJobs();
61+
};
6762

68-
useEffect(() => {
69-
const syncState = setTimeout(() => {
70-
if (!selectedFile) {
71-
setJobs([]);
72-
setMeta(EMPTY_META);
73-
return;
74-
}
63+
initialize();
7564

76-
loadJobs(selectedFile);
77-
}, 0);
78-
79-
return () => clearTimeout(syncState);
80-
}, [selectedFile, loadJobs]);
65+
return () => {
66+
isMounted = false;
67+
};
68+
}, [loadJobs]);
8169

8270
const triggerScraper = useCallback(async () => {
8371
setScraping(true);
8472
setError("");
8573

8674
try {
8775
await runScraperRequest();
88-
const foundFiles = await loadFiles();
89-
const nextFile = foundFiles[0]?.file ?? "";
90-
91-
if (!nextFile) {
92-
setSelectedFile("");
93-
setJobs([]);
94-
setMeta(EMPTY_META);
95-
return;
96-
}
97-
98-
if (nextFile !== selectedFile) {
99-
setSelectedFile(nextFile);
100-
return;
101-
}
102-
103-
await loadJobs(nextFile);
76+
await loadJobs();
10477
} catch (err: unknown) {
10578
setError(
10679
err instanceof Error
@@ -110,12 +83,9 @@ export function useJobsData(initialSelectedFile = "") {
11083
} finally {
11184
setScraping(false);
11285
}
113-
}, [loadFiles, loadJobs, selectedFile]);
86+
}, [loadJobs]);
11487

11588
return {
116-
files,
117-
selectedFile,
118-
setSelectedFile,
11989
jobs,
12090
meta,
12191
loading,
Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
11
export interface Job {
2-
palavra?: string | null;
2+
keyword?: string | null;
33
keywords?: string[] | null;
4-
titulo?: string | null;
5-
empresa?: string | null;
4+
title?: string | null;
5+
company?: string | null;
66
source?: string | null;
77
sources?: string[] | null;
8-
local?: string | null;
9-
link?: string | null;
10-
}
11-
12-
export interface JobFile {
13-
file: string;
8+
location?: string | null;
9+
url?: string | null;
1410
}
1511

1612
export interface JobsMeta {
17-
file: string;
18-
modifiedAt: string | number | null;
13+
hasNext: boolean;
14+
hasPrev: boolean;
15+
limit: number;
16+
page: number;
1917
total: number;
18+
totalPages: number;
2019
}
2120

22-
export interface JobsResponse {
21+
export interface JobsResponse extends JobsMeta {
2322
jobs: Job[];
24-
file: string;
25-
modifiedAt: string | number | null;
26-
total: number;
2723
}

frontend/src/domains/jobs/infrastructure/jobsApi.ts

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { JobFile, JobsResponse } from "@/domains/jobs/domain/job.types";
1+
import type { JobsResponse } from "@/domains/jobs/domain/job.types";
22

33
function normalizeBaseUrl(value: string | undefined): string {
44
return typeof value === "string" ? value.trim().replace(/\/+$/, "") : "";
@@ -57,38 +57,15 @@ function readMessage(payload: unknown): string | undefined {
5757
return undefined;
5858
}
5959

60-
export async function fetchJobFiles(): Promise<JobFile[]> {
61-
const response = await fetch(buildApiUrl("/jobs/files"), {
62-
credentials: "include",
63-
});
64-
const payload = (await readPayload(response)) as { files?: unknown } & Record<
65-
string,
66-
unknown
67-
>;
68-
69-
if (!response.ok) {
70-
throw buildError(
71-
readMessage(payload),
72-
"Falha ao listar arquivos de vagas.",
73-
);
74-
}
75-
76-
if (!Array.isArray(payload.files)) {
77-
return [];
78-
}
60+
export async function fetchJobsByAPI(page?: number, limit?: number): Promise<JobsResponse> {
61+
const queryParams = new URLSearchParams();
62+
if (page !== undefined) queryParams.set("page", String(page));
63+
if (limit !== undefined) queryParams.set("limit", String(limit));
7964

80-
return payload.files.filter(
81-
(entry): entry is JobFile =>
82-
!!entry &&
83-
typeof entry === "object" &&
84-
"file" in entry &&
85-
typeof (entry as { file: unknown }).file === "string",
86-
);
87-
}
65+
const queryString = queryParams.toString();
66+
const urlPath = queryString ? `/jobs/search?${queryString}` : `/jobs/search`;
8867

89-
export async function fetchJobsByFile(fileName: string): Promise<JobsResponse> {
90-
const suffix = fileName ? `?file=${encodeURIComponent(fileName)}` : "";
91-
const response = await fetch(buildApiUrl(`/jobs${suffix}`), {
68+
const response = await fetch(buildApiUrl(urlPath), {
9269
credentials: "include",
9370
});
9471
const payload = (await readPayload(response)) as Record<string, unknown>;
@@ -99,13 +76,12 @@ export async function fetchJobsByFile(fileName: string): Promise<JobsResponse> {
9976

10077
return {
10178
jobs: Array.isArray(payload.jobs) ? payload.jobs : [],
102-
file: typeof payload.file === "string" ? payload.file : "",
103-
modifiedAt:
104-
typeof payload.modifiedAt === "string" ||
105-
typeof payload.modifiedAt === "number"
106-
? payload.modifiedAt
107-
: null,
10879
total: Number(payload.total || 0),
80+
hasNext: Boolean(payload.hasNext),
81+
hasPrev: Boolean(payload.hasPrev),
82+
page: Number(payload.page || 0),
83+
limit: Number(payload.limit || 0),
84+
totalPages: Number(payload.totalPages || 0),
10985
};
11086
}
11187

frontend/src/domains/jobs/presentation/components/JobsFiltersCard.tsx

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Badge } from "@/shared/ui/badge";
22
import { Card, CardContent } from "@/shared/ui/card";
33
import { Input } from "@/shared/ui/input";
4-
import type { JobFile, JobsMeta } from "@/domains/jobs/domain/job.types";
4+
import type { JobsPaginationMeta } from "@/domains/jobs/application/useJobsData";
55
import {
66
useMemo,
77
useState,
@@ -13,7 +13,6 @@ import {
1313
import {
1414
FiBriefcase,
1515
FiCheck,
16-
FiFileText,
1716
FiFilter,
1817
FiPlus,
1918
FiSearch,
@@ -30,10 +29,7 @@ interface JobsFiltersCardProps {
3029
onRemoveFilter: (filterToRemove: string) => void;
3130
onClearFilters: () => void;
3231
keywords: string[];
33-
selectedFile: string;
34-
setSelectedFile: Dispatch<SetStateAction<string>>;
35-
files: JobFile[];
36-
meta: JobsMeta;
32+
meta: JobsPaginationMeta;
3733
actions?: ReactNode;
3834
}
3935

@@ -56,11 +52,7 @@ export function JobsFiltersCard({
5652
onRemoveFilter,
5753
onClearFilters,
5854
keywords,
59-
selectedFile,
60-
setSelectedFile,
61-
files,
6255
meta,
63-
actions,
6456
}: JobsFiltersCardProps) {
6557
const [seeKeywordsModal, setSeeKeywordsModal] = useState(false);
6658
const [searchTerm, setSearchTerm] = useState("");
@@ -99,7 +91,10 @@ export function JobsFiltersCard({
9991
}
10092

10193
setSearch((current) => {
102-
const currentTerms = getSelectedFilters(current, []);
94+
const currentTerms = current
95+
.split(/[,;/]+/)
96+
.map((item) => item.trim())
97+
.filter(Boolean);
10398

10499
if (currentTerms.includes(nextTerm)) {
105100
return current;
@@ -132,7 +127,6 @@ export function JobsFiltersCard({
132127
<FiPlus className="h-4 w-4" />
133128
</button>
134129
</form>
135-
<div className="flex items-center gap-2">{actions}</div>
136130
</div>
137131

138132
<div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between">
@@ -151,28 +145,7 @@ export function JobsFiltersCard({
151145
))}
152146
</select>
153147

154-
<select
155-
aria-label="Selecionar arquivo"
156-
className="h-12 min-w-[180px] rounded-2xl border border-slate-300 bg-white px-3 text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-[#14AE5C]/40 dark:border-[#35506f] dark:bg-[#0b1527] dark:text-slate-100"
157-
value={selectedFile}
158-
onChange={(event) => setSelectedFile(event.target.value)}
159-
>
160-
{files.map((file) => (
161-
<option key={file.file} value={file.file}>
162-
{file.file}
163-
</option>
164-
))}
165-
</select>
166-
167148
<div>
168-
<Badge
169-
variant="secondary"
170-
className="gap-1.5 rounded-full border border-slate-300 bg-slate-100 px-3 py-1 text-xs text-slate-700 dark:border-[#35506f] dark:bg-[#24324c] dark:text-slate-100"
171-
>
172-
<FiFileText className="h-3.5 w-3.5" />
173-
{meta.file || "Sem arquivo"}
174-
</Badge>
175-
176149
<Badge className="gap-1.5 rounded-full bg-[#0c6b35] px-3 py-1 text-xs text-white">
177150
<FiBriefcase className="h-3.5 w-3.5" />
178151
{meta.total} vagas
@@ -239,4 +212,4 @@ export function JobsFiltersCard({
239212
)}
240213
</>
241214
);
242-
}
215+
}

0 commit comments

Comments
 (0)