copied the code from the working repo

This commit is contained in:
2024-11-30 16:00:48 +03:00
parent f22b92869b
commit 15ac0cb9b8
148 changed files with 23342 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
'use client';
import { PropsWithChildren } from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
const ClientProvider = ({ children }: PropsWithChildren) => {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
export default ClientProvider;

View File

@@ -0,0 +1,13 @@
'use client';
import type { PropsWithChildren } from 'react';
import { SDKProvider } from '@tma.js/sdk-react';
export function TmaSDKLoader({ children }: PropsWithChildren) {
return (
<SDKProvider acceptCustomStyles debug>
{children}
</SDKProvider>
);
}

View File

@@ -0,0 +1,109 @@
export const skillsOptions = [
'C',
'C++',
'C#',
'Java',
'Python',
'Ruby',
'Ruby on Rails',
'R',
'Matlab',
'Django',
'NetBeans',
'Scala',
'JavaScript',
'TypeScript',
'Go',
'Software Development',
'Application Development',
'Web Applications',
'Object Oriented Programming',
'Aspect Oriented Programming',
'Concurrent Programming',
'Mobile Development (iOS)',
'Mobile Development (Android)',
'Data Science',
'Data Analytics',
'Data Mining',
'Data Visualization',
'Machine Learning',
'TensorFlow',
'PyTorch',
'Keras',
'Theano',
'Statistical Analysis',
'Bayesian Analysis',
'Regression Analysis',
'Time Series Analysis',
'Clustering',
'K-means',
'KNN',
'Decision Trees',
'Random Forest',
'Dimensionality Reduction',
'PCA',
'SVD',
'Gradient Descent',
'Stochastic Gradient Descent',
'Outlier Detection',
'Frequent Itemset Mining',
'SQL',
'NoSQL',
'SQL Server',
'MS SQL Server',
'Apache Hadoop',
'Apache Spark',
'Apache Airflow',
'Apache Impala',
'Apache Drill',
'HTML',
'CSS',
'React',
'Angular',
'Vue.js',
'Node.js',
'Express.js',
'REST',
'SOAP',
'Web Platforms',
'System Architecture',
'Distributed Computing',
'AWS',
'AWS Glue',
'Azure',
'Google Cloud Platform',
'Docker',
'Kubernetes',
'UNIX',
'Linux',
'Windows',
'MacOS',
'Embedded Hardware',
'Debugging',
'Unit Testing',
'Integration Testing',
'System Testing',
'Code Review',
'Git',
'SVN',
'CI/CD',
'Software Documentation',
'IDE',
'CASE Tools',
'Computational Complexity',
'Algorithm Design',
'Data Structures',
'Mathematical Modeling',
'Statistics',
'Technical Writing',
'Technical Support',
'System Design',
'System Development',
'Technical Guidance',
'Client Interface',
'Vendor Interface',
'Emerging Technologies',
'Jira',
'Trello',
'Software Architecture',
];

View File

@@ -0,0 +1,15 @@
'use client'
import React from 'react';
import { LoadingOutlined } from '@ant-design/icons';
import { Space, Spin } from 'antd';
const LoadingPage: React.FC = () => {
return (
// <Space>
// <Spin indicator={<LoadingOutlined style={{ fontSize: 48, position:'absolute', left:'50%', top: '50%', transform:'translate(-50%, -50%)' }} spin />} />
// </Space>
<h3 style={{position: 'absolute', left:'50%', top:'50%', transform: 'translate(-50%, -50%)', color: 'white'}}>Загрузака...</h3>
);
};
export default LoadingPage;

View File

@@ -0,0 +1,17 @@
'use client';
import Resume from "../widgets/Resume/Resume";
interface MainPageProps {
id: number;
}
const MainPage: React.FC<MainPageProps> = ({ id }) => {
return (
<div >
<Resume/>
</div>
);
};
export default MainPage;

View File

@@ -0,0 +1,13 @@
'use client'
import SearchWidget from '../widgets/Search/SearchWidget';
const SearchPage: React.FC = () => {
return (
<>
<SearchWidget />
</>
);
};
export default SearchPage;

View File

@@ -0,0 +1,39 @@
.form {
font-size: 28px;
color: white;
}
// .inputs{
// background-color: var(--secondary_bg_color),
// }
.checkbox_button {
color:white;
}
// .checkbox_button:hover {
// border-color: #40a9ff;
// }
// .checkbox_button input {
// display: none;
// }
// .checkbox_button-checked {
// background-color: #40a9ff;
// color: white;
// border-color: #40a9ff;
// }
// .checkbox_button-checked:hover {
// background-color: #69c0ff;
// border-color: #69c0ff;
// }
// .checkbox_button-checked .ant-checkbox-inner {
// background-color: transparent;
// }
// .checkbox_button input:checked + .checkbox_button {
// background-color: #40a9ff;
// color: white;
// border-color: #40a9ff;
// }

View File

@@ -0,0 +1,403 @@
import React, { useEffect, useState } from 'react';
import { Button, Checkbox, Form, Input, Radio, Select } from 'antd';
import r from './Resume.module.scss';
import { initThemeParams, MiniApp, useInitData, useMiniApp, usePopup } from '@tma.js/sdk-react';
import {
deleteStudent,
editStudent,
fetchHardSkills,
fetchHardSkillsAll,
fetchStudent,
sendStudent,
} from '@/api/api';
import { Bot, FormValues, Request, Student } from '@/types/types';
import { useQuery } from 'react-query';
import { sendDataBot } from '@/api/bot';
type LayoutType = Parameters<typeof Form>[0]['layout'];
const { Option } = Select;
const Resume: React.FC = () => {
const [form] = Form.useForm();
const [formLayout, setFormLayout] = useState<LayoutType>('horizontal');
const miniApp = useMiniApp();
const [themeParams] = initThemeParams();
const initData = useInitData(true);
const popup = usePopup();
const { isLoading, isError, data, error } = useQuery(
['student', initData?.user?.id],
() => fetchStudent(initData?.user?.id || 0),
{
enabled: !!initData?.user?.id, // Включить запрос только когда initData.user.id доступен
refetchOnWindowFocus: false,
retry: false,
}
);
const hardSkill = useQuery(
['skills', initData?.user?.id],
() => fetchHardSkills(initData?.user?.id || 0),
{
enabled: !!initData?.user?.id, // Включить запрос только когда initData.user.id доступен
refetchOnWindowFocus: false,
retry: false,
}
);
const { data: allSkills } = useQuery(
['hardSkills'], // Обратите внимание на использование массива в качестве ключа
() => fetchHardSkillsAll(),
{
refetchOnWindowFocus: false,
retry: false,
}
);
const onFinish = async (values: FormValues) => {
const hard: string[] = values.skills;
const student: Request = {
StudentID: initData?.user?.id || 0,
Name: values.Name,
Type: values.Type,
Group: values.Group,
Faculties: values.Faculties,
Phone_number: values.Phone_number,
Time: values.Time, // список времени
Soft_skills: values.Soft_skills,
Link: 'link', // предполагается, что у вас есть ссылка
Email: values.Email,
Hardskills: values.skills, // список навыков
};
const editedStudent: Omit<Student, 'StudentID'> = {
Name: values.Name,
Type: values.Type,
Group: values.Group,
Time: values.Time,
Faculties: values.Faculties,
Phone_number: values.Phone_number,
Soft_skills: values.Soft_skills,
Link: 'link',
Email: values.Email,
Hardskills: values.skills,
};
const dataBot: Bot = {
id: initData?.user?.id || 0,
};
if (data) {
const result = await editStudent(editedStudent, initData?.user?.id || 0);
if (result.status == 200) {
popup.open({
title: '',
message: 'Изменния были внесены',
buttons: [{ id: 'my-id', type: 'default', text: 'Закрыть' }],
});
}
console.log(result.status);
} else {
const result = await sendStudent(student);
console.log(result.status);
const resultbot = await sendDataBot(dataBot);
console.log(resultbot);
miniApp.close();
}
};
useEffect(() => {
if (data) {
form.setFieldsValue({
Name: data.data.Name,
Type: data.data.Type,
Group: data.data.Group,
Time: data.data.Time,
Soft_skills: data.data.Soft_skills,
Email: data.data.Email,
skills: hardSkill.data?.data.map(
(skill: { Title: string }) => skill.Title
),
});
}
}, [data, form]);
const resetResume = async () => {
const result = await deleteStudent(initData?.user?.id || 0);
if (result.status == 204) {
popup.open({
title: '',
message: 'Ваше резюме было удалено',
buttons: [{ id: 'my-id', type: 'default', text: 'Закрыть' }],
});
}
await form.resetFields();
miniApp.close();
};
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const handleCheckboxChange = (checkedValues: string[]) => {
setSelectedValues(checkedValues);
};
const onFormLayoutChange = ({ layout }: { layout: LayoutType }) => {
setFormLayout(layout);
};
// if (isLoading) {
// return <span>Загрузка...</span>;
// }
return (
<Form
layout={formLayout}
form={form}
initialValues={{ layout: formLayout }}
onValuesChange={onFormLayoutChange}
onFinish={onFinish}
style={{ maxWidth: formLayout === 'inline' ? 'none' : 600 }}
className={r.form}
>
<Form.Item
label={
<label style={{ color: themeParams.textColor, fontSize: '20px' }}>
Как вас зовут?
</label>
}
className={r.form_item}
rules={[
{ required: true, message: 'Пожалуйста, введите имя и фамилию!' },
]}
name='Name'
>
<Input placeholder='Фамилия Имя' />
</Form.Item>
<Form.Item
label={
<label style={{ color: themeParams.textColor, fontSize: '20px' }}>
Факультет
</label>
}
className={r.form_item}
rules={[{ required: true, message: 'Пожалуйста, введите факультет!' }]}
name='Faculties'
>
<Input placeholder='Фамилия Имя' />
</Form.Item>
<Form.Item
label={
<label style={{ color: themeParams.textColor, fontSize: '20px' }}>
Номер телефона
</label>
}
className={r.form_item}
rules={[
{ required: true, message: 'Пожалуйста, введите номер телефона!' },
]}
name='Phone_number'
>
<Input placeholder='Фамилия Имя' />
</Form.Item>
<Form.Item
label={
<label style={{ color: themeParams.textColor, fontSize: '20px' }}>
Что вы ищите?
</label>
}
rules={[
{ required: true, message: 'Пожалуйста, выберит тип занятости!' },
]}
name='Type'
>
<Radio.Group>
<Radio.Button value='Работа'>Работу</Radio.Button>
<Radio.Button value='Стажировка'>Стажировку</Radio.Button>
</Radio.Group>
</Form.Item>
{/* <Form.Item
label={
<label
style={{
color: themeParams.textColor,
fontSize: '20px',
marginBottom: '20px',
}}
>
Что вы ищите?
</label>
}
rules={[
{ required: true, message: 'Пожалуйста, выберите тип занятости!' },
]}
name='Type'
>
<Select
mode='multiple'
allowClear
style={{ width: '100%' }}
placeholder='Выберите тип занятости'
>
<Option value='ищу работу'>Ищу работу</Option>
<Option value='ищу стажировку у партнеров'>
Ищу стажировку у партнеров
</Option>
<Option value='ищу стажировку сторонних компаний'>
Ищу стажировку сторонних компаний
</Option>
<Option value='хочу заниматься научной работой'>
Хочу заниматься научной работой
</Option>
<Option value='хочу работать в МТУСИ'>Хочу работать в МТУСИ</Option>
</Select>
</Form.Item> */}
<Form.Item
label={
<label style={{ color: themeParams.textColor, fontSize: '20px' }}>
Ваша академическая группа:
</label>
}
name='Group'
rules={[
{
required: true,
message: 'Пожалуйста, введите номер вашей группы!',
},
{
pattern: /^[А-ЯЁ]{2,3}\d{4}$/,
message: 'Введите корректный номер группы (например, БВТ2202)!',
},
]}
>
<Input placeholder=ВТ2202' style={{ width: '85px' }} maxLength={7} />
</Form.Item>
<Form.Item
label={
<label
style={{
color: themeParams.textColor,
fontSize: '20px',
marginBottom: '20px',
}}
>
Какую занятость (часов в неделю) вы рассматриваете?
</label>
}
name='Time'
>
<Checkbox.Group onChange={handleCheckboxChange} value={selectedValues}>
<Checkbox
value='20'
className={r.checkbox_button}
style={{
color: themeParams.textColor,
}}
>
20
</Checkbox>
<Checkbox
value='30'
className={r.checkbox_button}
style={{
color: themeParams.textColor,
}}
>
30
</Checkbox>
<Checkbox
value='40'
className={r.checkbox_button}
style={{
color: themeParams.textColor,
}}
>
40
</Checkbox>
</Checkbox.Group>
</Form.Item>
<Form.Item
label={
<label
style={{
color: themeParams.textColor,
fontSize: '20px',
marginBottom: '20px',
}}
>
Какими навыками вы обладаете?
</label>
}
rules={[
{ required: true, message: 'Пожалуйста, укажите ваши hard skills!' },
]}
name='skills'
>
<Select
mode='multiple'
allowClear
style={{ width: '100%' }}
placeholder='Выберите навыки'
loading={isLoading}
>
{allSkills?.map(skill => (
<Option key={skill.Hard_skillID} value={skill.Title}>
{skill.Title}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
label={
<label
style={{
color: themeParams.textColor,
fontSize: '20px',
marginBottom: '20px',
}}
>
Расскажите немного о своих soft skills:
</label>
}
rules={[
{ required: true, message: 'Пожалуйста, введите ваши soft skills!' },
]}
name='Soft_skills'
>
<Input.TextArea maxLength={150} />
</Form.Item>
<Form.Item
label={
<label style={{ color: themeParams.textColor, fontSize: '20px' }}>
Оставьте почту для работодателей:
</label>
}
className={r.form_item}
rules={[
{ required: true, message: 'Пожалуйста, введите вашу почту!' },
{ type: 'email', message: 'Введите корректный email!' },
]}
name='Email'
>
<Input placeholder='example@mtuci.ru' className={r.inputs} />
</Form.Item>
<Form.Item>
<Button type='primary' htmlType='submit'>
Отправить
</Button>
</Form.Item>
{data && (
<Form.Item>
<Button danger onClick={resetResume}>
Стереть
</Button>
</Form.Item>
)}
</Form>
);
};
export default Resume;

View File

@@ -0,0 +1,38 @@
.container {
padding: 20px;
}
.form {
margin-bottom: 20px;
color: white;
}
.error {
color: red;
}
.item{
color:white;
}
.spin{
position:absolute;
left:50%;
top:50%;
transform: translate(-50%, -50%);
}
.cardWrapper {
display: flex;
flex-direction: column;
gap: 20px; /* Расстояние между карточками */
}
.card {
border: 1px solid #d9d9d9; /* Цвет и стиль обводки */
border-radius: 4px; /* Закругление углов */
padding: 16px; /* Отступы внутри карточки */
transition: box-shadow 0.3s; /* Плавный переход для теней */
&:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Тень при наведении */
}
}

View File

@@ -0,0 +1,175 @@
'use client'
import React, { useState, useEffect } from 'react';
import { useQuery } from 'react-query';
import { searchJobs } from '@/api/api';
import { Input, Button, Spin, List, Select, Checkbox, InputNumber, Card } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import style from './SearchWidget.module.scss';
import { JobData } from '@/types/types';
import { skillsOptions } from '@/fsd/entities/Resume/data';
import { initThemeParams } from '@tma.js/sdk-react';
const { Option } = Select;
const SearchWidget: React.FC = () => {
const [query, setQuery] = useState('');
const [year, setYear] = useState<number | undefined>();
const [qualification, setQualification] = useState<boolean | undefined>();
const [time, setTime] = useState<string[]>([]);
const [salary, setSalary] = useState<number | undefined>();
const [hardskills, setHardskills] = useState<string[]>([]);
const [searchResults, setSearchResults] = useState<JobData[]>([]);
const [themeParams] = initThemeParams();
// Функция для формирования объекта параметров
const buildQueryParams = () => {
const params: any = {}; // Или укажите более точный тип, соответствующий JobsSearch
if (year !== undefined) {
params.year = year;
}
if (qualification !== undefined) {
params.qualification = qualification;
}
if (time.length > 0) {
params.time = time; // здесь может потребоваться изменить на массив строк
}
if (salary !== undefined) {
params.salary = salary;
}
if (hardskills.length > 0) {
params.hardskills = hardskills;
}
if (query) {
params.search = query;
}
return params; // Возвращаем объект вместо строки
};
// Вызов useQuery с объектом
const { data, error, isLoading, refetch } = useQuery<JobData[]>(
['searchJobs', buildQueryParams()],
() => {
const queryParams = buildQueryParams(); // Получаем объект параметров
return searchJobs(queryParams); // Передаём объект в функцию
},
{ enabled: false, refetchOnWindowFocus: false, retry: false }
);
// Используем useEffect для обновления searchResults, когда запрос завершен
useEffect(() => {
if (data) {
setSearchResults(data);
}
}, [data]);
const handleSearch = () => {
refetch();
};
if (isLoading) return <Spin size='large' className={style.spin} />;
if (error)
return <div className={style.error}>Ошибка при поиске вакансий</div>;
return (
<div className={style.container}>
<div className={style.form}>
<Input
placeholder='Поиск по названию вакансии или компании...'
value={query}
onChange={e => setQuery(e.target.value)}
prefix={<SearchOutlined />}
style={{ marginBottom: 20 }}
/>
<Select
placeholder='Выберите Курс'
style={{ width: '100%', marginBottom: 20 }}
onChange={value => setYear(value)}
value={year}
>
<Option value={1}>1</Option>
<Option value={2}>2</Option>
<Option value={3}>3</Option>
<Option value={4}>4</Option>
{/* Добавьте другие годы по необходимости */}
</Select>
<Checkbox
checked={qualification}
onChange={e => setQualification(e.target.checked)}
style={{ marginBottom: 20, color: themeParams.textColor }}
>
Требуется квалификация
</Checkbox>
<Select
mode='multiple'
placeholder='Выберите занятость'
style={{ width: '100%', marginBottom: 20 }}
onChange={value => setTime(value)}
value={time}
>
<Option value='20'>20</Option>
<Option value='30'>30</Option>
<Option value='40'>40</Option>
</Select>
<InputNumber
placeholder='Минимальная зарплата'
value={salary}
onChange={value => setSalary(value !== null ? value : undefined)}
style={{ width: '100%', marginBottom: 20 }}
/>
<Select
mode='multiple'
placeholder='Выберите hard skills'
style={{ width: '100%', marginBottom: 20 }}
onChange={value => setHardskills(value)}
value={hardskills}
options={skillsOptions.map(skill => ({ value: skill, label: skill }))}
/>
<Button type='primary' onClick={handleSearch} style={{ width: '100%' }}>
Поиск
</Button>
</div>
<div style={{ marginTop: 20 }}>
{isLoading && <Spin size='large' />}
{searchResults.length > 0 ? (
<div className={style.cardWrapper}>
{searchResults.map((item: JobData) => (
<Card
key={item.Email} // Предположим, что Email уникален для вакансий
title={item.Job_name}
bordered={false}
style={{ marginBottom: 20 }}
className={style.card}
>
<p>Год: {item.Year}</p>
<p>Квалификация: {item.Qualification ? 'Да' : 'Нет'}</p>
<p>Зарплата: {item.Salary}</p>
<p>Soft Skills: {item.Soft_skills}</p>
<p>Обязанности: {item.Responsibilities}</p>
<p>
Email: <a href={`mailto:${item.Email}`}>{item.Email}</a>
</p>
</Card>
))}
</div>
) : (
!isLoading && (
<div style={{ color: themeParams.textColor }}>
Вакансии не найдены
</div>
)
)}
</div>
</div>
);
};
export default SearchWidget;