zmiany do mobile, filtrowanie, poprawki
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
97b7e8726d
commit
45a2c8252e
@ -21,7 +21,7 @@ function App() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`${styles.flexStart} mt-18 sm:mt-[6.5rem]`}>
|
||||
<div className={`${styles.flexStart} mt-18 sm:mt-[4.5rem] md:mt-[6.5rem]`}>
|
||||
<div className={`${styles.boxWidth2} `}>
|
||||
<Routes>
|
||||
<Route path="/home" element={<Home />} />
|
||||
|
||||
106
src/components/CategorySelect.jsx
Normal file
106
src/components/CategorySelect.jsx
Normal file
@ -0,0 +1,106 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { categories } from '../consts';
|
||||
import { search } from '../assets';
|
||||
|
||||
const CategorySelect = props => {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [suggestions, setSuggestions] = useState([]);
|
||||
const [selectedCategoryIds, setSelectedCategoryIds] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(suggestions);
|
||||
console.log(selectedCategoryIds);
|
||||
}, [suggestions, selectedCategoryIds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.searchQuery && props.searchQuery.categories) {
|
||||
setSelectedCategoryIds(props.searchQuery.categories);
|
||||
}
|
||||
}, [props.searchQuery]);
|
||||
|
||||
// useEffect(() =>{
|
||||
// setSelectedCategoryIds([])
|
||||
// }, [props.clearSearchQuery])
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const value = e.target.value;
|
||||
setInputValue(value);
|
||||
if (value.length > 0) {
|
||||
setSuggestions(categories.filter(category =>
|
||||
category.name.toLowerCase().includes(value.toLowerCase()) &&
|
||||
!selectedCategoryIds.includes(category.id)
|
||||
));
|
||||
} else {
|
||||
setSuggestions([]);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSuggestionClick = (suggestion) => {
|
||||
if (!selectedCategoryIds.includes(suggestion.id)) {
|
||||
const newSelectedIds = [...selectedCategoryIds, suggestion.id];
|
||||
setSelectedCategoryIds(newSelectedIds);
|
||||
props.setSearchQuery(prevState => ({...prevState, categories: newSelectedIds }));
|
||||
}
|
||||
setInputValue('');
|
||||
setSuggestions([]);
|
||||
}
|
||||
|
||||
const handleRemoveCategory = (id) => {
|
||||
const newSelectedIds = selectedCategoryIds.filter(prevId => prevId !== id);
|
||||
setSelectedCategoryIds(newSelectedIds);
|
||||
props.setSearchQuery({ categories: newSelectedIds });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mb-3'>
|
||||
<p className='text-center'>Kategorie</p>
|
||||
<div className='flex flex-wrap gap-y-2 mt-1 '>
|
||||
{selectedCategoryIds.map(id => {
|
||||
const category = categories.find(cat => cat.id === id);
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
onClick={() => handleRemoveCategory(id)}
|
||||
className='min-w-fit w-8 py-1 px-2 mr-3 border-2 hover:bg-gray-300 rounded-xl hover:cursor-pointer '
|
||||
>
|
||||
<p className='font-poppins text-sm'>{category.name}
|
||||
<span className='relative -top-[2px] left-1'>×</span>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className='border-2 rounded-lg px-3 py-1 w-full mt-2'
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
placeholder='Wpisz kategorię...'
|
||||
/>
|
||||
{inputValue.length >= 1 && suggestions.length > 0 && (
|
||||
<div className='absolute h-40 w-[90%] overflow-y-auto'>
|
||||
{suggestions.map(suggestion => (
|
||||
<div
|
||||
className='mx-3 p-3 border-2 border-gray-300 bg-gray-100 hover:bg-gray-200 cursor-pointer'
|
||||
key={suggestion.id}
|
||||
onClick={() => handleSuggestionClick(suggestion)}
|
||||
>
|
||||
{suggestion.name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
CategorySelect.propTypes = {
|
||||
setSearchQuery: PropTypes.func.isRequired,
|
||||
searchQuery: PropTypes.shape({
|
||||
categories: PropTypes.arrayOf(PropTypes.string)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
export default CategorySelect
|
||||
@ -11,11 +11,11 @@ const Cookies = props => {
|
||||
if (!show) return null;
|
||||
return (
|
||||
<>
|
||||
<div className='bg-gray-500 opacity-90 h-32 w-full fixed bottom-0 z-40 flex justify-center items-center'>
|
||||
<p className=' text-black'>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<button className='bg-white px-4 py-2 rounded-md ml-4'
|
||||
<div className='bg-neutral-900 opacity-[99%] h-24 sm:h-16 w-full fixed bottom-0 z-40 flex justify-center items-center'>
|
||||
<p className='mx-5 text-justify text-white font-'>Korzystamy z plików cookie, aby zapewnić prawidłowe działanie witryny. </p>
|
||||
<button className= 'bg-white px-4 py-2 rounded-md ml-4 mr-4 ring-2 ring-dimWhite hover:ring-4 duration-300 font-semibold'
|
||||
onClick={() => {handleCookies; setShow(!show)}}>
|
||||
Zgoda</button>
|
||||
Wyrażenie zgody</button>
|
||||
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -1,91 +1,185 @@
|
||||
|
||||
import Search from './Search'
|
||||
import { useEffect, useState } from 'react';
|
||||
import { categories, work_from_home, employment_types } from '../consts';
|
||||
import propTypes from 'prop-types';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Search from './Search';
|
||||
import Selector from './Selector';
|
||||
const Filter = (props) => {
|
||||
useEffect(() => {
|
||||
console.log(props.searchQuery)
|
||||
}, [props.searchQuery]);
|
||||
import RangeSlider from './RangeSlider';
|
||||
import CategorySelect from './CategorySelect';
|
||||
import { categories, work_from_home, employment_types } from '../consts';
|
||||
|
||||
const Filter = ({ isOpen, searchQuery, setSearchQuery, clearSearchQuery, onClick }) => {
|
||||
const [min_salary, max_salary] = [0, 100000];
|
||||
|
||||
useEffect(() => {
|
||||
console.log(searchQuery);
|
||||
}, [searchQuery]);
|
||||
|
||||
|
||||
const handle_checked_change = (e) => {
|
||||
const { name, value, checked } = e.target;
|
||||
props.setSearchQuery(prevState => {
|
||||
const newArray = checked
|
||||
? [...(prevState[name] || []), value]
|
||||
: (prevState[name] || []).filter(item => item !== value);
|
||||
return { ...prevState, [name]: newArray };
|
||||
});
|
||||
}
|
||||
|
||||
const handleRangeChange = useCallback((newMin, newMax) => {
|
||||
setSearchQuery(prevState => ({
|
||||
...prevState,
|
||||
min_salary: newMin,
|
||||
max_salary: newMax
|
||||
}));
|
||||
}, [setSearchQuery]);
|
||||
|
||||
const handleChange = useCallback((e, name) => {
|
||||
const value = e.target.value;
|
||||
const numValue = parseInt(value, 10);
|
||||
|
||||
setSearchQuery(prevState => {
|
||||
switch (name) {
|
||||
case 'name':
|
||||
return { ...prevState, name: value };
|
||||
case 'min_salary':
|
||||
if (value === '' || isNaN(numValue)) {
|
||||
return { ...prevState, min_salary: '' };
|
||||
}
|
||||
return { ...prevState, min_salary: numValue };
|
||||
case 'max_salary':
|
||||
if (value === '' || isNaN(numValue)) {
|
||||
return { ...prevState, max_salary: '' };
|
||||
}
|
||||
return { ...prevState, max_salary: numValue };
|
||||
default:
|
||||
return prevState;
|
||||
}
|
||||
});
|
||||
}, [setSearchQuery]);
|
||||
|
||||
const handleCheckedChange = useCallback((e) => {
|
||||
const { name, value, checked } = e.target;
|
||||
setSearchQuery(prevState => {
|
||||
// Sprawdź, czy dla danej nazwy już istnieje tablica w stanie
|
||||
if (!prevState[name]) {
|
||||
prevState[name] = [];
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
// Jeśli checkbox jest zaznaczony, dodaj wartość do odpowiedniej tablicy
|
||||
return {
|
||||
...prevState,
|
||||
[name]: [...prevState[name], value]
|
||||
};
|
||||
} else {
|
||||
// Jeśli checkbox jest odznaczony, usuń wartość z odpowiedniej tablicy
|
||||
return {
|
||||
...prevState,
|
||||
[name]: prevState[name].filter(item => item !== value)
|
||||
};
|
||||
}
|
||||
});
|
||||
}, [setSearchQuery]);
|
||||
|
||||
const filterButtonClass = useMemo(() => (
|
||||
'bg-gray-600 text-white py-1 sm:py-3 px-12 mx-auto sm:mx-40 col-span-2 rounded-md font-semibold ' +
|
||||
'text-xl hover:bg-gray-500 duration-300 hover:scale-110 my-3 sm:my-5 sm:min-h-full'
|
||||
), []);
|
||||
|
||||
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>
|
||||
<div className={`z-10 flex flex-col bg-dimWhite collapsible px-3 sm:px-4 ${isOpen ? 'expanded border-4' : ''}`}>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 mb-2">
|
||||
<div className="hidden md:block" />
|
||||
<p className="mx-auto mt-3 place-self-center text-center font-poppins font-semibold text-xl text-gray-600">Filtry</p>
|
||||
<button
|
||||
className="rounded-xl mt-4 w-32 py-1 bg-slate-400 hover:bg-slate-600 duration-100"
|
||||
onClick={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"
|
||||
type="text"
|
||||
value={searchQuery.name || ''}
|
||||
onChange={(e) => handleChange(e, 'name')}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-x-4">
|
||||
<Search
|
||||
label="Min. wynagrodzenie"
|
||||
label2="wynagrodzenie"
|
||||
placeholder="Wpisz kwotę minimalną..."
|
||||
name="min_salary"
|
||||
type="text"
|
||||
value={searchQuery.min_salary || ''}
|
||||
onChange={(e) => handleChange(e, 'min_salary')}
|
||||
/>
|
||||
<Search
|
||||
label="Max. wynagrodzenie"
|
||||
placeholder="Wpisz kwotę maksymalną..."
|
||||
name="max_salary"
|
||||
type="text"
|
||||
value={searchQuery.max_salary || ''}
|
||||
onChange={(e) => handleChange(e, 'max_salary')}
|
||||
/>
|
||||
<RangeSlider
|
||||
min={min_salary}
|
||||
max={max_salary}
|
||||
minVal={searchQuery.min_salary || min_salary}
|
||||
maxVal={searchQuery.max_salary || max_salary}
|
||||
setSearchQuery={setSearchQuery}
|
||||
onRangeChange={handleRangeChange}
|
||||
isOpen={isOpen}
|
||||
/>
|
||||
</div>
|
||||
<CategorySelect
|
||||
setSearchQuery = {setSearchQuery}
|
||||
searchQuery = {searchQuery}
|
||||
clearSearchQuery = {clearSearchQuery}
|
||||
/>
|
||||
{/* <Selector
|
||||
value_to_map_from={categories}
|
||||
name="Kategorie"
|
||||
inputname="categories"
|
||||
onChange={handleCheckedChange}
|
||||
state={searchQuery || {}}
|
||||
/> */}
|
||||
{/* <Selector
|
||||
value_to_map_from={work_from_home}
|
||||
name="Praca zdalna"
|
||||
inputname="employment_types"
|
||||
onChange={handleCheckedChange}
|
||||
state={searchQuery || {}}
|
||||
/>
|
||||
<Selector
|
||||
value_to_map_from={employment_types}
|
||||
name="Typ kontraktu"
|
||||
inputname="employment_types"
|
||||
onChange={handleCheckedChange}
|
||||
state={searchQuery || {}}
|
||||
/> */}
|
||||
|
||||
<button className={filterButtonClass} onClick={onClick}>
|
||||
Filtruj
|
||||
</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={props.searchQuery || {} }/>
|
||||
<Selector value_to_map_from={work_from_home} s name='Praca zdalna' inputname='work_from_home' state={props.searchQuery || {}} onChange={handle_checked_change}/>
|
||||
<Selector value_to_map_from={employment_types} name='Typ kontraktu' inputname='employment' state={props.searchQuery || {}} 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>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
Filter.propTypes = {
|
||||
isOpen: propTypes.bool,
|
||||
searchQuery: propTypes.object,
|
||||
setSearchQuery: propTypes.func,
|
||||
categories: propTypes.array,
|
||||
clearSearchQuery: propTypes.func,
|
||||
onClick: propTypes.func
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
searchQuery: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
min_salary: PropTypes.number,
|
||||
max_salary: PropTypes.number,
|
||||
categories: PropTypes.arrayOf(PropTypes.string),
|
||||
work_from_home: PropTypes.arrayOf(PropTypes.string),
|
||||
employment_types: PropTypes.arrayOf(PropTypes.string)
|
||||
}),
|
||||
setSearchQuery: PropTypes.func.isRequired,
|
||||
clearSearchQuery: PropTypes.func.isRequired,
|
||||
onClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
Filter.defaultProps = {
|
||||
searchQuery: {
|
||||
categories: [],
|
||||
work_from_home: [],
|
||||
employment: []
|
||||
employment_types: []
|
||||
}
|
||||
};
|
||||
|
||||
export default Filter;
|
||||
export default Filter;
|
||||
76
src/components/RangeSlider.jsx
Normal file
76
src/components/RangeSlider.jsx
Normal file
@ -0,0 +1,76 @@
|
||||
import { useRef, useEffect, useCallback } from 'react';
|
||||
import propTypes from 'prop-types';
|
||||
|
||||
|
||||
const RangeSlider = ({ min, max, minVal, maxVal, setSearchQuery, onRangeChange, isOpen }) => {
|
||||
const minValRef = useRef(minVal);
|
||||
const maxValRef = useRef(maxVal);
|
||||
const range = useRef(null);
|
||||
|
||||
const getPercent = useCallback(
|
||||
(value) => Math.round(((value - min) / (max - min)) * 100),
|
||||
[min, max]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const minPercent = getPercent(minVal === '' ? min : minVal);
|
||||
const maxPercent = getPercent(maxVal === '' ? max : maxVal);
|
||||
|
||||
if (range.current) {
|
||||
range.current.style.left = `${minPercent}%`;
|
||||
range.current.style.width = `${maxPercent - minPercent}%`;
|
||||
}
|
||||
}, [minVal, maxVal, getPercent, min, max]);
|
||||
|
||||
const handleChange = useCallback((event) => {
|
||||
const { id, value } = event.target;
|
||||
const numValue = Number(value);
|
||||
|
||||
let newMinVal = minVal === '' ? min : minVal;
|
||||
let newMaxVal = maxVal === '' ? max : maxVal;
|
||||
|
||||
if (id === 'range1') {
|
||||
newMinVal = numValue;
|
||||
minValRef.current = newMinVal;
|
||||
} else if (id === 'range2') {
|
||||
newMaxVal = numValue;
|
||||
maxValRef.current = newMaxVal;
|
||||
}
|
||||
|
||||
onRangeChange(newMinVal, newMaxVal);
|
||||
}, [minVal, maxVal, min, max, onRangeChange]);
|
||||
|
||||
return (
|
||||
<div className='col-span-2 my-5 px-8 slider-wrapper'>
|
||||
<input
|
||||
id="range1"
|
||||
type="range"
|
||||
min={min}
|
||||
step={100}
|
||||
max={max}
|
||||
value={minVal === '' ? min : minVal}
|
||||
onChange={handleChange}
|
||||
className={`thumb w-[85%] sm:w-[84%] thumb--left thumb--animatedin thumb--animatedout`}
|
||||
style={isOpen ? { zIndex: (minVal === '' ? min : minVal) > (maxVal === '' ? max - 100 : maxVal - 100) ? 5 : undefined, } : { display: 'none' }}
|
||||
/>
|
||||
<input
|
||||
id="range2"
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={100}
|
||||
value={maxVal === '' ? max : maxVal}
|
||||
onChange={handleChange}
|
||||
className={`thumb w-[85%] sm:w-[84%] thumb--right thumb--animatedin thumb--animatedout sm`}
|
||||
style={isOpen ? {} : { display: 'none'}}
|
||||
/>
|
||||
|
||||
<div className="slider" >
|
||||
<div className="slider__track " />
|
||||
<div ref={range} className="slider__range" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RangeSlider;
|
||||
@ -82,7 +82,7 @@ const Salary = ({handleChange, formData, removeFields, setFormData}) => {
|
||||
className={`h-12 border-2 rounded-xl px-5 mx-4 w-full ${!require_salary ? 'bg-slate-200' : ''} `}/></div>
|
||||
|
||||
<div>
|
||||
<p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Typ kontraktu</p>
|
||||
<p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Typ <br className='hidden sm:block md:hidden'/>kontraktu</p>
|
||||
<select
|
||||
value={formData['employment_type'] || 'default' }
|
||||
name='employmentType'
|
||||
@ -96,7 +96,7 @@ const Salary = ({handleChange, formData, removeFields, setFormData}) => {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Praca zdalna</p>
|
||||
<p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Praca <br className='block md:hidden'/> zdalna</p>
|
||||
<select
|
||||
value={formData['work_from_home'] || 'default' }
|
||||
name='workFromHome'
|
||||
|
||||
@ -21,13 +21,13 @@ useEffect(() => {
|
||||
}, [props.value])
|
||||
|
||||
return (
|
||||
<div className='grid sm:py-1'
|
||||
<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"
|
||||
<label className="mx-2 text-l text-center ml-3 font-poppins no-spin-buttons"
|
||||
htmlFor="search">
|
||||
{props.label}
|
||||
<p className='text-sm'>{props.label}</p>
|
||||
<span className='text-sm text-red-700'>
|
||||
{check_errors(props.value, props.name) ? '' : ' *'}
|
||||
</span>
|
||||
@ -38,9 +38,9 @@ useEffect(() => {
|
||||
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
|
||||
onChange={props.onChange}
|
||||
className={`border-2 ${ props.name === 'min_salary' || props.name === 'max_salary' ? 'w-[12rem] xs:w-[20rem] sm:w-full md:w-full lg:w-full' : '' }
|
||||
bg-white h-8 px-5 rounded-lg
|
||||
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ą!'}
|
||||
@ -55,7 +55,8 @@ Search.propTypes = {
|
||||
name: propTypes.string,
|
||||
setSearchQuery: propTypes.func,
|
||||
value: propTypes.string,
|
||||
_type: propTypes.string
|
||||
_type: propTypes.string,
|
||||
onChange: propTypes.func
|
||||
}
|
||||
|
||||
export default Search
|
||||
|
||||
@ -1,33 +1,35 @@
|
||||
import propTypes from 'prop-types'
|
||||
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[props.inputname]?.includes(value_to_map_from.id)}
|
||||
/>
|
||||
{value_to_map_from.name}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<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={Array.isArray(props.state[props.inputname]) && props.state[props.inputname].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
|
||||
value_to_map_from: PropTypes.array,
|
||||
setSearchQuery: PropTypes.func,
|
||||
name: PropTypes.string,
|
||||
inputname: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
state: PropTypes.object
|
||||
}
|
||||
|
||||
export default Selector
|
||||
export default Selector
|
||||
@ -102,15 +102,15 @@ const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={prevStep}
|
||||
className="h-12 w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] scale-100 text-white hover:scale-125 duration-300"
|
||||
className="h-16 md:h-12 w-80 md:w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] text-white hover:scale-125 duration-300"
|
||||
>
|
||||
<span className="text-[18px]">←</span> Przejdź do
|
||||
<span className="text-[18px]">←</span> Przejdź do <br className="block md:hidden"/>
|
||||
poprzedniego kroku
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={nextStep}
|
||||
className="h-12 w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] scale-100 text-white hover:scale-125 duration-300"
|
||||
className="h-16 md:h-12 w-80 md:w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] text-white hover:scale-125 duration-300"
|
||||
>
|
||||
Przejdź do następnego kroku
|
||||
<span className="text-[18px]">→</span>{" "}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
|
||||
import styles from "../style";
|
||||
import propTypes from "prop-types";
|
||||
import { lorem_ipsum, lorem_ipsum_premium, lorem_ipsum_starter } from "../consts";
|
||||
|
||||
const StepOneJoblisting = ({ nextStep, handleChange, formData }) => {
|
||||
// Funkcja do obsługi kliknięcia na div i aktualizacji stanu
|
||||
@ -9,48 +10,62 @@ const StepOneJoblisting = ({ nextStep, handleChange, formData }) => {
|
||||
};
|
||||
|
||||
const activeStyle =
|
||||
"h-[500px] w-64 border-4 rounded-xl border-stone-200 bg-gray-200 div-transition scale-125";
|
||||
"h-[200px] sm:h-[350px] md:h-min w-64 pb-4 border-4 rounded-xl border-stone-200 bg-gray-200 div-transition scale-105 sm:scale-110";
|
||||
const inactiveStyle =
|
||||
"h-[500px] w-64 rounded-xl bg-gray-200 border-gray-200 div-transition cursor-pointer hover:bg-gray-300";
|
||||
"h-[200px] sm:h-[350px] md:h-min w-64 pb-4 rounded-xl bg-gray-200 border-gray-200 div-transition cursor-pointer hover:bg-gray-300";
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={` grid grid-cols-1 ${styles.paddingX} pb-8 pt-8 gap-1 h-full`}
|
||||
className={`grid grid-cols-1 ${styles.paddingX} pb-8 mt-24 xs:mt-2 xs:pt-8 gap-1 h-full`}
|
||||
>
|
||||
<h1 className={`${styles.heading1} text-center`}>
|
||||
Zacznij dodawać ogłoszenie z izaac.pl
|
||||
<h1 className={`text-center text-2xl sm:text-[40px] font-bold`}>
|
||||
Zacznij dodawać <br className="block md:hidden" /> ogłoszenie z izaac.pl
|
||||
</h1>
|
||||
<p className={`${styles.paragraph} text-center`}>
|
||||
Wybierz pakiet najlepiej odpowiadający Twoim potrzebom
|
||||
<p className={`text-center text-xl mt-3`}>
|
||||
Wybierz pakiet najlepiej <br className="block md:hidden" />odpowiadający Twoim potrzebom
|
||||
</p>
|
||||
</div>
|
||||
<div className={` ${styles.flexStart} ${styles.paddingX} gap-16 mt-20`}>
|
||||
{/* Przykładowe divy jako przyciski wyboru */}
|
||||
|
||||
<div className="h-[300px] sm:h-[600px]">
|
||||
<div className={` ${styles.flexStart} ${styles.paddingX} gap-3 xs:gap-5 sm:gap-8 mt-6`}>
|
||||
|
||||
<div
|
||||
className={
|
||||
formData.posting_option === "M" ? activeStyle : inactiveStyle
|
||||
}
|
||||
onClick={handleDivClick("M")}
|
||||
>
|
||||
<p className={`${styles.paragraph} text-center mt-4`}>Starter</p>
|
||||
<p
|
||||
className={`font-poppins text-l
|
||||
sm:text-xl text-center mt-4`}>
|
||||
Starter
|
||||
</p>
|
||||
<ul className="hidden md:block">
|
||||
{lorem_ipsum_starter.map((lorem_ipsum) =>
|
||||
(<li className="text-sm lista-outside mx-8 text-justify list-inside">{lorem_ipsum}</li>)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="h-[400px] w-0.5 bg-gray-300"></div>
|
||||
<div className="h-[150px] sm:h-[250px] md:h-[300px] w-0.5 bg-gray-300"></div>
|
||||
<div
|
||||
className={
|
||||
formData.posting_option === "S" ? activeStyle : inactiveStyle
|
||||
}
|
||||
onClick={handleDivClick("S")}
|
||||
>
|
||||
<p className={`${styles.paragraph} text-center mt-4`}>Standard</p>
|
||||
<div className="mt-6">
|
||||
<p className={`${styles.paragraph} text-center`}>
|
||||
<p className={`font-poppins text-l sm:text-xl text-center mt-4`}>Standard</p>
|
||||
<div className="mt-2">
|
||||
<p className={`font-poppins text-sm sm:text-[14px] text-center mb-2`}>
|
||||
najczęsciej wybierany
|
||||
</p>
|
||||
<ul className="hidden md:block">
|
||||
{lorem_ipsum.map((lorem_ipsum) =>
|
||||
(<li className="text-sm lista-outside mx-8 text-justify list-inside">{lorem_ipsum}</li>)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[400px] w-0.5 bg-gray-300"></div>
|
||||
<div className="h-[150px] sm:h-[250px] md:h-[300px] w-0.5 bg-gray-300"></div>
|
||||
<div
|
||||
className={
|
||||
formData.posting_option === "P" ? activeStyle : inactiveStyle
|
||||
@ -58,21 +73,26 @@ const StepOneJoblisting = ({ nextStep, handleChange, formData }) => {
|
||||
onClick={handleDivClick("P")}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<p className={`${styles.paragraph} text-center mt-4`}>Premium</p>
|
||||
<p className={`font-poppins text-l sm:text-xl text-center mt-4`}>Premium</p>
|
||||
<ul className="hidden md:block">
|
||||
{lorem_ipsum_premium.map((lorem_ipsum) =>
|
||||
(<li className="text-sm lista-outside mx-8 text-justify list-inside">{lorem_ipsum}</li>)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid pt-32 items-center justify-center">
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<button
|
||||
type="button"
|
||||
onClick={nextStep}
|
||||
className="h-12 w-72 rounded-xl bg-gray-700
|
||||
className="absolute inset-x-0 md:-left-4 bottom-[4rem] mx-auto h-12 w-72 rounded-xl bg-gray-700
|
||||
font-poppins font-semibold text-[14px] text-white
|
||||
hover:scale-125 duration-300"
|
||||
>
|
||||
Przejdź do następnego kroku
|
||||
<span className="text-[18px]">→</span>{" "}
|
||||
</button>
|
||||
<div className="h-12 w-full"></div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import propTypes from 'prop-types';
|
||||
|
||||
import MaximumLength from '@ckeditor/ckeditor5-react'
|
||||
import { categories, experience_levels } from '../consts';
|
||||
import styles from '../style'
|
||||
import { CKEditor } from '@ckeditor/ckeditor5-react';
|
||||
@ -17,7 +17,7 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
|
||||
const data = editor.getData();
|
||||
setEditorData(data);
|
||||
}
|
||||
|
||||
const max_char = 600;
|
||||
const handleNextStep = () => {
|
||||
if (validateForm()) {
|
||||
console.log(errors)
|
||||
@ -137,6 +137,16 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
|
||||
editor={ClassicEditor}
|
||||
data={formData['content'] || ''}
|
||||
required
|
||||
// config={{
|
||||
// plugins: [WordCount],
|
||||
// toolbar: ['wordCount'],
|
||||
// wordCount: {
|
||||
// onUpdate: stats => {
|
||||
// console.log(stats.words, stats.characters)
|
||||
// }
|
||||
// }
|
||||
|
||||
// }}
|
||||
onChange={(event, editor) => {
|
||||
const data = editor.getData();
|
||||
handleChange('content')(data); // Wywołanie zmodyfikowanej funkcji handleChange
|
||||
@ -205,11 +215,11 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
|
||||
formData={formData}/>
|
||||
</div>
|
||||
|
||||
<div className={`${styles.flexCenter} p-20 gap-20`}>
|
||||
<div className={`${styles.flexCenter} py-10 gap-20`}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={prevStep}
|
||||
className='h-12 w-72 rounded-xl bg-gray-700
|
||||
className='h-16 md:h-12 w-80 md:w-72 rounded-xl bg-gray-700
|
||||
font-poppins font-semibold text-[14px] scale-100
|
||||
text-white hover:scale-125 duration-300'>
|
||||
<span className='text-[18px]'>←</span>
|
||||
@ -217,7 +227,7 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleNextStep}
|
||||
className='h-12 w-72 rounded-xl
|
||||
className='h-16 md:h-12 w-80 md:w-72 rounded-xl
|
||||
bg-gray-700 font-poppins
|
||||
font-semibold text-[14px] scale-100
|
||||
text-white hover:scale-125 duration-300'>
|
||||
|
||||
@ -96,9 +96,21 @@ const WorkApp = () => {
|
||||
};
|
||||
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState({});
|
||||
const [searchQuery, setSearchQuery] = useState({
|
||||
min_salary: '',
|
||||
max_salary: '',
|
||||
categories: [],
|
||||
name: ''
|
||||
});
|
||||
const clearSearchQuery = () => {
|
||||
setSearchQuery({});
|
||||
setSearchQuery({
|
||||
min_salary: '',
|
||||
max_salary: '',
|
||||
categories: [],
|
||||
name: ''
|
||||
});
|
||||
getSkills();
|
||||
setIsOpen(!isOpen)
|
||||
};
|
||||
|
||||
const [selectedOgloszenie, setSelectedOgloszenie] = useState(ogloszenia[0]);
|
||||
@ -144,13 +156,11 @@ const WorkApp = () => {
|
||||
<div
|
||||
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"
|
||||
} `}
|
||||
sm:h-[89vh] ${isDetailsVisible ? "hidden sm:block" : "block" }`}
|
||||
>
|
||||
<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">
|
||||
<h1 className="text-center text-xl sm:text-l md:text-2xl font-poppins font-semibold text-slate-800 py-2 mx-2">
|
||||
Oferty pracy
|
||||
</h1>
|
||||
<button
|
||||
|
||||
@ -326,4 +326,35 @@ export const employment_types = [
|
||||
"id": "INT",
|
||||
"name": "Staż",
|
||||
},
|
||||
]
|
||||
|
||||
export const lorem_ipsum = [
|
||||
"Lorem ipsum dolor sit amet",
|
||||
"consectetur adipiscing elit",
|
||||
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ",
|
||||
"Ut enim ad minim veniam",
|
||||
"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
|
||||
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ',
|
||||
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||
]
|
||||
|
||||
export const lorem_ipsum_starter = [
|
||||
"Lorem ipsum dolor sit amet",
|
||||
"consectetur adipiscing elit",
|
||||
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ",
|
||||
"Ut enim ad minim veniam",
|
||||
"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
|
||||
]
|
||||
|
||||
export const lorem_ipsum_premium = [
|
||||
"Lorem ipsum dolor sit amet",
|
||||
"consectetur adipiscing elit",
|
||||
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ",
|
||||
"Ut enim ad minim veniam",
|
||||
"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
|
||||
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ',
|
||||
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ",
|
||||
"Ut enim ad minim veniam",
|
||||
"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
|
||||
]
|
||||
150
src/index.css
150
src/index.css
@ -154,6 +154,10 @@ input[type="number"] {
|
||||
list-style: inside;
|
||||
}
|
||||
|
||||
.lista-outside {
|
||||
list-style: outside;
|
||||
}
|
||||
|
||||
.sidebar-show {
|
||||
-webkit-animation: slide-top 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
animation: slide-top 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
@ -162,4 +166,148 @@ input[type="number"] {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.slider-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.slider__track,
|
||||
.slider__range,
|
||||
.slider__left-value,
|
||||
.slider__right-value {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.slider__track,
|
||||
.slider__range {
|
||||
border-radius: 3px;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
.slider__track {
|
||||
background-color: #ced4da;
|
||||
width: 100;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.slider__range {
|
||||
background-color: #9fe5e1;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.slider__left-value,
|
||||
.slider__right-value {
|
||||
color: #dee2e6;
|
||||
font-size: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.slider__left-value {
|
||||
left: 6px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.slider__right-value {
|
||||
right: -4px;
|
||||
color: black;
|
||||
|
||||
}
|
||||
|
||||
/* Removing the default appearance */
|
||||
.thumb,
|
||||
.thumb::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.thumb {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
height: 0;
|
||||
outline: none;
|
||||
transition-duration: 0.5s;
|
||||
transition-property: display;
|
||||
}
|
||||
|
||||
.thumb--left {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.thumb--right {
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
/* For Chrome browsers */
|
||||
.thumb::-webkit-slider-thumb {
|
||||
background-color: #f1f5f7;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 1px 1px #ced4da;
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin-top: 4px;
|
||||
pointer-events: all;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* For Firefox browsers */
|
||||
.thumb::-moz-range-thumb {
|
||||
background-color: #f1f5f7;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 1px 1px #ced4da;
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin-top: 4px;
|
||||
pointer-events: all;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
@-webkit-keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.thumb--animatedin {
|
||||
animation: fadeIn 0.5s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
@keyframes fadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
.thumb--animatedout {
|
||||
animation: fadeOut 0.5s;
|
||||
}
|
||||
|
||||
.t-width {
|
||||
width: 84%;
|
||||
}
|
||||
|
||||
.search-width {
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user