Skip to content

Commit 7c749f0

Browse files
authored
Develop (#34)
2 parents 2aa4efb + c6bda93 commit 7c749f0

15 files changed

Lines changed: 3151 additions & 4933 deletions

frontend/package-lock.json

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

frontend/src/App.tsx

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ import { JobsFiltersCard } from "@/components/JobsFiltersCard";
22
import { JobsHeaderCard } from "@/components/JobsHeaderCard";
33
import { JobsTableCard } from "@/components/JobsTableCard";
44
import { Button } from "@/components/ui/button";
5-
import { ThemeToggle } from "@/components/ui/theme-toggle";
65
import { useJobsData } from "@/hooks/useJobsData";
76
import { useJobsFiltering } from "@/hooks/useJobsFiltering";
87
import { useJobsPagination } from "@/hooks/useJobsPagination";
9-
import { useTheme } from "@/hooks/useTheme";
108
import type { JobsMeta } from "@/types/jobs";
119
import { RefreshCcw } from "lucide-react";
1210
import { useCallback, type SetStateAction } from "react";
@@ -19,9 +17,7 @@ function formatDate(timestamp: JobsMeta["modifiedAt"]): string {
1917
}
2018

2119
function App() {
22-
const { resolvedTheme, toggleTheme } = useTheme();
23-
24-
const { files, selectedFile, setSelectedFile, jobs, meta, loading, scraping, error, loadJobs, triggerScraper } =
20+
const { files, selectedFile, setSelectedFile, jobs, meta, loading, scraping, error, triggerScraper } =
2521
useJobsData();
2622

2723
const { search, setSearch, keywordFilter, setKeywordFilter, keywords, filteredJobs } = useJobsFiltering(jobs);
@@ -68,22 +64,11 @@ function App() {
6864
}, [triggerScraper]);
6965

7066
return (
71-
<main className="relative min-h-screen overflow-hidden bg-background px-4 py-8 transition-colors duration-300 md:px-8">
67+
<main className="relative min-h-screen overflow-hidden bg-background px-4 transition-colors duration-300 md:px-8">
7268
<div className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(circle_at_20%_15%,rgba(236,195,117,0.35),transparent_30%),radial-gradient(circle_at_80%_10%,rgba(92,151,191,0.28),transparent_35%),radial-gradient(circle_at_50%_95%,rgba(201,120,99,0.22),transparent_40%)] dark:bg-[radial-gradient(circle_at_20%_15%,rgba(240,180,95,0.18),transparent_30%),radial-gradient(circle_at_80%_10%,rgba(92,151,191,0.18),transparent_35%),radial-gradient(circle_at_50%_95%,rgba(201,120,99,0.15),transparent_40%)]" />
7369

74-
<section className="mx-auto flex w-full max-w-7xl flex-col gap-6">
75-
<JobsHeaderCard
76-
meta={meta}
77-
actions={
78-
<>
79-
<Button onClick={handleScraper} disabled={loading || scraping}>
80-
<RefreshCcw className={`h-4 w-4 ${scraping ? "animate-spin" : ""}`} />
81-
{scraping ? "Buscando vagas..." : "Buscar vagas"}
82-
</Button>
83-
<ThemeToggle theme={resolvedTheme} onToggle={toggleTheme} />
84-
</>
85-
}
86-
/>
70+
<section className="mx-auto flex w-full flex-col gap-6">
71+
<JobsHeaderCard />
8772

8873
<JobsFiltersCard
8974
search={search}
@@ -94,8 +79,17 @@ function App() {
9479
selectedFile={selectedFile}
9580
setSelectedFile={handleSelectedFileChange}
9681
files={files}
97-
loading={loading || scraping}
98-
onRefresh={() => loadJobs(selectedFile)}
82+
meta={meta}
83+
actions={
84+
<>
85+
<Button onClick={handleScraper} disabled={loading || scraping}>
86+
<RefreshCcw
87+
className={`h-4 w-4 ${scraping ? "animate-spin" : ""}`}
88+
/>
89+
{scraping ? "Buscando vagas..." : "Buscar vagas"}
90+
</Button>
91+
</>
92+
}
9993
/>
10094

10195
<JobsTableCard

frontend/src/assets/logo-painel-vagas.svg

Lines changed: 9 additions & 0 deletions
Loading

frontend/src/components/JobsFiltersCard.tsx

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Button } from "@/components/ui/button";
1+
import { Badge } from "@/components/ui/badge";
22
import { Card, CardContent } from "@/components/ui/card";
33
import { Input } from "@/components/ui/input";
4-
import type { JobFile } from "@/types/jobs";
5-
import { RefreshCcw, Search } from "lucide-react";
6-
import type { Dispatch, SetStateAction } from "react";
4+
import type { JobFile, JobsMeta } from "@/types/jobs";
5+
import { Search } from "lucide-react";
6+
import type { Dispatch, ReactNode, SetStateAction } from "react";
77

8+
import { BriefcaseBusiness, FileSpreadsheet } from "lucide-react";
89
interface JobsFiltersCardProps {
910
search: string;
1011
setSearch: Dispatch<SetStateAction<string>>;
@@ -14,8 +15,8 @@ interface JobsFiltersCardProps {
1415
selectedFile: string;
1516
setSelectedFile: Dispatch<SetStateAction<string>>;
1617
files: JobFile[];
17-
loading: boolean;
18-
onRefresh: () => void;
18+
meta: JobsMeta;
19+
actions?: ReactNode;
1920
}
2021

2122
export function JobsFiltersCard({
@@ -27,50 +28,65 @@ export function JobsFiltersCard({
2728
selectedFile,
2829
setSelectedFile,
2930
files,
30-
loading,
31-
onRefresh,
31+
meta,
32+
actions,
3233
}: JobsFiltersCardProps) {
3334
return (
3435
<Card className="border-border/70 bg-card/85 backdrop-blur dark:bg-card/95">
35-
<CardContent className="grid gap-3 pt-6 md:grid-cols-4">
36-
<div className="relative md:col-span-2">
37-
<Search className="pointer-events-none absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
38-
<Input
39-
value={search}
40-
onChange={(event) => setSearch(event.target.value)}
41-
className="pl-9"
42-
placeholder="Buscar por titulo, empresa, local ou link"
43-
/>
36+
<CardContent className="flex flex-col gap-3 pt-6">
37+
{/* Campo de busca */}
38+
<div className="flex items-center gap-3">
39+
<div className="relative flex-1">
40+
<Search className="pointer-events-none absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
41+
<Input
42+
value={search}
43+
onChange={(event) => setSearch(event.target.value)}
44+
className="pl-9 w-full"
45+
placeholder="Buscar por titulo, empresa, local ou link"
46+
/>
47+
</div>
48+
<div className="flex items-center gap-2">{actions}</div>
4449
</div>
4550

46-
<select
47-
className="h-10 w-full rounded-md border border-input bg-background px-3 text-sm"
48-
value={keywordFilter}
49-
onChange={(event) => setKeywordFilter(event.target.value)}
50-
>
51-
<option value="all">Todas as palavras-chave</option>
52-
{keywords.map((keyword) => (
53-
<option key={keyword} value={keyword}>
54-
{keyword}
55-
</option>
56-
))}
57-
</select>
58-
59-
<div className="flex gap-2">
51+
{/* Seção de filtros */}
52+
<div className="flex items-center gap-2 flex">
6053
<select
61-
className="h-10 w-full rounded-md border border-input bg-background px-3 text-sm"
62-
value={selectedFile}
63-
onChange={(event) => setSelectedFile(event.target.value)}
54+
className="h-10 rounded-md border border-input bg-background px-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring"
55+
value={keywordFilter}
56+
onChange={(event) => setKeywordFilter(event.target.value)}
6457
>
65-
{files.map((file) => (
66-
<option key={file.file} value={file.file}>
67-
{file.file}
58+
<option value="all">Todas as palavras-chave</option>
59+
{keywords.map((keyword) => (
60+
<option key={keyword} value={keyword}>
61+
{keyword}
6862
</option>
6963
))}
7064
</select>
71-
<Button variant="outline" size="sm" onClick={onRefresh} disabled={loading}>
72-
<RefreshCcw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
73-
</Button>
65+
66+
<div>
67+
<select
68+
className="h-10 w-full rounded-md border border-input bg-background px-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring"
69+
value={selectedFile}
70+
onChange={(event) => setSelectedFile(event.target.value)}
71+
>
72+
{files.map((file) => (
73+
<option key={file.file} value={file.file}>
74+
{file.file}
75+
</option>
76+
))}
77+
</select>
78+
</div>
79+
80+
<div className="flex items-center gap-2">
81+
<Badge variant="secondary" className="gap-1 text-xs">
82+
<FileSpreadsheet className="h-3.5 w-3.5" />
83+
{meta.file || "Sem arquivo"}
84+
</Badge>
85+
<Badge className="gap-1 text-xs">
86+
<BriefcaseBusiness className="h-3.5 w-3.5" />
87+
{meta.total} vagas
88+
</Badge>
89+
</div>
7490
</div>
7591
</CardContent>
7692
</Card>
Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,27 @@
1-
import { Badge } from "@/components/ui/badge";
2-
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
3-
import type { JobsMeta } from "@/types/jobs";
4-
import { BriefcaseBusiness, FileSpreadsheet } from "lucide-react";
5-
import type { ReactNode } from "react";
1+
import {
2+
CardDescription,
3+
CardTitle,
4+
} from "@/components/ui/card";
5+
import Logo from "../assets/logo-painel-vagas.svg";
6+
import { ThemeToggle } from "@/components/ui/theme-toggle";
7+
import { useTheme } from "@/hooks/useTheme";
68

7-
interface JobsHeaderCardProps {
8-
meta: JobsMeta;
9-
actions?: ReactNode;
10-
}
11-
12-
export function JobsHeaderCard({ meta, actions }: JobsHeaderCardProps) {
9+
export function JobsHeaderCard() {
10+
const { resolvedTheme, toggleTheme } = useTheme();
1311
return (
14-
<Card className="border-border/70 bg-card/85 backdrop-blur dark:bg-card/95">
15-
<CardHeader className="gap-4 pb-4 md:flex-row md:items-center md:justify-between">
16-
<div>
17-
<CardTitle className="text-3xl">Painel de Vagas</CardTitle>
18-
<CardDescription>Leitura automatica dos arquivos XLSX gerados em output.</CardDescription>
19-
</div>
20-
<div className="flex flex-wrap items-center gap-2">
21-
{actions}
22-
<Badge variant="secondary" className="gap-1 text-xs">
23-
<FileSpreadsheet className="h-3.5 w-3.5" />
24-
{meta.file || "Sem arquivo"}
25-
</Badge>
26-
<Badge className="gap-1 text-xs">
27-
<BriefcaseBusiness className="h-3.5 w-3.5" />
28-
{meta.total} vagas
29-
</Badge>
12+
<>
13+
<div className="w-screen bg-[#004726] dark:bg-[#003318] p-6 -mx-8 flex items-end">
14+
<CardTitle className="text-3xl text-white">
15+
<img src={Logo} alt="Painel de Vagas" />
16+
</CardTitle>
17+
<CardDescription className="text-white">
18+
Leitura automatica dos arquivos XLSX gerados em output.
19+
</CardDescription>
20+
<div className="ml-auto self-start mr-8">
21+
<ThemeToggle theme={resolvedTheme} onToggle={toggleTheme} />
3022
</div>
31-
</CardHeader>
32-
</Card>
23+
</div>
24+
25+
</>
3326
);
3427
}

frontend/src/components/JobsTableCard.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function JobsTableCard({
3535
onPageSizeChange,
3636
}: JobsTableCardProps) {
3737
return (
38-
<Card className="border-border/70 bg-card/90 backdrop-blur dark:bg-card/95">
38+
<Card className="border-border/70 bg-card/90 backdrop-blur dark:bg-card/95 mb-4">
3939
<CardHeader>
4040
<CardTitle className="text-lg">Vagas Encontradas</CardTitle>
4141
<CardDescription>
@@ -44,7 +44,7 @@ export function JobsTableCard({
4444
</CardHeader>
4545
<CardContent>
4646
{error ? (
47-
<div className="rounded-md border border-red-500/40 bg-red-500/10 p-4 text-sm text-red-700 dark:text-red-300">
47+
<div className="rounded-md border border-red-500/40 bg-red-500/10 p-4 text-sm text-red-700 dark:text-red-300 mb-4">
4848
{error}
4949
</div>
5050
) : null}
@@ -91,36 +91,36 @@ export function JobsTableCard({
9191
<Table>
9292
<TableHeader>
9393
<TableRow>
94-
<TableHead>Palavra-chave</TableHead>
9594
<TableHead>Titulo</TableHead>
9695
<TableHead>Empresa</TableHead>
97-
<TableHead>Fonte</TableHead>
9896
<TableHead>Local</TableHead>
9997
<TableHead>Link</TableHead>
98+
<TableHead>Palavra-chave</TableHead>
99+
<TableHead>Fonte</TableHead>
100100
</TableRow>
101101
</TableHeader>
102102
<TableBody>
103103
{paginatedJobs.map((job, index) => (
104104
<TableRow key={`${job.link || `${job.titulo || "vaga"}-${index}`}-${index}`}>
105-
<TableCell>{job.palavra || "-"}</TableCell>
106105
<TableCell className="font-medium">{job.titulo || "-"}</TableCell>
107106
<TableCell>{job.empresa || "-"}</TableCell>
108-
<TableCell>{job.source || "-"}</TableCell>
109107
<TableCell>{job.local || "-"}</TableCell>
110108
<TableCell>
111109
{job.link ? (
112110
<a
113111
href={job.link}
114112
target="_blank"
115113
rel="noreferrer"
116-
className="text-primary underline-offset-4 hover:underline"
114+
className="text-primary underline-offset-4 hover:underline dark:text-[#14AE5C]"
117115
>
118116
Abrir vaga
119117
</a>
120118
) : (
121119
"-"
122120
)}
123121
</TableCell>
122+
<TableCell>{job.palavra || "-"}</TableCell>
123+
<TableCell>{job.source || "-"}</TableCell>
124124
</TableRow>
125125
))}
126126

frontend/src/components/ui/button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const buttonVariants = cva(
77
{
88
variants: {
99
variant: {
10-
default: "bg-primary text-primary-foreground hover:opacity-90",
10+
default: "bg-primary text-primary-foreground hover:opacity-75",
1111
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
1212
},
1313
size: {

frontend/src/components/ui/theme-toggle.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function ThemeToggle({ theme, onToggle }: ThemeToggleProps) {
1818
onClick={onToggle}
1919
aria-label={isDark ? "Ativar tema claro" : "Ativar tema escuro"}
2020
title={isDark ? "Ativar tema claro" : "Ativar tema escuro"}
21-
className="gap-2"
21+
className="gap-2 hover:bg-accent/40 transition-all duration-300 rounded-lg"
2222
>
2323
{isDark ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
2424
<span className="hidden sm:inline">{isDark ? "Tema claro" : "Tema escuro"}</span>

frontend/src/index.css

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,24 @@
44
@tailwind components;
55
@tailwind utilities;
66

7+
78
@layer base {
89
:root {
9-
--background: 33 53% 96%;
10+
--background: 33 53% 100%;
1011
--foreground: 210 30% 15%;
1112
--card: 0 0% 100%;
1213
--card-foreground: 210 30% 15%;
13-
--primary: 198 63% 36%;
14+
--primary: 140 70% 20%;
1415
--primary-foreground: 0 0% 100%;
1516
--secondary: 38 52% 86%;
1617
--secondary-foreground: 210 30% 15%;
1718
--muted: 36 35% 91%;
1819
--muted-foreground: 210 15% 40%;
19-
--accent: 24 67% 81%;
20-
--accent-foreground: 210 35% 17%;
21-
--border: 30 28% 82%;
20+
--accent: 140 75% 38%;
21+
--accent-foreground: 0 0% 100%;
22+
--border: 0 0% 82%;
2223
--input: 30 28% 82%;
23-
--ring: 198 63% 36%;
24+
--ring: 140 75% 38%;
2425
--radius: 0.8rem;
2526
color-scheme: light;
2627
}
@@ -30,17 +31,17 @@
3031
--foreground: 210 30% 95%;
3132
--card: 216 26% 14%;
3233
--card-foreground: 210 30% 95%;
33-
--primary: 198 78% 63%;
34-
--primary-foreground: 218 35% 12%;
34+
--primary: 140 70% 20%;
35+
/* --primary-foreground: 218 35% 12%; */
3536
--secondary: 216 20% 23%;
3637
--secondary-foreground: 210 20% 94%;
3738
--muted: 216 20% 19%;
3839
--muted-foreground: 215 20% 72%;
39-
--accent: 25 75% 58%;
40+
--accent: 140 50% 45%;
4041
--accent-foreground: 0 0% 100%;
4142
--border: 216 16% 30%;
4243
--input: 216 16% 30%;
43-
--ring: 198 78% 63%;
44+
--ring: 140 75% 38%;
4445
color-scheme: dark;
4546
}
4647

@@ -49,6 +50,6 @@
4950
}
5051

5152
body {
52-
@apply bg-background font-sans text-foreground antialiased;
53+
@apply m-0 p-0 bg-background font-sans text-foreground antialiased;
5354
}
5455
}

0 commit comments

Comments
 (0)