diff --git a/app/frontend/src/components/pages/VacanciesPage.tsx b/app/frontend/src/components/pages/VacanciesPage.tsx
new file mode 100644
index 00000000..98f691a0
--- /dev/null
+++ b/app/frontend/src/components/pages/VacanciesPage.tsx
@@ -0,0 +1,83 @@
+import { router } from '@inertiajs/react';
+import { Box, Button, CloseButton, Container, Group, Pagination, Text, TextInput, Title } from '@mantine/core';
+import { useForm } from '@mantine/form';
+import { Search } from 'lucide-react';
+import type { VacancyCardProps } from '../../types';
+import { VacancyCard } from '../shared/VacancyCard';
+
+type PaginationMeta = {
+ total_pages: number;
+ current_page: number;
+};
+
+type VacancyPageProps = {
+ vacancies: VacancyCardProps[];
+ pagination: PaginationMeta;
+};
+
+function VacanciesPage({ vacancies, pagination }: VacancyPageProps) {
+ const form = useForm({
+ mode: "uncontrolled",
+ initialValues: {
+ search: "",
+ termsOfService: false,
+ },
+ onValuesChange: (value) => {
+ if (!value.search) {
+ handleSearch()
+ }
+ }
+ });
+
+ const handlePageChange = (pageNumber: number) => {
+ router.get('', { search: form.getValues().search, page: pageNumber }, { preserveState: true, replace: true });
+ }
+
+ function handleSearch() {
+ router.get('', { search: form.getValues().search, page: 1 }, {
+ preserveState: true,
+ replace: true,
+ });
+ }
+
+ if (!vacancies) return "Loading..."
+
+ return (
+
+ Поиск вакансий
+
+ Найдите работу мечты среди тысяч IT-вакансий
+
+
+
+
+ {vacancies.map((vacancy) => (
+
+ ))}
+
+
+
+
+ );
+}
+
+export default VacanciesPage;
\ No newline at end of file
diff --git a/app/frontend/src/components/shared/VacancyCard/ui/VacancyCard.tsx b/app/frontend/src/components/shared/VacancyCard/ui/VacancyCard.tsx
index 5251db9d..69041645 100644
--- a/app/frontend/src/components/shared/VacancyCard/ui/VacancyCard.tsx
+++ b/app/frontend/src/components/shared/VacancyCard/ui/VacancyCard.tsx
@@ -1,9 +1,8 @@
-import React from "react";
-import type { VacancyCardProps } from "../../../../types";
-import { Card, Group, Text, Badge, Button, Stack, Box } from '@mantine/core';
-import { Building2, MapPin, ChevronDown, ChevronUp, Send } from "lucide-react";
-import { useState } from "react";
import { router } from "@inertiajs/core";
+import { Badge, Box, Button, Card, Group, Stack, Text } from '@mantine/core';
+import { Building2, ChevronDown, ChevronUp, MapPin, Send } from "lucide-react";
+import React, { useState } from "react";
+import type { VacancyCardProps } from "../../../../types";
interface VacancyCardPropsWrapper {
props: VacancyCardProps;
@@ -15,30 +14,33 @@ export const VacancyCard: React.FC = ({ props }) => {
const [skillsExpanded, setSkillsExpanded] = useState(false);
- const skillsCutDesktop = skills.slice(0,12);
- const remainingSkillsCount = skills.length - 3;
- const hasMoreSkills = skills.length > 3;
+ const skills_array: string[] = skills ? skills.split(',') : []
+
+ const skillsCutDesktop = skills_array ? skills_array.slice(0, 12) : [];
+
+ const remainingSkillsCount = skills_array ? skills_array.length - 3 : 0;
+ const hasMoreSkills = skills_array ? skills_array.length > 3 : false;
- const displayedSkills = skillsExpanded ? skills : skills.slice(0, 3);
+ const displayedSkills = skillsExpanded ? (skills_array || []) : (skills_array ? skills_array.slice(0, 3) : []);
const handleCardLink = (e: React.MouseEvent) => {
e.preventDefault();
- router.get(`/vacancies/${id}`)
+ router.get(`/vacancies/${id}`);
};
const handleButtonLink = (e: React.MouseEvent) => {
e.stopPropagation();
- window.open(url, '_blank')
- }
+ window.open(url, '_blank');
+ };
return (
-
@@ -52,17 +54,17 @@ export const VacancyCard: React.FC = ({ props }) => {
{/* Информация о компании */}
- {company ?
+ {company ?
- {company.name}
+ {company}
:
Название компании не указано
}
- {city ?
+ {city ?
- {city.name}
+ {city}
:
Город не указан
}
@@ -71,10 +73,10 @@ export const VacancyCard: React.FC = ({ props }) => {
{/* Навыки */}
- {skills && skills.length > 0 ? (
+ {skills_array && skills_array.length > 0 ? (
skillsCutDesktop.map((skill) => (
- = ({ props }) => {
{/* Правая часть */}
-
+
{salary ?
{salary} :
Зарплата не указана
}
-
);
-};
-
+};
\ No newline at end of file
diff --git a/app/frontend/src/hooks/useDebounce.ts b/app/frontend/src/hooks/useDebounce.ts
new file mode 100644
index 00000000..8babb5bb
--- /dev/null
+++ b/app/frontend/src/hooks/useDebounce.ts
@@ -0,0 +1,17 @@
+import { useEffect, useState } from "react";
+
+export function useDebounce(value: T, delay: number) {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [value, delay]);
+
+ return debouncedValue;
+}
\ No newline at end of file
diff --git a/app/frontend/src/types/index.ts b/app/frontend/src/types/index.ts
index 87a14921..f4e267d2 100644
--- a/app/frontend/src/types/index.ts
+++ b/app/frontend/src/types/index.ts
@@ -6,19 +6,14 @@ export interface User {
}
export interface VacancyCardProps {
- id: number;
+ id: string;
title: string;
url?: string;
salary: string;
experience?: string;
employment?: string;
- company?: {
- id: number;
- name: string;
- };
- city?: {
- id: number;
- name: string;
- };
- skills: string[];
+ company?: string;
+ city?: string;
+ skills?: string;
+ address?: string;
}