dodanie nowych komponentow oraz dodanie walidacji propTypes
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Jakub Kaniecki 2024-07-08 20:52:43 +02:00
parent 10103fb1a1
commit 256bd8a0ee
31 changed files with 491 additions and 214 deletions

View File

@ -7,7 +7,7 @@
<link rel="icon" href="../icon/favicon.ico"/>
<!-- todo ICONs -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>izaac frontend</title>
<title>Izaac - praca dla inżynierów</title>
</head>
<body>
<div id="root"></div>

View File

@ -7,38 +7,29 @@ import { Route, Routes } from "react-router-dom";
import Contact from "./components/Contact";
import Mininav from "./components/Mininav";
import AddJobListing from "./components/AddJobListing";
import { useState, useEffect } from "react";
import axios from "axios";
import EmployerPanel from "./components/EmployerPanel";
function App() {
const loc = () => {
if (location.pathname.startsWith("work/jobposting")) {
return "";
} else {
return "overflow-hidden";
}
};
return (
<Router>
<div className={`bg-white w-full ${loc}`}>
<div className={`${styles.paddingX} ${styles.flexCenter} border-b-2`}>
<Mininav />
<div className={`${styles.boxWidth}`}>
<div className={`bg-white w-full`}>
<div className={`${styles.paddingX} ${styles.flexCenter} border-b-2 relative`}>
{/* <Mininav /> */}
<div className={`${styles.boxWidth} mx-auto`}>
<NavBar />
</div>
</div>
<div className={`${styles.flexStart}`}>
<div className={`${styles.flexStart} mt-18 sm:mt-0`}>
<div className={`${styles.boxWidth2} `}>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/work" element={<WorkApp />} />
<Route path="/work/postings" element={<WorkApp />} />
<Route path="/work/joboffers" element={<WorkApp />} />
<Route path="/contact" element={<Contact />} />
<Route path="/work/jobposting" element={<AddJobListing />} />
<Route path="/employerpanel" element={<EmployerPanel />} />
<Route path="/work/addjoboffer" element={<AddJobListing />} />
<Route path="/work/joboffers/:id" element={<WorkApp/>} />
{/* <Route path="/employerpanel" element={<EmployerPanel />} /> */}
{/* Add more routes as needed */}
</Routes>
</div>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'
import { useState, useEffect } from 'react'
import StepOneJoblisting from './StepOneJoblisting';
import StepTwoJoblisting from './StepTwoJoblisting';
import StepThreeJoblisting from './StepThreeJoblisting';

View File

@ -1,4 +1,4 @@
import React from 'react'
import propTypes from 'prop-types'
const Category = (props) => {
return (
@ -15,4 +15,11 @@ const Category = (props) => {
)
}
Category.propTypes = {
name: propTypes.string,
func: propTypes.func,
small: propTypes.bool,
last: propTypes.bool
}
export default Category

View File

@ -1,4 +1,4 @@
import React from "react";
import { ogloszenia } from "../consts";
import ListingSmall from "./ListingSmall";
import { useState } from "react";

View File

@ -1,30 +1,128 @@
import React from 'react'
import Search from './Search'
import { useEffect, useState } from 'react';
import { categories, work_from_home, employment_types } from '../consts';
import propTypes from 'prop-types';
import Selector from './Selector';
const Filter = (props) => {
if (!props.isOpen) return null;
return (
<div className={`sticky top-24 z-20 bg-gray-100 flex flex-wrap place-items-center items-center slide-container py-2 ${!props.isOpen ? '' : 'expanded'}`}>
<Search label='Wyszukaj ogłoszenie'
placeholder='Wpisz nazwę stanowiska...' />
<Search label='Lokalizacja'
placeholder='Wpisz lokalizację...' />
<Search label='Min. wynagrodzenie'
placeholder='Wpisz kwotę minimalną...' />
<Search label='Max. wynagrodzenie'
placeholder='Wpisz kwotę maksymalną...' />
useEffect(() => {
console.log(props.searchQuery)
console.log(selectedCategories)
console.log(selectedWorkFromHome)
console.log(selectedEmploymentTypes)
console.log(valueminSalary)
}, [props.searchQuery]);
<button className='bg-gray-600 text-white py-3 px-12 col-span-2
mx-auto rounded-md font-semibold text-xl hover:bg-gray-500 duration-150
mt-3
'
onClick={props.onClicked}
const [selectedCategories, setSelectedCategories] = useState([]);
const [selectedWorkFromHome, setSelectedWorkFromHome] = useState([]);
const [selectedEmploymentTypes, setSelectedEmploymentTypes] = useState([]);
const [valueminSalary, setValueminSalary] = useState('');
const [valuemaxSalary, setValuemaxSalary] = useState('');
const [valueName, setValueName] = useState('');
const [valueLocalization, setValueLocalization] = useState('');
useEffect(() => {
setSelectedCategories([])
setSelectedWorkFromHome([])
setSelectedEmploymentTypes([])
setValueminSalary('')
setValuemaxSalary('')
setValueName('')
setValueLocalization('')
}, [props.clearSearchQuery])
const handle_checked_change = (e) => {
switch (e.target.name) {
case 'categories':
if (e.target.checked) {
setSelectedCategories([...selectedCategories, e.target.value]);
} else {
setSelectedCategories(selectedCategories.filter(category => category !== e.target.value));
}
props.setSearchQuery(prevState =>
({...prevState,
categories: e.target.checked ? [...(prevState.categories || []), e.target.value] : (prevState.categories || []).filter(category => category !== e.target.value)}));
break;
case 'work_from_home':
if (e.target.checked) {
setSelectedWorkFromHome([...selectedWorkFromHome, e.target.value]);
} else {
setSelectedWorkFromHome(selectedCategories.filter(work_from_home => work_from_home !== e.target.value));
}
props.setSearchQuery(prevState =>
({...prevState,
work_from_home: e.target.checked ? [...(prevState.work_from_home || []), e.target.value] : (prevState.work_from_home || []).filter(work_from_home => work_from_home !== e.target.value)}));
break;
case 'employment':
if (e.target.checked) {
setSelectedEmploymentTypes([...selectedEmploymentTypes, e.target.value]);
} else {
setSelectedEmploymentTypes(selectedCategories.filter(employment => employment !== e.target.value));
}
props.setSearchQuery(prevState =>
({...prevState,
employment: e.target.checked ? [...(prevState.employment || []), e.target.value] : (prevState.employment || []).filter(employment => employment !== e.target.value)}));
break;
}
}
return (
// idea - make the page whole page grayed out and only the filter is visible
<div className={`z-10 bg-dimWhite collapsible px-3 sm:px-4 ${!props.isOpen ? '' : 'expanded border-4'}`}>
<div className='grid grid-cols-2 sm:grid-cols-3 mb-2'>
<div className='hidden sm:block'></div>
<p className='mx-auto mt-3 text-center font-poppins font-semibold text-xl text-gray-600'>Filtry</p>
<button className='rounded-xl mt-4 ph-6 w-32 bg-slate-400 hover:bg-slate-600 duration-100 ' onClick={props.clearSearchQuery}>
<span className='p-2 text-sm font-bold text-center font-poppins text-white'>Wyczyść filtry</span>
</button>
</div>
<Search label='Wyszukaj ogłoszenie'
placeholder='Wpisz nazwę stanowiska...'
name='name'
setSearchQuery={props.setSearchQuery}
_type='text'
value={props.searchQuery.name || ''}/>
<Search label='Lokalizacja'
placeholder='Wpisz lokalizację...'
name='localization'
_type='text'
setSearchQuery={props.setSearchQuery}
value={props.searchQuery.localization || ''} />
<Search label='Min. wynagrodzenie'
placeholder='Wpisz kwotę minimalną...'
name='min_salary'
_type='text'
setSearchQuery={props.setSearchQuery}
value={props.searchQuery.min_salary || '' } />
<Search label='Max. wynagrodzenie'
placeholder='Wpisz kwotę maksymalną...'
name='max_salary'
setSearchQuery={props.setSearchQuery}
_type='text'
value={props.searchQuery.max_salary || ''}/>
<Selector value_to_map_from={categories} name='Kategorie' inputname='categories' onChange={handle_checked_change} state={selectedCategories}/>
<Selector value_to_map_from={work_from_home} s name='Praca zdalna' inputname='work_from_home' state={selectedWorkFromHome} onChange={handle_checked_change}/>
<Selector value_to_map_from={employment_types} name='Typ kontraktu' inputname='employment' state={selectedEmploymentTypes} onChange={handle_checked_change} />
<button className='bg-gray-600 text-white py-1 sm:py-3 px-12 col-span-2 mx-auto rounded-md font-semibold text-xl hover:bg-gray-500 duration-150 my-3 sm:my-5 sm:min-h-full'
onClick={props.onClick}
>
Filtruj
</button>
</div>
)
}
};
export default Filter
Filter.propTypes = {
isOpen: propTypes.bool,
searchQuery: propTypes.object,
setSearchQuery: propTypes.func,
categories: propTypes.array,
clearSearchQuery: propTypes.func,
onClick: propTypes.func
};
export default Filter;

View File

@ -1,4 +1,4 @@
import React from 'react'
import styles from '../style'
const Home = () => {
return (

View File

@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from 'react';
import { useState, useRef, useEffect } from 'react';
import { placeholderImage } from '../assets';
import axios from 'axios';
import propTypes from 'prop-types';
const ImageUpload = ({setFormData, data}) => {
const [imageSrc, setImageSrc] = useState(placeholderImage);
@ -83,4 +83,9 @@ const ImageUpload = ({setFormData, data}) => {
);
};
ImageUpload.propTypes = {
setFormData: propTypes.func,
data: propTypes.object
};
export default ImageUpload;

View File

@ -9,7 +9,7 @@ const JobOfferContent = ({ id, skills }) => {
const fetchJobOfferById = async (jobId) => {
try {
const response = await axios.get(`https://izaac.knck.pl/api/jobposting/joboffers/${jobId}`);
const response = await axios.get(`https://izaac.knck.pl/api/jobposting/joboffers/${jobId}/?format=json`);
setJobOffer(response.data);
} catch (error) {
console.error('Failed to fetch job offer:', error);

View File

@ -1,11 +1,12 @@
import React from 'react'
import { IzaacLOGO } from '../assets'
const ListingSmall = ({ id, name, company_name, min_salary, max_salary, require_salary, index, onClick, _class, image }) => {
import { IzaacLOGO } from '../assets'
import propTypes from 'prop-types'
const ListingSmall = ({ id, name, company_name, min_salary, max_salary, require_salary, index, onClick, _class, image, selected, blur }) => {
return (
<div
key={id}
className={`flex flex-row items-center gap-4 drop-shadow cursor-pointer mb-2 sm:mr-4 mr-0 text-xl font-bold border rounded-[10px] p-2 sm: px-4 hover:border-l-8 hover:border-zinc-500 duration-300 ${index % 2 === 0 ? 'bg-gray-200' : 'bg-gray-300'} ${_class}`}
className={`flex flex-row ml-1 w-[98vw] sm:w-full items-center gap-4 drop-shadow cursor-pointer mb-2 sm:mr-4 mr-4 text-xl font-bold border rounded-[10px] p-2 sm:px-4 hover:border-l-8 hover:border-zinc-500 duration-300 blur-transition ${index % 2 === 0 ? 'bg-gray-200' : 'bg-gray-300'} ${_class} ${selected ? 'border-l-8 border-zinc-500 bg-gray-400' : 'border-l-8 border-transparent'} `}
onClick={onClick}
>
<div className='flex-shrink-0 w-[6rem] h-[6rem] bg-white rounded-lg flex items-center justify-center'>
@ -14,7 +15,7 @@ const ListingSmall = ({ id, name, company_name, min_salary, max_salary, require_
</div>
<div className='flex-grow flex flex-col justify-between'>
<p className='text-base font-bold text-left tracking-wide'>{name}</p>
<p className='text-sm text-left text-gray-600'>{company_name}</p>
<p className='text-xs text-left text-gray-600'>{company_name}</p>
{/* Wyświetlenie wynagrodzenia pod nazwą, jeśli jest wymagane */}
{require_salary && (
<p className='text-xs font-semibold text-left tracking-widest text-slate-800 mt-2'>{min_salary} - {max_salary} PLN</p>
@ -24,4 +25,19 @@ const ListingSmall = ({ id, name, company_name, min_salary, max_salary, require_
);
};
export default ListingSmall;
export default ListingSmall;
ListingSmall.propTypes = {
id: propTypes.number,
name: propTypes.string,
company_name: propTypes.string,
min_salary: propTypes.number,
max_salary: propTypes.number,
require_salary: propTypes.bool,
index: propTypes.number,
onClick: propTypes.func,
_class: propTypes.string,
image: propTypes.string,
selected: propTypes.bool,
blur: propTypes.string
}

View File

@ -1,4 +1,4 @@
import React from 'react'
import propTypes from 'prop-types';
import styles from '../style';
const Login = ({isOpen, onClose}) => {
@ -31,4 +31,9 @@ const Login = ({isOpen, onClose}) => {
)
}
Login.propTypes = {
isOpen: propTypes.bool,
onClose: propTypes.func
}
export default Login

View File

@ -1,8 +1,7 @@
import React, { useState } from 'react'
import { useState } from 'react'
import Register from './Register';
import Login from './Login';
import { BrowserRouter as Router } from "react-router-dom";
import { Route, Routes } from "react-router-dom";
const Mininav = () => {
const [isPopupOpen, setPopupOpen] = useState(false)
@ -17,7 +16,7 @@ const Mininav = () => {
<>
<div className='bg-gray-500 h-5 absolute w-full top-0 flex justify-end items-center z-50'>
{/* Link otwierający modal */}
<a href="/employerpanel" className='text-white font-poppins font-semibold text-xs'>Panel pracodawcy</a>
{/* <a href="/employerpanel" className='text-white font-poppins font-semibold text-xs'>Panel pracodawcy</a> */}
<button onClick={openPopup} className='text-white font-poppins font-semibold text-xs mx-12'>
Rejestracja
</button>

View File

@ -1,10 +1,10 @@
import React from 'react'
import { linki, linki_home } from '../consts';
import { close, search, menu, IzaacLOGO} from '../assets';
import { useState } from 'react';
import { useLocation } from 'react-router-dom';
import { Link } from 'react-router-dom';
import Mininav from './Mininav';
const NavBar = () => {
const[toggle, setToggle] = useState(false)
const[toggleSearch, setToggleSearch] = useState(false)
@ -20,7 +20,7 @@ const NavBar = () => {
const currentLinks = getLinks()
return (
<nav className='w-full flex pt-7 justify-between items-center navbar '>
<nav className='fixed top-0 left-0 sm:relative z-30 w-full flex pt-4 justify-between items-center navbar bg-white px-8'>
<img src={IzaacLOGO} alt="izaac" className='md:w-[200px] w-[120px]'/>
@ -76,5 +76,4 @@ const NavBar = () => {
)
}
export default NavBar

View File

@ -1,5 +1,5 @@
import React from 'react'
import styles from '../style';
import propTypes from 'prop-types';
const Register = ({isOpen, onClose}) => {
if (!isOpen) return null;
@ -44,5 +44,8 @@ const Register = ({isOpen, onClose}) => {
</div>
)
}
Register.propTypes = {
isOpen: propTypes.bool,
onClose: propTypes.func
}
export default Register

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'
import styles from '../style'
import { useState, useEffect } from 'react'
import propTypes from 'prop-types'
import {employment_types, work_from_home } from '../consts'
@ -112,5 +112,18 @@ const Salary = ({handleChange, formData, removeFields, setFormData}) => {
</div>
)
}
Salary.propTypes = {
handleChange: propTypes.func,
formData: propTypes.object,
removeFields: propTypes.func,
setFormData: propTypes.func,
min_salary: propTypes.string,
max_salary: propTypes.string,
require_salary: propTypes.bool,
employment_type: propTypes.string,
work_from_home: propTypes.string
}
export default Salary

View File

@ -1,19 +1,61 @@
import React from 'react'
import { useEffect, useState } from 'react'
import propTypes from 'prop-types'
const Search = (props) => {
return (
<div className='grid py-2'>
<label className="mx-2 text-l font- text-center font-poppins"
const [isHovered, setIsHovered] = useState(false)
const check_errors = (value, name) => {
if ((name === 'max_salary' || name === 'min_salary') && !(value === '' || value === undefined)) {
if (isNaN(parseInt(value))) {
return false;
} else {
return true;
}
} else {
return true;
}
};
useEffect(() => {
console.log(props.value)
}, [props.value])
return (
<div className='grid sm:py-1'
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<label className="mx-2 text-l font- text-center font-poppins no-spin-buttons"
htmlFor="search">
{props.label}
{props.label}
<span className='text-sm text-red-700'>
{check_errors(props.value, props.name) ? '' : ' *'}
</span>
</label>
<input type="text"
placeholder={props.placeholder}
className="border-2 border-gray-300
<input
type={props._type}
value={props.value || ''}
placeholder={props.placeholder}
name={props.name}
id={props.name}
onChange={(e) => props.setSearchQuery(prevState => ({...prevState, [props.name]: e.target.value}))}
className={`border-2
bg-white h-8 px-5 pr-16 mx-3 rounded-lg
text-sm focus:outline-none" />
text-sm focus:outline-none ${check_errors(props.value, props.name) ? 'border-gray-300': 'border-red-700' }`}/>
{isHovered && <div className='text-red-700 text-sm relative'>
{check_errors(props.value, props.name) ? '' : 'Wartość musi być liczbą!'}
</div>}
</div>
)
}
Search.propTypes = {
label: propTypes.string,
placeholder: propTypes.string,
name: propTypes.string,
setSearchQuery: propTypes.func,
value: propTypes.string,
_type: propTypes.string
}
export default Search

View File

@ -1,4 +1,4 @@
import React from 'react';
import propTypes from 'prop-types';
const levelMappings = {
'N': 'Nice to have',
@ -53,4 +53,12 @@ const SelectedSkill = ({ skill_name, skillId ,letter, onLevelChange, removeSkill
);
};
SelectedSkill.propTypes = {
skill_name: propTypes.string,
skillId: propTypes.number,
letter: propTypes.string,
onLevelChange: propTypes.func,
removeSkillFromList: propTypes.func,
};
export default SelectedSkill;

View File

@ -0,0 +1,35 @@
import propTypes from 'prop-types'
const Selector = (props) => {
return (
<>
<div className='grid grid-cols-2 sm:gap-[1px]'>
<span className='col-span-2 mx-auto text-l font- text-center font-poppins'>{props.name}</span>
{props.value_to_map_from.map((value_to_map_from) => (
<label key={value_to_map_from.id} className
='flex items-center justify-start text-sm font-poppins font-semibold text-gray-600'>
<input type='checkbox' name={props.inputname} value={value_to_map_from.id}
onChange={props.onChange}
className='mr-2'
checked={props.state.includes(value_to_map_from.id)}
/>
{value_to_map_from.name}
</label>
))}
</div>
</>
)
}
Selector.propTypes = {
value_to_map_from: propTypes.array,
setSearchQuery: propTypes.func,
name: propTypes.string,
inputname: propTypes.string,
onChange: propTypes.func,
state: propTypes.array
}
export default Selector

View File

@ -1,4 +1,4 @@
import React from 'react';
import propTypes from 'prop-types';
import SkillRender from './SkillRender';
@ -15,5 +15,9 @@ const SkillsList = ({skillData, skill_names}) => {
</div>
);
};
SkillsList.propTypes = {
skillData: propTypes.array,
skill_names: propTypes.array,
};
export default SkillsList;

View File

@ -1,5 +1,4 @@
import React from 'react'
import propTypes from 'prop-types';
const levelMappings = {
'N': 'Nice to have',
@ -42,4 +41,10 @@ const SkillRender = ({key, skill, level,}) => {
)
};
SkillRender.propTypes = {
key: propTypes.number,
skill: propTypes.string,
level: propTypes.string,
};
export default SkillRender;

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useState, useEffect } from 'react';
import propTypes from 'prop-types';
import SelectedSkill from './SelectedSkill';
import axios from 'axios';
const SkillsSelector = ({ formData, setFormData, skills }) => {
const [inputValue, setInputValue] = useState('');
@ -101,4 +101,10 @@ const SkillsSelector = ({ formData, setFormData, skills }) => {
);
};
SkillsSelector.propTypes = {
formData: propTypes.object.isRequired,
setFormData: propTypes.func.isRequired,
skills: propTypes.array.isRequired
};
export default SkillsSelector;

View File

@ -1,6 +1,9 @@
import React from "react";
import propTypes from "prop-types";
import styles from "../style";
import TextDivider from "./TextDivider";
const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
return (
<div className={`${styles.flexCenter}`}>
@ -120,4 +123,11 @@ const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
);
};
StepFourJoblisting.propTypes = {
handleChange: propTypes.func,
formData: propTypes.object,
nextStep: propTypes.func,
prevStep: propTypes.func,
};
export default StepFourJoblisting;

View File

@ -1,5 +1,7 @@
import React from "react";
import styles from "../style";
import propTypes from "prop-types";
const StepOneJoblisting = ({ nextStep, handleChange, formData }) => {
// Funkcja do obsługi kliknięcia na div i aktualizacji stanu
const handleDivClick = (value) => () => {
@ -76,4 +78,10 @@ const StepOneJoblisting = ({ nextStep, handleChange, formData }) => {
);
};
StepOneJoblisting.propTypes = {
nextStep: propTypes.func,
handleChange: propTypes.func,
formData: propTypes.object,
};
export default StepOneJoblisting;

View File

@ -1,12 +1,13 @@
import React, { useEffect } from 'react';
import DOMPurify from 'dompurify';
import styles from '../style';
import TextDivider from './TextDivider';
import ListingSmall from './ListingSmall';
import { IzaacLOGO } from '../assets'
import SkillsList from './SkillList';
import PropTypes from 'prop-types';
const StepThreeJoblisting = ({ formData, prevStep, skills, handleSubmit }) => {
// Funkcja do przetwarzania HTML i dodawania klas
const processHTML = (htmlString) => {
@ -103,4 +104,11 @@ const StepThreeJoblisting = ({ formData, prevStep, skills, handleSubmit }) => {
);
}
StepThreeJoblisting.propTypes = {
formData: PropTypes.object,
prevStep: PropTypes.func,
skills: PropTypes.array,
handleSubmit: PropTypes.func
}
export default StepThreeJoblisting;

View File

@ -1,4 +1,7 @@
import React, { useState } from 'react'
import { useState } from 'react'
import propTypes from 'prop-types';
import { categories, experience_levels } from '../consts';
import styles from '../style'
import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
@ -7,8 +10,6 @@ import SkillsSelector from './SkillsSelector';
import Salary from './Salary';
import { placeholderImage } from '../assets';
import { categories, experience_levels } from '../consts';
const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setFormData, removeFields, skills }) => {
const [editorData, setEditorData] = useState('');
const [imageSrc, setImageSrc] = useState(placeholderImage);
@ -230,4 +231,14 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
)
}
StepTwoJoblisting.propTypes = {
nextStep: propTypes.func,
prevStep: propTypes.func,
handleChange: propTypes.func,
formData: propTypes.object,
setFormData: propTypes.func,
removeFields: propTypes.func,
skills: propTypes.array
}
export default StepTwoJoblisting

View File

@ -1,4 +1,4 @@
import React from 'react'
import TextDivider from './TextDivider'
const Success = () => {

View File

@ -1,6 +1,7 @@
import React from 'react'
const TextDivider = ({text }) => {
import propTypes from 'prop-types'
const TextDivider = ({ text }) => {
return (
<div className='flex items-center justify-center mt-4 mb-6'>
<div className='h-0.5 bg-black w-[14rem] opacity-30'></div>
@ -13,4 +14,7 @@ const TextDivider = ({text }) => {
)
}
TextDivider.propTypes = {
text: propTypes.string
}
export default TextDivider

View File

@ -2,13 +2,15 @@ import React from "react";
import { useState, useEffect } from "react";
import ListingSmall from "./ListingSmall";
import SkillRender from "./SkillRender";
import Search from "./Search";
// import SkillRender from "./SkillRender";
// import Search from "./Search";
import Filter from "./Filter";
import Category from "./Category";
// import Category from "./Category";
import JobOfferContent from "./JobOfferContent";
import { categories } from "../consts";
import axios from "axios";
import { Link, useParams } from "react-router-dom";
function renderCircles(level) {
let numberOfFilledCircles;
@ -44,11 +46,17 @@ function renderCircles(level) {
}
const WorkApp = () => {
let { id } = useParams();
const [isDetailsVisible, setIsDetailsVisible] = useState(false);
// Czy szczegóły są widoczne
const [isOpen, setIsOpen] = useState(false);
// Funkcja pobierająca dane z API
// useEffect(() => {
// if (!isOpen) {
// setSearchQuery({ name: "", localization: "", min_salary: "", max_salary: "" });
// }}, [isOpen]);
const [ogloszenia, setOgloszenia] = useState([]);
const [skills, setSkills] = useState([]);
@ -64,12 +72,15 @@ const WorkApp = () => {
// console.log(data);
if (data.length > 0) {
setSelectedOgloszenie(data[0]);
// console.log(data[0])
console.log(data[0])
}
} catch (error) {
console.error(error);
}
};
if (id === undefined) {
id = ogloszenia[0]?.id;
}
const getSkills = async () => {
try {
@ -86,26 +97,27 @@ const WorkApp = () => {
const [searchQuery, setSearchQuery] = useState({});
const clearSearchQuery = () => {
setSearchQuery({});
};
const [selectedOgloszenie, setSelectedOgloszenie] = useState(ogloszenia[0]);
useEffect(() => {
getOgloszenia();
getSkills();
}, []);
useEffect(() => {
document.title = `Izaac - ${selectedOgloszenie?.name}`;
}, [selectedOgloszenie?.name]);
const handleOgloszenieClick = (ogloszenie) => {
setSelectedOgloszenie(ogloszenie);
setIsDetailsVisible(true); // Pokaż szczegóły na urządzeniach mobilnych
};
const prevSlide = () => {
setCurrentSlide(
(prevSlide) => (prevSlide - 1 + categories.length) % categories.length
);
};
const nextSlide = () => {
setCurrentSlide((prevSlide) => (prevSlide + 1) % categories.length);
};
const [currentSlide, setCurrentSlide] = React.useState(0);
// Funkcja do powrotu do listy ogłoszeń
const handleBackToList = () => {
setIsDetailsVisible(false);
@ -125,53 +137,50 @@ const WorkApp = () => {
}, []);
return (
<section className=" mx-auto bg-white w-full">
<div className="flex-grow h-5/6 grid grid-cols-10">
<div className="px-3 flex col-span-11 sm:flex-wrap flex-shrink-0 items-center place-content-center">
<button onClick={prevSlide} className="lg:hidden mx-6 font-semibold place-self-start self-center leading-5">
Poprzednia <br/> kategoria
</button>
{categories.map((category, index) => (
<div
key={index}
className={`lg:block ${
index === currentSlide ? "block" : "hidden"
} lg:flex flex-grow lg:flex-none flex-basis-0"`}
>
<Category
name={category.name}
last={index === categories.length - 1}
small={isSmallViewport}
func={() => { getOgloszenia({ category: category.id }); }}
/>
</div>
))}
<button onClick={nextSlide} className="lg:hidden font-semibold leading-5 place-self-end self-center mr-6">
Następna <br/> kategoria
</button>
</div>
<section className={`bg-white w-full`}>
<div className="h-[88vh] flex flex-row sm:px-12">
<div
className={`static rounded-xl col-span-11 sm:col-span-4 bg-gray-100 overflow-y-auto px-2 my-4 mx-2 sm:h-[84vh] h-[84vh] ${
className={`relative bg-gray-100 w-full sm:w-[40%]
${isOpen ? 'h-[100vh] overflow-y-hidden' : 'sm:overflow-y-auto'}
sm:h-[89vh] ${
isDetailsVisible ? "hidden sm:block" : "block"
}`}
} `}
>
<div className="z-10 sticky top-0 col-span-11 sm:cols-span-4 h-24 bg-slate-300 flex items-center place-content-center mb-2">
<h1 className="text-center text-3xl font-poppins font-semibold text-slate-800 py-4 mx-6">
<div className="z-10 sticky top-[70px] sm:top-0 w-[100vw] sm:w-full">
<div className="z-10 w-full fixed sm:sticky top-[4.4rem] sm:top-0 min-h-fit bg-slate-300 flex items-center place-content-center py-1 sm:py-2">
<h1 className="text-center text-xl sm:text-2xl font-poppins font-semibold text-slate-800 py-2 mx-6">
Oferty pracy
</h1>
<button
className="block font-poppins text-white font-semibold
bg-gray-500 px-4 py-2 mx-6 rounded-md"
bg-gray-500 px-4 py-2 mx-6 rounded-md hover:bg-gray-600 duration-100"
onClick={() => setIsOpen(!isOpen)}
>
Pokaż filtry
{!isOpen ? 'Pokaż filtry' : 'Ukryj filtry'}
</button>
</div>
<div className="z-10 sticky top-24 col-span-11 sm:cols-span-4 bg-slate-300 mb-2">
{isOpen && <Filter isOpen={isOpen} />}
<div className={`z-10 absolute top-[3rem] sm:top-16 w-[100vw] sm:w-full`}>
<Filter isOpen={isOpen}
searchQuery={searchQuery || {}}
setSearchQuery={setSearchQuery}
categories={categories}
clearSearchQuery={clearSearchQuery}
onClick={()=> {getOgloszenia(searchQuery)
setIsOpen(!isOpen)
}}/>
</div>
</div>
<div className={`sm:mt-2 mr-2 mt-[8rem] noblur ${!isOpen ? '' : 'blur'}`}
>
{ogloszenia.map((ogloszenie, index) => (
<Link
key={ogloszenie.id}
to={`/work/joboffers/${ogloszenie.id}`} >
<ListingSmall
key={ogloszenie.id}
name={ogloszenie.name}
@ -181,14 +190,18 @@ const WorkApp = () => {
require_salary={ogloszenie.require_salary}
image={ogloszenie.image}
index={index}
onClick={() => handleOgloszenieClick(ogloszenie)}
onClick={() => {handleOgloszenieClick(ogloszenie)}}
selected={selectedOgloszenie?.id === ogloszenie.id}
blur={isOpen ? 'blur' : ''}
/>
</Link>
))}
</div>
</div>
<div
className={`sm:col-span-6 col-span-11 bg-gray-100 sm:h-[84vh] h-[84vh] overflow-y-auto p-4 my-4 ${
className={`absolute sm:relative w-full sm:w-[60%] bg-gray-100 sm:h-[89vh] h-[100vh] top-16 sm:top-0 -left-4 right-0 overflow-y-auto p-4 mx-4 blur-transition ${
isDetailsVisible ? "block" : "hidden sm:block"
}`}
} noblur ${isOpen ? 'blur' : ''}`}
>
{isDetailsVisible && (
<div className="grid grid-cols-6">
@ -202,19 +215,8 @@ const WorkApp = () => {
)}
{selectedOgloszenie && (
<JobOfferContent id={selectedOgloszenie.id} skills={skills} />
<JobOfferContent id={id} skills={skills} />
)}
{/* <div className='grid sm:grid-cols-2 md:grid-cols-5 grid-cols-2'>
{Object.entries(selectedOgloszenie.neededSkills).map(([skill, level]) => (
<SkillRender
skill={skill}
level={level}
/>
))}
</div>
<div className='text-slate-800 font-medium mt-4'>
{selectedOgloszenie.tresc}
</div> */}
</div>
</div>
</section>

View File

@ -169,12 +169,12 @@ export const linki = [
},
{
"id": "2",
"name": "work/postings",
"name": "work/joboffers",
"title": "Ogłoszenia",
},
{
"id": "3",
"name": "work/jobposting",
"name": "work/addjoboffer",
"title": "Dodaj ogłoszenie",
},
{

View File

@ -22,6 +22,27 @@
}
}
/* @layer components {
.no-spin-buttons::-webkit-inner-spin-button,
.no-spin-buttons::-webkit-outer-spin-button {
@apply -webkit-appearance-none margin-0;
}
.no-spin-buttons {
@apply -moz-appearance-textfield;
}
} */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Dla Firefox */
input[type="number"] {
-moz-appearance: textfield;
}
@tailwind components;
@tailwind utilities;
@ -60,11 +81,43 @@
}
/* Expanded state styles */
.expanded {
max-height: fit-content; /* Adjust as needed */
opacity: 1;
.collapsible {
max-height: 0;
overflow: hidden;
transition-property: all;
transition-timing-function: ease-in-out;
transition-duration: 0.5s;
}
/* Styl dla elementu, gdy jest rozwinięty */
.collapsible.expanded {
max-height: 1000px; /* Przykładowa maksymalna wysokość, może wymagać dostosowania */
opacity: 1;
transition-property: all;
transition-timing-function: ease-in-out;
transition-duration: 0.5s;
}
.div-transition {
transition-property: filter;
transition-duration: 1s;
transition-timing-function: ease-in-out;
}
.noblur {
filter: blur(0px);
transition-property: filter;
transition-duration: 0.5s;
transition-timing-function: ease-in-out;
}
.blur {
filter: blur(5px);
transition-property: filter;
transition-duration: 0.5s;
transition-timing-function: ease-in-out;
}
.minus-z-index {
z-index: -10;
}
@ -109,59 +162,4 @@
.sidebar-hide {
-webkit-animation: slide-down 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
animation: slide-down 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
@-webkit-keyframes slide-top {
0% {
-webkit-transform: translateX(600px);
transform: translateX(600px);
}
100% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
}
@keyframes slide-top {
0% {
-webkit-transform: translateX(600px);
transform: translateX(600px);
}
100% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
}
@-webkit-keyframes slide-down {
0% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
100% {
-webkit-transform: translateX(600px);
transform: translateX(600px);
}
}
@keyframes slide-down {
0% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
100% {
-webkit-transform: translateX(600px);
transform: translateX(600px);
}
}
@media (max-width: 768px) {
.details-section {
display: none;
}
.list-section {
display: block;
}
}
}

View File

@ -7,7 +7,7 @@ module.exports = {
colors: {
primary: "#00040f",
secondary: "#00f6ff",
dimWhite: "rgba(255, 255, 255, 0.7)",
dimWhite: "rgba(255, 255, 255, 0.8)",
dimBlue: "rgba(9, 151, 124, 0.1)",
},
fontFamily: {