This commit is contained in:
Jakub K 2024-03-17 19:49:59 +01:00
parent a5f58d1bdb
commit 3f8624efd6
25 changed files with 952 additions and 409 deletions

View File

@ -1,26 +1,30 @@
import styles from './style';
import NavBar from './components/NavBar';
import WorkApp from './components/WorkApp';
import Home from './components/Home';
import { BrowserRouter as Router } from 'react-router-dom';
import { Route, Routes } from 'react-router-dom';
import Contact from './components/Contact';
import Mininav from './components/Mininav';
import AddJobListing from './components/AddJobListing';
import styles from "./style";
import NavBar from "./components/NavBar";
import WorkApp from "./components/WorkApp";
import Home from "./components/Home";
import { BrowserRouter as Router } from "react-router-dom";
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 ""
const loc = () => {
if (location.pathname.startsWith("work/jobposting")) {
return "";
} else {
return "overflow-hidden"
return "overflow-hidden";
}
}
};
return (
<Router>
<div className={`bg-white w-full ${loc}`}>
<div className={`${styles.paddingX} ${styles.flexCenter} border-b-2`}>
<Mininav />
<Mininav />
<div className={`${styles.boxWidth}`}>
<NavBar />
</div>
@ -34,15 +38,14 @@ function App() {
<Route path="/work/postings" element={<WorkApp />} />
<Route path="/contact" element={<Contact />} />
<Route path="/work/jobposting" element={<AddJobListing />} />
<Route path="/employerpanel" element={<EmployerPanel />} />
{/* Add more routes as needed */}
</Routes>
</div>
</div>
</div>
</Router>
)
};
);
}
export default App;

View File

@ -3,11 +3,15 @@ import StepOneJoblisting from './StepOneJoblisting';
import StepTwoJoblisting from './StepTwoJoblisting';
import StepThreeJoblisting from './StepThreeJoblisting';
import StepFourJoblisting from './StepFourJoblisting'
import Success from './Success';
import axios from 'axios';
const AddJobListing = () => {
const [currentStep, setCurrentStep] = useState(1)
const [skills, setSkills] = useState([])
const [formData, setFormData] = useState({
'posting_option': 'S',
'requiresalary': true
'require_salary': true
})
const nextStep = () => {
setCurrentStep(currentStep + 1);
@ -47,13 +51,34 @@ const AddJobListing = () => {
console.log('Aktualny stan formularza:', formData);
};
const handleSubmit = () => {
const handleSubmit = async () => {
try {
const response = await axios.post('http://izaac.izaac.pl/api/jobposting/joboffers/', formData);
console.log('Data posted:', response.data);
nextStep();
} catch (error) {
console.error("Error posting data:", error);
}
}
const getSkills = async () => {
try {
const response = await axios.get('http://izaac.izaac.pl/api/jobposting/skills/');
setSkills(response.data.sort((a, b) => a.skill_name.localeCompare(b.skill_name)));
} catch (error) {
console.error("Error fetching data:", error);
}
}
useEffect(() => {
console.log('Aktualny stan formularza:', formData);
}, [formData]);
useEffect(() => {
getSkills();
} , [currentStep])
switch (currentStep) {
case 1:
return <StepOneJoblisting
@ -64,6 +89,7 @@ const AddJobListing = () => {
return <StepFourJoblisting
nextStep={nextStep}
formData={formData}
prevStep={prevStep}
handleChange={handleChange} />;
case 3:
return <StepTwoJoblisting
@ -72,12 +98,17 @@ const AddJobListing = () => {
removeFields={removeFields}
setFormData={setFormData}
handleChange={handleChange}
formData={formData} />;
formData={formData}
skills={skills} />;
case 4:
return <StepThreeJoblisting
prevStep={prevStep}
handleSubmit={handleSubmit}
formData={formData} />;
formData={formData}
skills={skills}
/>;
case 5:
return <Success />;
default:
return <div>Unknown step</div>;
}

View File

@ -0,0 +1,18 @@
import React from 'react'
const Category = (props) => {
return (
<div className='flex flex-shrink-0 items-center gap-6 mr-6'>
<a className='mx-auto text-center text-l
font-poppins font-semibold text-slate-800
py-4 hover:scale-125 duration-200'
href='' >
{props.name}
</a>
<div className={`w-0.5 h-6 bg-black opacity-10
${props.last ? 'hidden' : 'block'}`}></div>
</div>
)
}
export default Category

View File

@ -0,0 +1,96 @@
import React from "react";
import { ogloszenia } from "../consts";
import ListingSmall from "./ListingSmall";
import { useState } from "react";
import SkillRender from "./SkillRender";
const EmployerPanel = () => {
const [showActions, setShowActions] = useState(true);
const [selectedOgloszenie, setSelectedOgloszenie] = useState(ogloszenia[0]);
const [isDetailsVisible, setIsDetailsVisible] = useState(false); // Dodany stan
const handleOgloszenieClick = (ogloszenie) => {
setSelectedOgloszenie(ogloszenie);
setIsDetailsVisible(true); // Pokaż szczegóły na urządzeniach mobilnych
};
// Funkcja do powrotu do listy ogłoszeń
const handleBackToList = () => {
setIsDetailsVisible(false);
};
const handleOnClick = () => {
setShowActions(showActions);
handleOgloszenieClick(selectedOgloszenie);
};
return (
<>
<div className="px-10 pt-2 grid grid-cols-3 relative">
<div className={`md:col-span-1 col-span-3 ${isDetailsVisible ? 'hidden md:block' : 'block'} h-[80vh] overflow-y-auto`}>
<p className="sticky top-2 z-50 h-16 text-center font-bold text-[32px] mr-3 mt-2 mb-2 bg-gray-300 rounded-xl text-gray-800 p-2 ">
Twoje ogłoszenia
</p>
{ogloszenia.map((ogloszenie, index) => (
<ListingSmall
key={ogloszenie.id}
name={ogloszenie.name}
company_name={ogloszenie.company_name}
salaryrange={ogloszenie.salaryRange}
requiresalary={ogloszenie.requiresalary}
index={index}
onClick={() => handleOgloszenieClick(ogloszenie)}
/>
))}
</div>
<div className="md:col-span-2 col-span-3">
{isDetailsVisible && (
<>
<div className="grid grid-cols-2 place-items-center">
<button className="font-bold sm:w-[80%] w-[90%] px-1 sm:h-12 h-16 ml-6 border-2 rounded-xl mt-6 bg-gray-300 hover:bg-gray-400 duration-300">
Przedłuż ważność ogłoszenia
</button>
<button className="font-bold sm:w-[80%] w-[90%] px-1 sm:h-12 h-16 ml-6 border-2 rounded-xl mt-6 bg-gray-300 hover:bg-gray-400 duration-300">
Wyróżnij ogłoszenie
</button>
<button className="font-bold sm:w-[80%] w-[90%] px-1 sm:h-12 h-16 ml-6 border-2 rounded-xl mt-6 bg-gray-300 hover:bg-gray-400 duration-300">
Zakończ ogłoszenie
</button>
<button className="font-bold sm:w-[80%] w-[90%] px-1 sm:h-12 h-16 ml-6 border-2 rounded-xl mt-6 bg-gray-300 hover:bg-gray-400 duration-300">
Dodaj nowe ogłoszenie
</button>
{isDetailsVisible && (
<button
onClick={handleBackToList}
className="md:hidden block font-poppins text-white font-semibold bg-gray-500 px-4 py-2 rounded-md mx-4 mt-6 w-[90%] col-span-2 place-self-center"
>
Powrót do listy
</button>
)}
</div>
<div
className={`bg-gray-200 md:h-[68vh] h-[95%] overflow-y-auto p-4 my-4 ${
isDetailsVisible ? "block" : "hidden md:block"
}`}
>
<div className="grid md:grid-cols-5 sm:grid-cols-4 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>
</div>
</>
);
};
export default EmployerPanel;

30
src/components/Filter.jsx Normal file
View File

@ -0,0 +1,30 @@
import React from 'react'
import Search from './Search'
const Filter = (props) => {
if (!props.isOpen) return null;
return (
<div className={`sticky top-24 z-20 bg-gray-100 grid grid-cols-2 slide-container my-6 py-5 ${!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ą...' />
<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}
>
Filtruj
</button>
</div>
)
}
export default Filter

View File

@ -6,6 +6,8 @@ import axios from 'axios';
const ImageUpload = ({setFormData, data}) => {
const [imageSrc, setImageSrc] = useState(placeholderImage);
const fileInputRef = useRef(); // Referencja do ukrytego inputu plików
const [uploadedImageData, setUploadedImageData] = useState(null);
const handleImageChange = async (e) => {
const file = e.target.files[0];
@ -38,7 +40,7 @@ const ImageUpload = ({setFormData, data}) => {
setImageSrc(url)
}
catch (error) {
console.error('Błąd podczas przesyłania obrazka:', e)
console.error('Błąd podczas przesyłania obrazka:', error)
}
}

View File

@ -0,0 +1,65 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import DOMPurify from 'dompurify';
import SkillRender from './SkillRender'; // Ensure this import is correctly pointing to your SkillRender component
const JobOfferContent = ({ id, skills }) => {
const [jobOffer, setJobOffer] = useState(null);
const fetchJobOfferById = async (jobId) => {
try {
const response = await axios.get(`http://izaac.izaac.pl/api/jobposting/joboffers/${jobId}`);
setJobOffer(response.data);
} catch (error) {
console.error('Failed to fetch job offer:', error);
}
};
const processHTML = (htmlString) => {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');
doc.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach((tag) => {
tag.classList.add('no-tailwindcss-base', 'text-xl');
});
doc.querySelectorAll('p').forEach((tag) => {
tag.classList.add('no-tailwindcss-base', 'text-slate-800', 'text-lg');
});
doc.querySelectorAll('ol, ul').forEach((tag) => tag.classList.add('no-tailwindcss-base'));
doc.querySelectorAll('li').forEach((tag) => tag.classList.add('lista'));
return doc.body.innerHTML;
};
const cleanAndProcessData = (userInput) => {
const sanitizedHTML = DOMPurify.sanitize(userInput, { USE_PROFILES: { html: true } });
return processHTML(sanitizedHTML);
};
const renderContent = (htmlString) => {
return <div dangerouslySetInnerHTML={{ __html: cleanAndProcessData(htmlString) }} />;
};
useEffect(() => {
fetchJobOfferById(id);
}, [id]);
return (
<>
{jobOffer && (
<>
<h1 className="my-4 mx-6 text-3xl font-bold text-slate-800">{jobOffer.name} w {jobOffer.company_name}</h1>
<div className="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 gap-4">
{jobOffer.skill_levels.map((skillLevel) => {
const skill = skills.find((s) => s.id === parseInt(skillLevel.skill_id));
return skill ? (
<SkillRender key={skill.id} skill={skill.skill_name} level={skillLevel.skill_level} />
) : null;
})}
</div>
<div className="text-slate-800 font-medium mt-4">{renderContent(jobOffer.content)}</div>
</>
)}
</>
);
};
export default JobOfferContent;

View File

@ -1,55 +0,0 @@
import React from 'react'
import styles from '../style'
import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
const JobPosting = () => {
return (
<>
<div className='grid grid-cols-3'>
<div className='sm:px-20 px-16 sm:pt-12 pt-6 pb-6 xl:col-span-2 col-span-3'>
<span className='justify-center'>
<p className={`${styles.paragraph}`}>Zacznij dodawać ogłoszenie o pracę z Izaac.pl </p>
</span>
</div>
</div>
<div className='no-tailwindcss-base'>
<div className='editor-container max-w-none h-[70vh]'>
<form action="">
<div className='grid mb-4'>
<label className={`${styles.paragraph} px-4`} htmlFor="">Nazwa ogłoszenia</label>
<input type="text" name="" id="" placeholder='Wpisz nazwę stanowiska...' className={`border-b-2 px-4 my-3 mx-2 ${styles.paragraph} `}/>
</div>
<CKEditor
editor={ ClassicEditor }
className=""
config={{}}
data="<h1>Zacznij pisać ogłoszenie!</h1>"
onReady={ editor => {
// You can store the "editor" and use when it is needed.
console.log( 'Editor is ready to use!', editor );
} }
onChange={ ( event, editor ) => {
const data = editor.getData();
console.log( { event, editor, data } );
} }
onBlur={ ( event, editor ) => {
console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
console.log( 'Focus.', editor );
} }
/>
</form>
</div>
</div>
</>
)
}
export default JobPosting

View File

@ -0,0 +1,27 @@
import React from 'react'
import { IzaacLOGO } from '../assets'
const ListingSmall = ({ id, name, company_name, min_salary, max_salary, require_salary, index, onClick, _class, image }) => {
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}`}
onClick={onClick}
>
<div className='flex-shrink-0 w-[6rem] h-[6rem] bg-white rounded-lg flex items-center justify-center'>
{/* Obrazek jest wyśrodkowany i zachowuje proporcje */}
<img src={image || IzaacLOGO} alt={name} className='object-contain object-center p-2' />
</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>
{/* 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>
)}
</div>
</div>
);
};
export default ListingSmall;

View File

@ -1,6 +1,8 @@
import React, { 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)
@ -13,14 +15,16 @@ const Mininav = () => {
return (
<>
<div className='bg-gray-500 h-5 absolute w-full top-0 flex justify-end items-center'>
<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>
<button onClick={openPopup} className='text-white font-poppins font-semibold text-xs mx-12'>
Rejestracja
</button>
<button onClick={openLogin} className='text-white font-poppins font-semibold text-xs sm:mr-12 mr-6 sm:pr-12'>
Zaloguj się
</button>
</div>
{/* Komponent modalu */}
<Register isOpen={isPopupOpen} onClose={closePopup} />

View File

@ -25,10 +25,14 @@ const Register = ({isOpen, onClose}) => {
<div className='mb-6 pr-4'>
<input type="password" name="" id="" placeholder='Powtórz hasło' className={`border-b px-4 my-3 mx-2 ${styles.paragraph} w-full text-center `}/>
</div>
<div className='mb-6 pr-4'>
<div className='pr-4'>
<input type="checkbox" name="" id="" placeholder='' className={`border-b-2 px-4 my-3 mx-2 ${styles.paragraph} text-center `}/>
<label>Zapoznałem sie z <a href='#' className='hover:text-blue-600 font-bold sm'>regulaminem portalu Izaac.pl</a> i go akceptuję</label>
</div>
<div className='mb-6 pr-4'>
<input type="checkbox" name="" id="" placeholder='' className={`border-b-2 px-4 my-3 mx-2 ${styles.paragraph} text-center `}/>
<label>Rejestruję się jako pracodawca</label>
</div>
<button type="submit" className="bg-gray-700 text-white px-4 py-2 rounded w-full font-semibold text">
Z a r e j e s t r u j &nbsp;&nbsp;s i ę

View File

@ -1,35 +1,24 @@
import React, { useState, useEffect } from 'react'
import styles from '../style'
const EmploymentTypes = [
'Kontrakt B2B',
'Umowa o pracę',
'Umowa zlecenie',
'Umowa o dzieło',
'Staż',
]
const WorkFromHomes = [
'Praca zdalna',
'Hybrydowo',
'W biurze',
'Inne'
]
import {employment_types, work_from_home } from '../consts'
const Salary = ({handleChange, formData, removeFields, setFormData}) => {
const [requireSalary, setRequireSalary] = useState(true)
const [require_salary, setrequire_salary] = useState(true)
const handleRequireSalary = () => {
setRequireSalary(prevRequireSalary => !prevRequireSalary);
const handlerequire_salary = () => {
setrequire_salary(prevrequire_salary => !prevrequire_salary);
};
useEffect(() => {
setFormData({...formData, 'requiresalary': requireSalary});
}, [requireSalary]);
setFormData({...formData, 'require_salary': require_salary});
}, [require_salary]);
const [minBigger, setMinBigger] = useState(true);
const handleCheck = () => {
let minSalary = parseInt(formData.minSalary);
let maxSalary = parseInt(formData.maxSalary);
if (minSalary > maxSalary) {
let min_salary = parseInt(formData.min_salary);
let max_salary = parseInt(formData.max_salary);
if (min_salary > max_salary) {
setMinBigger(true)
}
else {
@ -39,29 +28,29 @@ const Salary = ({handleChange, formData, removeFields, setFormData}) => {
useEffect(() => {
handleCheck();
console.log(minBigger)
console.log(formData.minSalary)
console.log(formData.maxSalary)
console.log(formData.min_salary)
console.log(formData.max_salary)
}, [formData.minSalary, formData.maxSalary]);
}, [formData.min_salary, formData.max_salary]);
useEffect(() => {
})
useEffect( () =>{
if (formData.requiresalary === false) {
removeFields(['minsalary', 'maxsalary']);
setRequireSalary(formData.requiresalary)
if (formData.require_salary === false) {
removeFields(['min_salary', 'max_salary']);
setrequire_salary(formData.require_salary)
}
}, [formData.requiresalary])
}, [formData.require_salary])
return (
<div className='grid grid-cols-4 mt-4 gap-6'>
<div className={`grid grid-cols-4 mt-4 gap-6 ${minBigger ? 'mb-10' : ''} duration-300`}>
<div className='col-span-4'>
<input
className='ml-6'
type="checkbox"
checked={!requireSalary}
name="requiresalary"
onChange={handleRequireSalary}
checked={!require_salary}
name="require_salary"
onChange={handlerequire_salary}
id="" />
<span className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'> Nie chce podawać wysokości wynagrodzenia</span>
</div>
@ -69,53 +58,53 @@ const Salary = ({handleChange, formData, removeFields, setFormData}) => {
<div className='relative'>
<p className='ml-5 font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Min. wynagrodzenie</p>
<input
disabled={!requireSalary}
name='minsalary'
value={formData['minsalary'] || '' }
disabled={!require_salary}
name='min_salary'
value={formData['min_salary'] || '' }
onChange={handleChange('minsalary')}
onChange={handleChange('min_salary')}
type='number'
className={`h-12 border-2 rounded-xl px-5 mx-4 w-full ${!requireSalary ? 'bg-slate-200' : ''}`}/>
className={`h-12 border-2 rounded-xl px-5 mx-4 w-full ${!require_salary ? 'bg-slate-200' : ''}`}/>
{minBigger && (
<div className='absolute top-[5rem] left-8 border-2 border-red-500 bg-red-200 rounded-b-xl p-2 opacity-90' >
<p className='font-poppins text-red-700 text-[14px] mt-1 mb-1'>Minimalne wynagrodzenie nie może być większe od maks. wynagrodzenia</p>
<p className='font-poppins text-red-700 text-[11px] mt-1 mb-1'>Minimalne wynagrodzenie nie może być większe od maks. wynagrodzenia</p>
</div>
)}
</div>
<div >
<p className='ml-5 font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Maks. wynagrodzenie</p>
<input
disabled={!requireSalary}
value={formData['maxsalary'] || '' }
name="maxSalary"
onChange={handleChange('maxsalary')}
disabled={!require_salary}
value={formData['max_salary'] || '' }
name="max_salary"
onChange={handleChange('max_salary')}
type='number'
className={`h-12 border-2 rounded-xl px-5 mx-4 w-full ${!requireSalary ? 'bg-slate-200' : ''} `}/></div>
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>
<select
value={formData['employmentType'] || 'default' }
value={formData['employment_type'] || 'default' }
name='employmentType'
onChange={handleChange('employmentType')}
onChange={handleChange('employment_type')}
className='h-12 border-2 rounded-xl px-5 mx-4 w-full '>
<option value={'default'} disabled >Wybierz opcję</option>
{EmploymentTypes.map(EmploymentType => (
<option key={EmploymentType}>{EmploymentType}</option>
{employment_types.map(employment_type => (
<option key={employment_type.id} value={employment_type.id}>{employment_type.name}</option>
))}
</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 zdalna</p>
<select
value={formData['workFromHome'] || 'default' }
value={formData['work_from_home'] || 'default' }
name='workFromHome'
onChange={handleChange('workFromHome')}
onChange={handleChange('work_from_home')}
className='h-12 border-2 rounded-xl px-5 mx-4 w-full'>
<option value={'default'} disabled >Wybierz opcję</option>
{WorkFromHomes.map(WorkFromHomes => (
<option key={WorkFromHomes}>{WorkFromHomes}</option>
{work_from_home.map(work_from_home => (
<option key={work_from_home.id} value={work_from_home.id}>{work_from_home.name}</option>
))}
</select>
</div>

19
src/components/Search.jsx Normal file
View File

@ -0,0 +1,19 @@
import React from 'react'
const Search = (props) => {
return (
<div className='grid py-2'>
<label className="mx-2 text-l font- text-center font-poppins"
htmlFor="search">
{props.label}
</label>
<input type="text"
placeholder={props.placeholder}
className="border-2 border-gray-300
bg-white h-10 px-5 pr-16 mx-3 rounded-lg
text-sm focus:outline-none" />
</div>
)
}
export default Search

View File

@ -7,6 +7,7 @@ const levelMappings = {
'A': 'Zaawansowany',
'E': 'Ekspert',
};
const skillLevels = ['N', 'B', 'M', 'A', 'E'];
function renderCircles(letter, handleCircleClick) {
const level = levelMappings[letter];
@ -17,7 +18,7 @@ function renderCircles(letter, handleCircleClick) {
{[...Array(5)].map((_, index) => (
<div
key={index}
onClick={() => handleCircleClick(Object.keys(levelMappings)[index])}
onClick={() => handleCircleClick(skillLevels[index])}
className={`justify-self-center w-3.5 h-3.5 rounded-full
${index < numberOfFilledCircles ? 'bg-red-300 hover:bg-red-700' : 'bg-gray-500 hover:bg-gray-600'}`}
/>
@ -27,16 +28,19 @@ function renderCircles(letter, handleCircleClick) {
};
const SelectedSkill = ({ skill, letter, onLevelChange, removeSkillFromList, formData }) => {
const handleCircleClick = (levelIndex) => {
const letter = Object.keys(levelMappings)[levelIndex];
onLevelChange(skill, levelIndex);
const SelectedSkill = ({ skill_name, skillId ,letter, onLevelChange, removeSkillFromList }) => {
const handleCircleClick = (newLetter) => {
// const letter = Object.keys(levelMappings)[levelIndex];
console.log(`Circle clicked: ${newLetter}`);
onLevelChange(skillId, newLetter);
};
return (
<div className="skill-block">
<div key={skill} className="relative selected-skill bg-slate-200 rounded-2xl h-20 w-56 p-2 mb-5">
<p className='text-slate-700 font-semibold text-[12px] text-center'>{skill}</p>
<button onClick={(e) => {e.preventDefault(); removeSkillFromList(skill)}} className="absolute top-1 right-5 text-black">
<div className="mr-12">
<div key={skillId} className="relative selected-skill bg-slate-200 rounded-2xl h-min w-full p-2 mb-5 m-2 ">
<div className='flex justify-center items-center'>
<span className='text-slate-700 font-semibold text-[12px] text-center w-48 '>{skill_name}</span>
</div>
<button onClick={(e) => {e.preventDefault(); removeSkillFromList(skill_name)}} className="absolute top-1 right-5 text-black">
&times;
</button>
<div className='h-0.5 w-full bg-dimWhite opacity-60 mt-1'></div>
@ -45,7 +49,6 @@ const SelectedSkill = ({ skill, letter, onLevelChange, removeSkillFromList, form
</div>
<p className='font-poppins font-semibold text-slate-700 text-center text-[12px] mt-1'>{levelMappings[letter]}</p>
</div>
</div>
);
};

View File

@ -0,0 +1,19 @@
import React from 'react';
import SkillRender from './SkillRender';
const SkillsList = ({skillData, skill_names}) => {
return (
<div className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3'>
{skillData.map((skillLevel) => {
const skill = skill_names.find((s) => s.id === parseInt(skillLevel.skill_id));
return skill ? (
<SkillRender key={skill.id} skill={skill.skill_name} level={skillLevel.skill_level} />
) : null;
})}
</div>
);
};
export default SkillsList;

View File

@ -0,0 +1,45 @@
import React from 'react'
const levelMappings = {
'N': 'Nice to have',
'B': 'Podstawowy',
'M': 'Średnio zaawansowany',
'A': 'Zaawansowany',
'E': 'Ekspert',
};
function renderCircles(letter) {
const level = levelMappings[letter];
const numberOfFilledCircles = Object.keys(levelMappings).indexOf(letter) + 1;
return (
<>
{[...Array(5)].map((_, index) => (
<div
key={index}
className={`justify-self-center w-3.5 h-3.5 rounded-full
${index < numberOfFilledCircles ? 'bg-red-300 hover:bg-red-700' : 'bg-gray-500 hover:bg-gray-600'}`}
/>
))}
</>
);
};
const SkillRender = ({key, skill, level,}) => {
return (
<div className='w-64 h-42 rounded-[15px] bg-slate-200 py-4 m-2 hover:bg-slate-300 drop-shadow-sm' key={key}>
<p className='text-center font-bold mb-1 px-2'>{skill}</p>
<div className='h-0.5 mx-auto w-52 mt-2 justify-center bg-dimWhite opacity-60'></div>
<div className='mt-3 grid grid-cols-5'>{renderCircles(level)}</div>
<p className='mt-3 text-xs text-center font-bold'>{levelMappings[level]}</p>
</div>
)
};
export default SkillRender;

View File

@ -1,142 +1,104 @@
import React, { useState, useEffect } from 'react';
import SelectedSkill from './SelectedSkill';
const initialSkillsList = [
'Inżynieria oprogramowania',
'Analiza danych',
'Projektowanie CAD',
'Automatyka',
'Robotyka',
'Inżynieria materiałowa',
'Inżynieria elektryczna',
'Inżynieria mechaniczna',
'Programowanie mikrokontrolerów',
'Analiza strukturalna',
'Modelowanie 3D',
'Projektowanie układów elektronicznych',
'Programowanie PLC',
'Sztuczna inteligencja',
'Machine learning',
'Inżynieria chemiczna',
'Inżynieria biomedyczna',
'Inżynieria środowiska',
'Automatyka przemysłowa',
'Zarządzanie projektami technicznymi',
'Prototypowanie',
'Przetwarzanie sygnałów',
'Inżynieria systemów',
'Analiza termodynamiczna',
'Fizyka stosowana',
'Inżynieria procesowa',
'Wytwarzanie addytywne',
'Technologia nanomateriałów',
'Projektowanie systemów wbudowanych',
'Inżynieria bezpieczeństwa',
];
import axios from 'axios';
const SkillsSelector = ({formData, setFormData}) => {
const SkillsSelector = ({ formData, setFormData, skills }) => {
const [inputValue, setInputValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const [selectedSkills, setSelectedSkills] = useState([]);
const [skill_levels, setskill_levels] = useState([]);
const handleInputChange = (e) => {
const value = e.target.value;
setInputValue(value);
if (value.length >= 0) {
setSuggestions(initialSkillsList.filter(skill =>
skill.toLowerCase().includes(value.toLowerCase())));
if (value.length > 0) {
setSuggestions(skills.filter(skill =>
skill.skill_name.toLowerCase().includes(value.toLowerCase())
));
} else {
setSuggestions([]);
}
};
const handleSuggestionClick = (skill) => {
if (!selectedSkills.includes(skill)) {
setSelectedSkills(prevSkills => [skill, ...prevSkills]);
};
setSkillLevels(prevLevels => ({
...prevLevels,
[skill]: 'N' // lub inny domyślny poziom
}));
// Handle click on suggestion
const handleSuggestionClick = (suggestion) => {
const skill = skills.find(skill => skill.skill_name === suggestion);
if (skill && !selectedSkills.some(selectedSkill => selectedSkill.id === skill.id)) {
setSelectedSkills(prevSkills => [...prevSkills, skill]);
setskill_levels(prevLevels => [...prevLevels, { skill_id: skill.id, skill_level: 'N' }]);
}
setInputValue('');
setSuggestions([]);
};
// Handle removal of a skill from the list
const removeSkillFromList = (skillToRemove) => {
setSelectedSkills(prevSkills => prevSkills.filter(skill => skill !== skillToRemove));
setSkillLevels(prevLevels => {
const newLevels = { ...prevLevels };
delete newLevels[skillToRemove];
return newLevels;
});
};
const [skillLevels, setSkillLevels] = useState({});
const handleLevelChange = (skill, newLevel) => {
setSkillLevels(prevLevels => ({ ...prevLevels, [skill]: newLevel }));
setSelectedSkills(prevSkills => prevSkills.filter(skill => skill.id !== skillToRemove.id));
setskill_levels(prevLevels => prevLevels.filter(level => level.skill_id !== skillToRemove.id));
};
// Handle skill level change
const handleLevelChange = (skillId, newLevel) => {
setskill_levels(prevLevels =>
prevLevels.map(level =>
level.skill_id === skillId ? { ...level, skill_level: newLevel } : level
)
);
};
// Update formData on skill_levels change
useEffect(() => {
// Aktualizacja formData, aby zawierała skillLevels
setFormData(prevFormData => ({
...prevFormData,
skillLevels: skillLevels
skill_levels
}));
}, [skillLevels]);
useEffect(() => {
// Jeśli formData zawiera już wybrane umiejętności i ich poziomy
if (formData.skillLevels) {
setSkillLevels(formData.skillLevels);
// Ustaw wybrane umiejętności na podstawie kluczy w formData.skillLevels
setSelectedSkills(Object.keys(formData.skillLevels));
}
}, []);
useEffect(() => {
console.log('Zaktualizowane poziomy umiejętności:', skillLevels);
}, [skillLevels, ]);
}, [skill_levels, setFormData]);
return (
<div className='relative mt-3 grid grid-cols-5'>
<div className='relative mt-3 grid grid-cols-2'>
<div className='col-span-5 grid mb-4'>
<div>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="Wpisz umiejętność"
className="selected-skill bg-slate-200 rounded-2xl h-20 w-56 py-2 px-4 text-slate-700 font-semibold text-[12px] col-span-3"
/>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="Wpisz umiejętność..."
className="selected-skill bg-slate-200 rounded-2xl h-20 w-full
sm:w-56 py-2 px-4 text-slate-700 font-semibold text-[16px]
col-span-3"
/>
{inputValue.length >= 1 && suggestions.length > 0 && (
<div className={`absolute border-4 w-56 top-16 z-10 overflow-y-auto overflow-x-hidden ${suggestions.length > 6 ? 'h-52' : 'h-min' }`}>
{suggestions.map(suggestion => (
<div
key={suggestion.id}
onClick={() => handleSuggestionClick(suggestion.skill_name)}
className="w-56 h-max px-3 py-1 bg-slate-300 border-b-2 cursor-pointer"
>
<p className='text-slate-700 font-semibold text-[12px]'>{suggestion.skill_name}</p>
</div>
))}
</div>
)}
</div>
{inputValue.length >= 1 && suggestions.length > 0 && (
<div className="absolute border-4 w-56 top-16 z-10 overflow-y-auto overflow-x-hidden m" >
{suggestions.map(suggestion => (
<div
key={suggestion}
onClick={() => handleSuggestionClick(suggestion)}
className="w-56 h-max px-3 py-1 bg-slate-300 border-b-2 cursor-pointer"
>
<p className='text-slate-700 font-semibold text-[12px]'> {suggestion}</p>
</div>
))}
</div>
)}
</div>
{/* Lista wybranych umiejętności z możliwością określenia poziomu */}
<div className='col-span-5 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3'>
{selectedSkills.map(skill => (
<SelectedSkill
key={skill}
skill={skill}
letter={skillLevels[skill] || 'N'} // Domyślny poziom, jeśli nie ustawiony
onLevelChange={handleLevelChange}
removeSkillFromList={removeSkillFromList}
key={skill.id}
skill_name={skill.skill_name}
skillId={skill.id}
letter={skill_levels.find(
level => level.skill_id === skill.id)?.skill_level || 'N'}
onLevelChange={handleLevelChange}
removeSkillFromList={() => removeSkillFromList(skill)}
/>
))}
</div>
</div>
);
};
export default SkillsSelector;
export default SkillsSelector;

View File

@ -76,7 +76,7 @@ const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
</div>
<div className="col-span-1 mt-4 mx-2">
<div className={`${styles.paragraph} px-4 py-2`}>
Nazwa firmy
Nip firmy
<span className={`${styles.paragraph} text-red-700`}>*</span>
</div>
<input

View File

@ -2,8 +2,12 @@ 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'
const StepThreeJoblisting = ({ formData, prevStep }) => {
import SkillsList from './SkillList';
const StepThreeJoblisting = ({ formData, prevStep, skills, handleSubmit }) => {
// Funkcja do przetwarzania HTML i dodawania klas
const processHTML = (htmlString) => {
const parser = new DOMParser();
@ -33,62 +37,68 @@ const StepThreeJoblisting = ({ formData, prevStep }) => {
return <div dangerouslySetInnerHTML={{ __html: cleanAndProcessData(htmlString) }} />;
};
const skillData = Object.entries(formData.skillLevels).map(([skill_id, skill_level]) => {
return {skill_id, skill_level, jobposting_id: null};
});
useEffect(()=>{
console.log(skillData)
}, [skillData,])
return (
<div className='text-center'>
<p className='mt-8 text-[24px] font-poppins font-bold '>
Wybrałeś opcję {formData.posting_option}
</p>
<TextDivider text={`Tak będzie wyglądało Twoje ogłoszenie na liście ogłoszeń`} />
<div className={`grid grid-cols-4`}>
<div
className={`job-listing col-start-2 col-end-4 grid grid-cols-4 drop-shadow cursor-pointer mb-2 sm:mr-4 mr-0 text-xl font-bold border rounded-[10px] p-2 hover:border-l-8 hover:border-zinc-500 duration-300 bg-gray-200`}
>
<p className='col-span-3 text-xl text-left tracking-wide'>F.H.U. Januszex</p>
<p className='place-self-end text-[16px] font-semibold tracking-widest text-slate-800'>12000 PLN - 15000 PLN</p>
<p className='col-span-4 text-xl text-left tracking-wide'>Starszy Inżynier ds. ciągłości produkcji</p>
</div>
<div
className={`col-start-2 col-end-4 grid grid-cols-4 drop-shadow cursor-pointer mb-2 sm:mr-4 mr-0 text-xl font-bold border rounded-[10px] p-2 hover:border-l-8 hover:border-zinc-500 duration-300 bg-gray-200`}
>
<p className='col-span-3 text-base text-left tracking-wide'>{cleanAndProcessData(formData.company_name)}</p>
{
formData.requiresalary && (<p className='place-self-end text-[16px] font-semibold tracking-widest text-slate-800'>{cleanAndProcessData(formData.minsalary)} PLN - {cleanAndProcessData(formData.maxsalary)} PLN</p> )
}
{
!formData.requiresalary && (<p className='place-self-end text-xl text-slate-800'></p> )
}
<p className='col-span-4 text-xl text-left'>{cleanAndProcessData(formData.name)}</p>
</div>
<div
className={`job-listing2 col-start-2 col-end-4 grid grid-cols-4 drop-shadow cursor-pointer mb-2 sm:mr-4 mr-0 text-xl font-bold border rounded-[10px] p-2 hover:border-l-8 hover:border-zinc-500 duration-300 bg-gray-200`}
>
<p className='col-span-3 text-xl text-left tracking-wide'>Papieżeks Sp. z o. o.</p>
<p className='place-self-end text-[16px] font-semibold tracking-widest text-slate-800'>9000 PLN - 12000 PLN</p>
<p className='col-span-4 text-xl text-left tracking-wide'>Konstruktor - inżynier mechanik</p>
</div>
<ListingSmall
id={9999}
name={'Starszy Inżynier ds. ciągłości produkcji'}
company_name={'F.H.U. Januszex'}
min_salary={'12000'}
max_salary={'15000 PLN'}
image={IzaacLOGO}
require_salary={true}
index={0}
onClick={() => {}}
_class={`col-start-2 col-end-4 job-listing`}
/>
<ListingSmall
id={999999}
name={formData.name}
company_name={formData.company_name}
min_salary={formData.min_salary}
max_salary={formData.max_salary}
requiresalary={formData.require_salary}
image={formData.image}
index={0}
onClick={() => {}}
_class={`col-start-2 col-end-4`}/>
<ListingSmall
id={99999}
name={'Konstruktor - inżynier mechanik'}
company_name={'Papieżeks Sp. z o. o.'}
image={IzaacLOGO}
min_salary={'9000'}
max_salary={'12000'}
requiresalary={true}
index={1}
onClick={() => {}}
_class={`col-start-2 col-end-4 job-listing2`} />
</div>
<TextDivider text={`Tak będzie się prezentowała treść Twojego ogłoszenia`} />
<div className='grid grid-cols-5 mt-4 mb-4'>
<div className='col-span-3 col-start-2 bg-gray-100 text-justify text-[18px] leading-[1.75rem] p-4 rounded-xl'>
<SkillsList skillData={formData.skill_levels} skill_names={skills}/>
{renderContent(formData.content)}
</div>
</div>
<button
type="button"
onClick={prevStep}
className='h-12 w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] text-white hover:scale-125 duration-300 mb-5'>
className='h-12 w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] text-white hover:scale-125 duration-300 mb-5 mx-12'>
<span className='text-[18px]'></span>&nbsp;&nbsp;Przejdź do poprzedniego kroku
</button>
<button
type="button"
onClick={handleSubmit}
className='h-12 w-72 rounded-xl bg-green-700 font-poppins font-semibold text-[14px] text-white hover:scale-125 duration-300 mb-5 mx-12'>
Dodaj ogłoszenie!&nbsp;&nbsp;<span className='text-[18px]'></span>
</button>
</div>
);
}

View File

@ -5,9 +5,13 @@ import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import ImageUpload from './ImageUpload';
import SkillsSelector from './SkillsSelector';
import Salary from './Salary';
import { placeholderImage } from '../assets';
const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setFormData, removeFields }) => {
import { categories, experience_levels } from '../consts';
const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setFormData, removeFields, skills }) => {
const [editorData, setEditorData] = useState('');
const [imageSrc, setImageSrc] = useState(placeholderImage);
const handleEditorChange = (event, editor) => {
const data = editor.getData();
setEditorData(data);
@ -39,29 +43,29 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
if (!formData.content) {
newErrors.content = 'To pole jest wymagane';
}
console.log(formData.requireSalary)
if (formData.requireSalary === true)
console.log(formData.require_salary)
if (formData.require_salary === true)
{
console.log(formData.requireSalary)
if (!formData.minsalary) {
newErrors.minsalary = 'To pole jest wymagane';
console.log(formData.require_salary)
if (!formData.min_salary) {
newErrors.min_salary = 'To pole jest wymagane';
}
if (!formData.maxsalary) {
newErrors.maxsalary = 'To pole jest wymagane';
if (!formData.max_salary) {
newErrors.max_salary = 'To pole jest wymagane';
}
}
if (!formData.workFromHome) {
newErrors.workFromHome = 'To pole jest wymagane';
if (!formData.work_from_home) {
newErrors.work_from_home = 'To pole jest wymagane';
}
if (!formData.employmentType) {
newErrors.employmentType = 'To pole jest wymagane';
if (!formData.employment_type) {
newErrors.employment_type = 'To pole jest wymagane';
}
if (!formData.image) {
newErrors.image = 'To pole jest wymagane';
}
if (Object.keys(formData.skillLevels).length === 0) {
newErrors.skillLevels = 'To pole jest wymagane';
if (Object.keys(formData.skill_levels).length === 0) {
newErrors.skill_levels = 'To pole jest wymagane';
}
setErrors(newErrors);
@ -82,7 +86,7 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
<div className='mx-auto max-w-none h-[70vh] w-[60vw]'>
<form action="">
<div className='grid mb-4'>
<div className='grid grid-cols-3 items-center'>
<div className='grid grid-cols-1 sm:grid-cols-3 items-center'>
<div className={`${styles.paragraph} px-4 py-2`}>Nazwa ogłoszenia
<span className={`${styles.paragraph} text-red-700`}>*</span></div>
<input type="text"
@ -119,6 +123,8 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
<ImageUpload
setFormData={setFormData}
data={formData}
_setImageSrc={setImageSrc}
_imageSrc={imageSrc}
/>
</div>
</div>
@ -151,10 +157,41 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
<SkillsSelector
formData={formData}
setFormData={setFormData}
skills={skills}
/>
</div>
</div>
<div className='w-full border-b-2'></div>
<div className={`${styles.paragraph} px-4 py-2`}>Kategoria ogłoszenia i poziom doświadczenia
<span className={`${styles.paragraph} text-red-700`}>*</span></div>
<div className='grid grid-cols-2 items-center mt-4 mb-4 gap-6'>
<p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Wybierz kategorię Twojego ogłoszenia</p>
<select
value={formData['category'] || 'default' }
name='category'
onChange={handleChange('category')}
className='h-12 border-2 rounded-xl px-5 mx-4 w-full '>
<option value={'default'} disabled >Wybierz opcję</option>
{categories.map((category, index) => (
<option key={index} value={category.id}>{category.name}</option>
))}
</select>
<p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Poziom doświadczenia</p>
<select
value={formData['experience_level'] || 'default' }
name='experience_level'
onChange={handleChange('experience_level')}
className='h-12 border-2 rounded-xl px-5 mx-4 w-full '>
<option value={'default'} disabled >Wybierz opcję</option>
{experience_levels.map((experience_level, index) => (
<option key={index} value={experience_level.id}>{experience_level.name}</option>
))}
</select>
<div>
</div>
</div>
<div className='w-full border-b-2'></div>
<div className={`${styles.paragraph} px-4 py-2`}>
Wynagrodzenie
<span className={`${styles.paragraph} text-red-700`}>*</span>

View File

@ -0,0 +1,20 @@
import React from 'react'
import TextDivider from './TextDivider'
const Success = () => {
return (
<>
<TextDivider text={`Twoje ogłoszenie zostało dodane!`} />
<div className='text-center'>
<p className='mt-8 text-[24px] font-poppins font-bold '>
Dziękujemy za dodanie ogłoszenia
</p>
<p className='mt-8 text-[16px] font-poppins font-bold '>
Twoje ogłoszenie zostanie opublikowane po weryfikacji
</p>
</div>
</>
)
}
export default Success

View File

@ -1,7 +1,14 @@
import React from 'react'
import { ogloszenia } from '../consts'
import { useState } from 'react';
import { useState, useEffect } from 'react';
import ListingSmall from './ListingSmall';
import SkillRender from './SkillRender';
import Search from './Search';
import Filter from './Filter';
import Category from './Category';
import JobOfferContent from './JobOfferContent';
import axios from 'axios';
function renderCircles(level) {
let numberOfFilledCircles;
@ -32,8 +39,49 @@ function renderCircles(level) {
};
const WorkApp = () => {
const [isDetailsVisible, setIsDetailsVisible] = useState(false);
// Czy szczegóły są widoczne
const [isOpen, setIsOpen] = useState(false);
// Funkcja pobierająca dane z API
const [ogloszenia, setOgloszenia] = useState([]);
const [skills, setSkills] = useState([]);
const getOgloszenia = async () => {
try {
const response = await axios.get('http://izaac.izaac.pl/api/jobposting/joboffers_list/');
const data = response.data;
setOgloszenia(data);
// console.log(data);
if (data.length > 0) {
setSelectedOgloszenie(data[0]);
// console.log(data[0])
}
} catch (error) {
console.error(error);
}
};
const getSkills = async () => {
try {
const response = await axios.get(`http://izaac.izaac.pl/api/jobposting/skills/`);
const data = response.data;
setSkills(data);
console.log(data);
} catch (error) {
console.error(error);
}
};
const [searchQuery, setSearchQuery] = useState('');
const [selectedOgloszenie, setSelectedOgloszenie] = useState(ogloszenia[0]);
const [isDetailsVisible, setIsDetailsVisible] = useState(false); // Dodany stan
useEffect(() => {
getOgloszenia();
getSkills();
}, []);
const handleOgloszenieClick = (ogloszenie) => {
setSelectedOgloszenie(ogloszenie);
@ -46,47 +94,80 @@ const WorkApp = () => {
};
return (
<section className=' mx-auto bg-white w-full'>
{isDetailsVisible && (
<div className='grid grid-cols-6'>
<button
onClick={handleBackToList}
className="sm:hidden block font-poppins text-white font-semibold bg-gray-500 px-4 py-2 rounded-md mx-4 col-span-4 col-start-2"
>
Powrót do listy
</button>
</div>
)}
<div className='flex-grow h-5/6 grid grid-cols-10 '>
<div className={`rounded-xl col-span-11 sm:col-span-4 bg-gray-100 overflow-y-auto p-2 my-4 mx-2 sm:h-[84vh] h-[84vh] ${isDetailsVisible ? 'hidden sm:block' : 'block'}`}>
<div className='flex-grow h-5/6 grid grid-cols-10'>
<div className='px-3 col-span-11 flex flex-wrap items-center place-content-center '>
<Category name='Budownictwo' />
<Category name='Elektryk' />
<Category name='Okretownictwo' />
<Category name='Energetyka' />
<Category name='Lorem Ipsum' />
<Category name='IT'/>
<Category name='Budownictwo'
last={true} />
</div>
<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] ${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'>Oferty pracy</h1>
<button
className='block font-poppins text-white font-semibold
bg-gray-500 px-4 py-2 mx-6 rounded-md'
onClick={() => setIsOpen(!isOpen)}
>Pokaż 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>
{ogloszenia.map((ogloszenie, index) => (
<div
key={ogloszenie.id}
onClick={() => handleOgloszenieClick(ogloszenie)}
className={`grid grid-cols-4 drop-shadow cursor-pointer mb-2 sm:mr-4 mr-0 text-xl font-bold border rounded-[10px] p-2 hover:border-l-8 hover:border-zinc-500 duration-300 ${index % 2 === 0 ? 'bg-gray-200' : 'bg-gray-300'}`}
>
<p className='col-span-3 text-sm'>{ogloszenie.company_name}</p>
<p className='place-self-end text-sm text-slate-800'>{ogloszenie.salaryRange}</p>
<p className='col-span-4'>{ogloszenie.name}</p>
</div>
<ListingSmall
key={ogloszenie.id}
name={ogloszenie.name}
company_name={ogloszenie.company_name}
min_salary={ogloszenie.min_salary}
max_salary={ogloszenie.max_salary}
require_salary={ogloszenie.require_salary}
image={ogloszenie.image}
index={index}
onClick={() => handleOgloszenieClick(ogloszenie)}
/>
))}
</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 ${isDetailsVisible ? 'block' : 'hidden sm:block'}`}>
<div className='grid sm:grid-cols-2 md:grid-cols-5 grid-cols-2'>
{isDetailsVisible && (
<div className='grid grid-cols-6'>
<button
onClick={handleBackToList}
className="sm:hidden block font-poppins text-white font-semibold bg-gray-500 px-4 py-2 rounded-md mx-4 col-span-4 col-start-2"
>
Powrót do listy
</button>
</div>
)}
{selectedOgloszenie
&&
<JobOfferContent
id={selectedOgloszenie.id}
skills={skills}
/>}
{/* <div className='grid sm:grid-cols-2 md:grid-cols-5 grid-cols-2'>
{Object.entries(selectedOgloszenie.neededSkills).map(([skill, level]) => (
<div className='col-span-1 w-36 h-30 rounded-[15px] bg-slate-300 py-4 pl-3 m-2 hover:bg-slate-200' key={skill}>
<p className='font-bold mb-1 '>{skill}</p>
{renderCircles(level)}
<p className='text-xs font-bold'>{level.toLowerCase()}</p>
</div>
<SkillRender
skill={skill}
level={level}
/>
))}
</div>
<div className='text-slate-800 font-medium mt-4'>
{selectedOgloszenie.tresc}
</div>
</div> */}
</div>
</div>
</section>
@ -94,3 +175,4 @@ const WorkApp = () => {
}
export default WorkApp
export { renderCircles }

View File

@ -13,7 +13,8 @@ export const ogloszenia = [
"Coś innego": "Zaawansowany",
"Papieżworks": "Podstawowy",
"Budowa papieży": "Ekspert",
}
},
"requiresalary": true
},
{
"id": "2",
@ -26,7 +27,8 @@ export const ogloszenia = [
"Elektronika": "Ekspert",
"PLC": "Zaawansowany",
"Automatyka przemysłowa": "Podstawowy"
}
},
"requiresalary": false
},
{
"id": "3",
@ -39,7 +41,8 @@ export const ogloszenia = [
"AutoCAD": "Ekspert",
"Zarządzanie projektem": "Zaawansowany",
"Nadzór budowlany": "Zaawansowany"
}
},
"requiresalary": true
},
{
"id": "4",
@ -52,7 +55,8 @@ export const ogloszenia = [
"AutoCAD": "Zaawansowany",
"SolidWorks": "Podstawowy",
"Budowa statków": "Ekspert"
}
},
"requiresalary": true
},
{
"id": "5",
@ -65,7 +69,8 @@ export const ogloszenia = [
"Elektronika": "Ekspert",
" PLC": "Zaawansowany",
"Automatyka przemysłowa": "Podstawowy"
}
},
"requiresalary": true
},
{
"id": "6",
@ -78,7 +83,8 @@ export const ogloszenia = [
"AutoCAD": "Ekspert",
"Zarządzanie projektem": "Zaawansowany",
"Nadzór budowlany": "Zaawansowany"
}
},
"requiresalary": true
},
{
"id": "7",
@ -91,7 +97,8 @@ export const ogloszenia = [
"AutoCAD": "Zaawansowany",
"SolidWorks": "Podstawowy",
"Budowa statków": "Ekspert"
}
},
"requiresalary": true
},
{
"id": "8",
@ -102,10 +109,12 @@ export const ogloszenia = [
"lokalizacja": "Gdańsk, Poland",
"neededSkills": {
"Elektronika": "Ekspert",
" PLC": "Zaawansowany",
"PLC": "Zaawansowany",
"Automatyka przemysłowa": "Podstawowy"
}
},
},
"requiresalary": true
}
,
{
"id": "9",
"company_name": "Kompania S. A",
@ -117,7 +126,8 @@ export const ogloszenia = [
"AutoCAD": "Ekspert",
"Zarządzanie projektem": "Zaawansowany",
"Nadzór budowlany": "Zaawansowany"
}
},
"requiresalary": true
},
{
"id": "10",
@ -130,7 +140,8 @@ export const ogloszenia = [
"AutoCAD": "Zaawansowany",
"SolidWorks": "Podstawowy",
"Budowa statków": "Ekspert"
}
},
"requiresalary": true
},
{
"id": "11",
@ -141,64 +152,13 @@ export const ogloszenia = [
"lokalizacja": "Gdańsk, Poland",
"neededSkills": {
"Elektronika": "Ekspert",
" PLC": "Zaawansowany",
"PLC": "Zaawansowany",
"Automatyka przemysłowa": "Podstawowy"
}
},
{
"id": "12",
"company_name": "Kompania S. A",
"name": "Inżynier budowlany",
"tresc": "Zapraszamy do aplikowania osoby z doświadczeniem w nadzorowaniu i zarządzaniu dużymi projektami budowlanymi.",
"salaryRange": "14,000 - 22,000 PLN",
"lokalizacja": "Gdańsk, Poland",
"neededSkills": {
"AutoCAD": "Ekspert",
"Zarządzanie projektem": "Zaawansowany",
"Nadzór budowlany": "Zaawansowany"
}
},
{
"id": "13",
"company_name": "Kompania S. A",
"name": "Inżynier mechanik w branży okrętowniczej",
"tresc": "Poszukujemy doświadczonego inżyniera mechanika specjalizującego się w budowie i konserwacji statków. Wymagane minimum 5 lat doświadczenia w branży okrętowniczej.",
"salaryRange": "10,000 - 15,000 PLN",
"lokalizacja": "Gdańsk, Poland",
"neededSkills": {
"AutoCAD": "Zaawansowany",
"SolidWorks": "Podstawowy",
"Budowa statków": "Ekspert"
}
},
{
"id": "14",
"company_name": "Kompania S. A",
"name": "Inżynier elektryk w branży energetycznej",
"tresc": "Szukamy inżyniera elektryka z doświadczeniem w projektowaniu i implementacji systemów energetycznych.",
"salaryRange": "12,000 - 18,000 PLN",
"lokalizacja": "Gdańsk, Poland",
"neededSkills": {
"Elektronika": "Ekspert",
" PLC": "Zaawansowany",
"Automatyka przemysłowa": "Podstawowy"
}
},
{
"id": "15",
"company_name": "Kompania S. A",
"name": "Inżynier budowlany",
"tresc": "Zapraszamy do aplikowania osoby z doświadczeniem w nadzorowaniu i zarządzaniu dużymi projektami budowlanymi.",
"salaryRange": "14,000 - 22,000 PLN",
"lokalizacja": "Gdańsk, Poland",
"neededSkills": {
"AutoCAD": "Ekspert",
"Zarządzanie projektem": "Zaawansowany",
"Nadzór budowlany": "Zaawansowany"
}
},
//... Możesz dodać więcej ogłoszeń według tego wzoru
},
"requiresalary": true
}
];
export const linki = [
@ -250,4 +210,120 @@ export const linki_home = [
"name": "contact",
"title": "Kontakt",
},
]
export const categories = [
{
"id": "A",
"name": "Budownictwo",
},
{
"id": "B",
"name": "IT",
},
{
"id": "C",
"name": "Elektryka i Elektronika",
},
{
"id": "D",
"name": "Produkcja",
},
{
"id": "E",
"name": "Mechanika i konstrukcje",
},
{
"id": "F",
"name": "Chemia i Biotechnologia",
},
{
"id": "G",
"name": "Biomedyczne",
},
{
"id": "H",
"name": "Automatyka i Robotyka",
},
{
"id": "I",
"name": "Logistyka i Transport",
},
{
"id": "J",
"name": "Sprzedaż",
},
{
"id": "Z",
"name": "Inne",
},
]
export const experience_levels = [
{
"id": "A",
"name": "Stażysta",
},
{
"id": "B",
"name": "Junior",
},
{
"id": "C",
"name": "Mid",
},
{
"id": "D",
"name": "Senior",
},
{
"id": "E",
"name": "Lead",
},
{
"id": "F",
"name": "Manager",
},
{
"id": "G",
"name": "Inne",
},
]
export const work_from_home = [
{
"id": "wfh",
"name": "Praca zdalna",
},
{
"id": "hyb",
"name": "Hybrydowa",
},
{
"id": "off",
"name": "Stacjonarna",
},
]
export const employment_types = [
{
"id": "B2B",
"name": "Kontrakt B2B",
},
{
"id": "FT",
"name": "Umowa o pracę",
},
{
"id": "MC",
"name": "Umowa zlecenie",
},
{
"id": "CW",
"name": "Umowa o dzieło",
},
{
"id": "INT",
"name": "Staż",
},
]

View File

@ -25,6 +25,14 @@
@tailwind components;
@tailwind utilities;
#root {
margin: 0;
padding: 0;
height: 100vh;
overflow: auto;
}
:root {
--black-gradient: linear-gradient(
@ -38,6 +46,29 @@
* {
scroll-behavior: smooth;
}
.category-hover:hover {
transform: scale(1.1);
/* Apply negative margin if needed */
}
.slide-container {
overflow: hidden;
max-height: 0;
transition: max-height 0.5s ease-out, opacity 0.5s ease;
opacity: 0;
}
/* Expanded state styles */
.expanded {
max-height: fit-content; /* Adjust as needed */
opacity: 1;
}
.minus-z-index {
z-index: -10;
}
.editor-container {
margin-left: auto;
margin-right: auto;

View File

@ -1,7 +1,32 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// Dodaj to jeśli korzystasz z HTTPS i potrzebujesz ignorować błędy certyfikatu
// import https from 'https';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
server: {
proxy: {
'/api': {
target: 'http://izaac.izaac.pl',
changeOrigin: false,
secure: false, // Ustaw na true jeśli łączysz się przez HTTPS
// Jeśli twoje połączenie HTTPS wymaga niestandardowego certyfikatu:
// https: {
// agent: new https.Agent({
// rejectUnauthorized: false,
// }),
// },
rewrite: (path) => path.replace(/^\/api/, ''),
configure: (proxy, options) => {
// Funkcja konfigurująca, gdzie możesz dodać dodatkowe nagłówki
proxy.on('proxyReq', function(proxyReq, req) {
// Dodaj tutaj swój nagłówek autoryzacyjny
proxyReq.setHeader('Authorization', 'Basic cnJnTkxTRXFsY2w0NVJWTVFhMEx4UUxPSE9nWjJMN1psR3BYVXJDcDpsS3NtcmFlU21paFk4clRRZ2Q4VFRHSW5jVWxvVzdYb2tSOFdMSTBvWnE4akNKNTlndUFrb3BnOVpPVWVYRmR5cnF3dUxpNlR5WExaSkRwMUtCOERXRXVzMDV0dFMzTlFIb0x2ZlJvT1F0SnBRYXh6eUlvODVveWtqUW4yNUtlYg==');
});
},
},
},
},
});