Skip to content

Commit 23a09ec

Browse files
feat: implement TypeScript support and refactor job listing components; add JobsHeaderCard, JobsFiltersCard, JobsTableCard, and utility components for improved functionality and UI
1 parent c460dfb commit 23a09ec

21 files changed

Lines changed: 214 additions & 75 deletions

frontend/eslint.config.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import js from '@eslint/js'
22
import globals from 'globals'
33
import reactHooks from 'eslint-plugin-react-hooks'
44
import reactRefresh from 'eslint-plugin-react-refresh'
5+
import tseslint from 'typescript-eslint'
56
import { defineConfig, globalIgnores } from 'eslint/config'
67

78
export default defineConfig([
89
globalIgnores(['dist']),
910
{
10-
files: ['**/*.{js,jsx}'],
11+
files: ['**/*.{ts,tsx}'],
1112
extends: [
1213
js.configs.recommended,
14+
...tseslint.configs.recommended,
1315
reactHooks.configs.flat.recommended,
1416
reactRefresh.configs.vite,
1517
],
@@ -23,7 +25,9 @@ export default defineConfig([
2325
},
2426
},
2527
rules: {
26-
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
28+
'no-unused-vars': 'off',
29+
'@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
30+
'react-refresh/only-export-components': 'off',
2731
},
2832
},
2933
])

frontend/jsconfig.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

frontend/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
"@types/react": "^19.2.14",
2323
"@types/react-dom": "^19.2.3",
2424
"@vitejs/plugin-react": "^6.0.1",
25+
"typescript": "^5.9.3",
26+
"typescript-eslint": "^8.46.1",
2527
"autoprefixer": "^10.4.27",
2628
"eslint": "^9.39.4",
2729
"eslint-plugin-react-hooks": "^7.0.1",
@@ -31,4 +33,4 @@
3133
"tailwindcss": "^3.4.17",
3234
"vite": "^8.0.1"
3335
}
34-
}
36+
}
Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1-
import { useEffect, useMemo, useState } from "react";
1+
import { useCallback, useEffect, useMemo, useState } from "react";
22
import { JobsFiltersCard } from "@/components/JobsFiltersCard";
33
import { JobsHeaderCard } from "@/components/JobsHeaderCard";
44
import { JobsTableCard } from "@/components/JobsTableCard";
5+
import { fetchJobFiles, fetchJobsByFile } from "@/services/jobsService";
6+
import type { Job, JobFile, JobsMeta } from "@/types/jobs";
57

6-
function formatDate(timestamp) {
8+
function formatDate(timestamp: JobsMeta["modifiedAt"]): string {
79
if (!timestamp) {
810
return "-";
911
}
1012
return new Date(timestamp).toLocaleString("pt-BR");
1113
}
1214

1315
function App() {
14-
const [files, setFiles] = useState([]);
16+
const [files, setFiles] = useState<JobFile[]>([]);
1517
const [selectedFile, setSelectedFile] = useState("");
1618
const [search, setSearch] = useState("");
1719
const [keywordFilter, setKeywordFilter] = useState("all");
18-
const [jobs, setJobs] = useState([]);
19-
const [meta, setMeta] = useState({ file: "", modifiedAt: null, total: 0 });
20+
const [jobs, setJobs] = useState<Job[]>([]);
21+
const [meta, setMeta] = useState<JobsMeta>({ file: "", modifiedAt: null, total: 0 });
2022
const [loading, setLoading] = useState(false);
2123
const [error, setError] = useState("");
2224

@@ -46,39 +48,30 @@ function App() {
4648
});
4749
}, [jobs, search, keywordFilter]);
4850

49-
async function loadJobs(fileName) {
51+
const loadJobs = useCallback(async (fileName: string) => {
5052
setLoading(true);
5153
setError("");
5254
try {
53-
const suffix = fileName ? `?file=${encodeURIComponent(fileName)}` : "";
54-
const response = await fetch(`/api/jobs${suffix}`);
55-
const payload = await response.json();
56-
57-
if (!response.ok) {
58-
throw new Error(payload.message || "Falha ao carregar vagas.");
59-
}
60-
61-
setJobs(Array.isArray(payload.jobs) ? payload.jobs : []);
55+
const data = await fetchJobsByFile(fileName);
56+
setJobs(data.jobs);
6257
setMeta({
63-
file: payload.file || "",
64-
modifiedAt: payload.modifiedAt || null,
65-
total: Number(payload.total || 0),
58+
file: data.file,
59+
modifiedAt: data.modifiedAt,
60+
total: data.total,
6661
});
67-
setSelectedFile(payload.file || fileName || "");
68-
} catch (err) {
62+
setSelectedFile(data.file || fileName || "");
63+
} catch (err: unknown) {
6964
setJobs([]);
7065
setMeta({ file: "", modifiedAt: null, total: 0 });
71-
setError(err.message || "Erro inesperado ao carregar vagas.");
66+
setError(err instanceof Error ? err.message : "Erro inesperado ao carregar vagas.");
7267
} finally {
7368
setLoading(false);
7469
}
75-
}
70+
}, []);
7671

7772
useEffect(() => {
7873
async function initializeFiles() {
79-
const response = await fetch("/api/jobs/files");
80-
const payload = await response.json();
81-
const foundFiles = Array.isArray(payload.files) ? payload.files : [];
74+
const foundFiles = await fetchJobFiles();
8275
setFiles(foundFiles);
8376
if (foundFiles[0]?.file) {
8477
setSelectedFile(foundFiles[0].file);
@@ -94,7 +87,7 @@ function App() {
9487
if (selectedFile) {
9588
loadJobs(selectedFile);
9689
}
97-
}, [selectedFile]);
90+
}, [selectedFile, loadJobs]);
9891

9992
return (
10093
<main className="relative min-h-screen overflow-hidden bg-background px-4 py-8 md:px-8">

frontend/src/components/JobsFiltersCard.jsx renamed to frontend/src/components/JobsFiltersCard.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
import { RefreshCcw, Search } from "lucide-react";
2+
import type { Dispatch, SetStateAction } from "react";
23
import { Button } from "@/components/ui/button";
34
import { Card, CardContent } from "@/components/ui/card";
45
import { Input } from "@/components/ui/input";
6+
import type { JobFile } from "@/types/jobs";
7+
8+
interface JobsFiltersCardProps {
9+
search: string;
10+
setSearch: Dispatch<SetStateAction<string>>;
11+
keywordFilter: string;
12+
setKeywordFilter: Dispatch<SetStateAction<string>>;
13+
keywords: string[];
14+
selectedFile: string;
15+
setSelectedFile: Dispatch<SetStateAction<string>>;
16+
files: JobFile[];
17+
loading: boolean;
18+
onRefresh: () => void;
19+
}
520

621
export function JobsFiltersCard({
722
search,
@@ -14,7 +29,7 @@ export function JobsFiltersCard({
1429
files,
1530
loading,
1631
onRefresh,
17-
}) {
32+
}: JobsFiltersCardProps) {
1833
return (
1934
<Card className="border-white/30 bg-card/85 backdrop-blur">
2035
<CardContent className="grid gap-3 pt-6 md:grid-cols-4">

frontend/src/components/JobsHeaderCard.jsx renamed to frontend/src/components/JobsHeaderCard.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { BriefcaseBusiness, FileSpreadsheet } from "lucide-react";
22
import { Badge } from "@/components/ui/badge";
33
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
4+
import type { JobsMeta } from "@/types/jobs";
45

5-
export function JobsHeaderCard({ meta }) {
6+
interface JobsHeaderCardProps {
7+
meta: JobsMeta;
8+
}
9+
10+
export function JobsHeaderCard({ meta }: JobsHeaderCardProps) {
611
return (
712
<Card className="border-white/30 bg-card/85 backdrop-blur">
813
<CardHeader className="gap-4 pb-4 md:flex-row md:items-center md:justify-between">
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
22
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
3+
import type { Job, JobsMeta } from "@/types/jobs";
34

4-
export function JobsTableCard({ meta, filteredJobs, jobs, loading, error, formatDate }) {
5+
interface JobsTableCardProps {
6+
meta: JobsMeta;
7+
filteredJobs: Job[];
8+
jobs: Job[];
9+
loading: boolean;
10+
error: string;
11+
formatDate: (timestamp: JobsMeta["modifiedAt"]) => string;
12+
}
13+
14+
export function JobsTableCard({ meta, filteredJobs, jobs, loading, error, formatDate }: JobsTableCardProps) {
515
return (
616
<Card className="border-white/30 bg-card/90 backdrop-blur">
717
<CardHeader>
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { cva } from "class-variance-authority";
1+
import type { HTMLAttributes } from "react";
2+
import { cva, type VariantProps } from "class-variance-authority";
23
import { cn } from "@/lib/utils";
34

45
const badgeVariants = cva(
@@ -16,7 +17,9 @@ const badgeVariants = cva(
1617
},
1718
);
1819

19-
function Badge({ className, variant, ...props }) {
20+
type BadgeProps = HTMLAttributes<HTMLSpanElement> & VariantProps<typeof badgeVariants>;
21+
22+
function Badge({ className, variant, ...props }: BadgeProps) {
2023
return <span className={cn(badgeVariants({ variant }), className)} {...props} />;
2124
}
2225

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { cva } from "class-variance-authority";
1+
import type { ButtonHTMLAttributes } from "react";
2+
import { cva, type VariantProps } from "class-variance-authority";
23
import { cn } from "@/lib/utils";
34

45
const buttonVariants = cva(
@@ -21,7 +22,9 @@ const buttonVariants = cva(
2122
},
2223
);
2324

24-
function Button({ className, variant, size, ...props }) {
25+
type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants>;
26+
27+
function Button({ className, variant, size, ...props }: ButtonProps) {
2528
return <button className={cn(buttonVariants({ variant, size, className }))} {...props} />;
2629
}
2730

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1+
import type { HTMLAttributes } from "react";
12
import { cn } from "@/lib/utils";
23

3-
function Card({ className, ...props }) {
4+
function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
45
return <div className={cn("rounded-xl border bg-card text-card-foreground shadow-sm", className)} {...props} />;
56
}
67

7-
function CardHeader({ className, ...props }) {
8+
function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
89
return <div className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />;
910
}
1011

11-
function CardTitle({ className, ...props }) {
12+
function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {
1213
return <h3 className={cn("text-xl font-semibold leading-none tracking-tight", className)} {...props} />;
1314
}
1415

15-
function CardDescription({ className, ...props }) {
16+
function CardDescription({ className, ...props }: HTMLAttributes<HTMLParagraphElement>) {
1617
return <p className={cn("text-sm text-muted-foreground", className)} {...props} />;
1718
}
1819

19-
function CardContent({ className, ...props }) {
20+
function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
2021
return <div className={cn("p-6 pt-0", className)} {...props} />;
2122
}
2223

0 commit comments

Comments
 (0)