75 Commits

Author SHA1 Message Date
16a5a1f548 partner request design 2024-05-03 19:23:46 +03:00
a763c5a69a partner request design 2024-05-03 19:23:15 +03:00
a7c58b2609 tracker tasks 2024-05-03 14:26:36 +03:00
3b8ec8e100 tracker tasks 2024-05-03 14:26:09 +03:00
94136c97e6 ProfileCalendar 2024-05-01 14:54:53 +03:00
4810412bd7 layout of the outstaffing page 2024-05-01 14:53:28 +03:00
3f46d60720 fix candidate resume 2024-04-24 19:20:21 +03:00
d49a2eb0c8 Merge pull request 'landing' (#36) from landing into main
Reviewed-on: #36
2024-04-22 19:03:38 +03:00
8de343016f stack steps 2024-04-22 19:03:00 +03:00
470bf6ec67 stack steps 2024-04-22 19:02:17 +03:00
dea1eb6104 resolve 2024-04-22 17:03:14 +03:00
ee20b49993 fix total hours 2024-04-22 17:01:37 +03:00
0e04e19c1b total day hours 2024-04-22 17:00:37 +03:00
bd4bfacd66 Add Cards in Profile 2024-04-22 16:19:40 +03:00
1bdabd32bf Merge pull request 'landing' (#35) from landing into main
Reviewed-on: #35
2024-04-22 16:04:48 +03:00
2d861de330 Merge pull request 'fixed/components' (#34) from fixed/components into main
Reviewed-on: #34
2024-04-22 14:05:18 +03:00
60aa8c7301 Merge branch 'main' of https://git.itguild.info/apuc/guild_front into fixed/components 2024-04-18 18:11:40 +03:00
13df697614 stack projects 2024-04-18 16:42:54 +03:00
79299d1177 stack projects 2024-04-18 16:42:18 +03:00
68a2df23c4 Merge pull request 'landing' (#33) from landing into main
Reviewed-on: #33
2024-04-18 15:27:03 +03:00
291e6a4a5d landing footer 2024-04-17 18:58:54 +03:00
0d436e71e4 landing footer 2024-04-17 18:58:39 +03:00
7a55188904 stack 2024-04-17 18:54:45 +03:00
3e80159c44 stack 2024-04-17 18:51:27 +03:00
363d43e04c Merge pull request 'landing' (#32) from landing into main
Reviewed-on: #32
2024-04-16 17:04:03 +03:00
a1da184bb0 Merge branch 'main' into landing
# Conflicts:
#	src/pages/roles/GuestPage.jsx
2024-04-16 17:02:36 +03:00
066cd569d3 landing 2024-04-16 17:01:13 +03:00
86a784bee2 landing 2024-04-16 17:00:44 +03:00
adf6ff4a1d Start fix table 2024-04-15 22:34:33 +03:00
9ffb981b67 Merge pull request 'fixed/components' (#31) from fixed/components into main
Reviewed-on: #31
2024-04-15 21:11:57 +03:00
0860591d28 Merge branch 'main' of https://git.itguild.info/apuc/guild_front into fixed/components 2024-04-15 21:09:17 +03:00
4a20d5e4fa WelcomePage fix 2024-04-15 16:21:41 +03:00
1434792d42 Fixed tracker card 2024-04-11 22:24:08 +03:00
ade31b767a Merge branch 'main' of https://git.itguild.info/apuc/guild_front into fixed/components 2024-04-11 21:06:34 +03:00
fafab29d25 Add welcome to the registration 2024-04-11 16:22:03 +03:00
10ffb1f7f1 Create new component TrackerTagList 2024-04-09 21:09:06 +03:00
75cf13d945 Merge branch 'main' of https://git.itguild.info/apuc/guild_front into fixed/components 2024-04-08 21:17:56 +03:00
a3e39b75d8 Create new component 2024-04-08 21:05:33 +03:00
271374b6c6 + 2024-04-08 04:32:56 +03:00
811ef4f69d remake layout report for day 2024-04-08 04:30:59 +03:00
c532302f73 Delete new component 2024-04-05 22:09:20 +03:00
36bdaf15e5 Merge branch 'main' of https://git.itguild.info/apuc/guild_front into fixed/components 2024-04-05 21:35:19 +03:00
7400319650 Merge pull request 'partner categories table' (#30) from table-pagination into main
Reviewed-on: #30
2024-04-05 16:14:55 +03:00
7580006e05 partner categories table 2024-04-05 16:11:46 +03:00
93d5ab015a Merge branch 'main' of https://git.itguild.info/apuc/guild_front 2024-04-04 17:32:36 +03:00
90274e8b17 Redesign calendar 2024-04-04 17:31:28 +03:00
9f98a0fea2 start fix tracker 2024-04-02 20:49:52 +03:00
1e950e9de0 Merge branch 'main' of https://git.itguild.info/apuc/guild_front into fixed/components 2024-04-02 20:04:21 +03:00
a3bcfc7cf7 fix 2024-04-02 18:29:29 +03:00
140d4689fe fix project cards 2024-04-02 18:29:03 +03:00
b19e00d98f Merge branch 'main' of https://git.itguild.info/apuc/guild_front into main 2024-04-02 17:48:38 +03:00
3580e25457 Fixed modal 2024-04-01 20:41:29 +03:00
9a0364e686 com 2024-03-31 20:40:54 +03:00
3e54f947b4 Merge pull request 'table-pagination' (#29) from table-pagination into main
Reviewed-on: #29
2024-03-31 20:39:59 +03:00
35f76abe60 all tasks table 2024-03-31 20:38:16 +03:00
caaee74810 all tasks table 2024-03-31 20:38:01 +03:00
c9dafd95a9 layout tracker 2024-03-22 16:31:59 +03:00
c14590a0cb Editing columns, fix addition of participants,
commenting catalog
2024-03-20 12:59:52 +03:00
5ad50a8ed0 Merge branch 'main' of https://git.itguild.info/apuc/guild_front 2024-03-19 16:58:36 +03:00
56344fa26e fix naming 2024-03-19 16:58:26 +03:00
f76e448951 commuit 2024-03-19 16:45:30 +03:00
42b597d585 Merge pull request 'error boundary and table pagination' (#28) from table-pagination into main
Reviewed-on: #28
2024-03-19 16:41:59 +03:00
77628b0de4 error boundary and table pagination 2024-03-19 16:41:33 +03:00
6e6d3d1823 Merge pull request 'table-pagination' (#27) from table-pagination into main
Reviewed-on: #27
2024-03-19 16:40:15 +03:00
2a5991da1f error boundary and table pagination 2024-03-19 16:39:36 +03:00
6afccb19d8 error boundary and table pagination 2024-03-19 16:39:00 +03:00
43ca1b54f1 small fix 2024-03-19 15:27:28 +03:00
923a84e488 remove tracker registration 2024-03-19 14:07:41 +03:00
3013dd1bd2 fix tracker landing redirect 2024-03-19 11:51:27 +03:00
882bdfcc24 small fixes 2024-03-18 18:47:31 +03:00
f180586eb3 Merge pull request 'routes' (#26) from routes into main
Reviewed-on: #26
2024-03-15 18:22:17 +03:00
78f2b34810 guardian routes 2024-03-15 18:19:48 +03:00
f2ad6b43bd guardian routes 2024-03-15 18:18:10 +03:00
727d55798a pop-up notifications 2024-03-12 16:14:44 +03:00
8797ba0778 Merge pull request 'auth-check' (#25) from auth-check into main
Reviewed-on: #25
2024-03-11 18:25:06 +03:00
114 changed files with 5156 additions and 3322 deletions

918
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,50 +10,9 @@ import {
import { getNotification } from "@redux/outstaffingSlice"; import { getNotification } from "@redux/outstaffingSlice";
import { Article } from "@pages/Article/Article"; import { MainPage } from "@pages/MainPage/MainPage";
import { Auth } from "@pages/Auth/Auth";
import { AuthForCandidate } from "@pages/AuthForCandidate/AuthForCandidate";
import { Blog } from "@pages/Blog/Blog";
import CatalogSpecialists from "@pages/CatalogSpecialists/CatalogSpecialists";
import { CompanyInfo } from "@pages/CompanyInfo/CompanyInfo";
import { FormPage } from "@pages/FormPage/FormPage";
import { Forms } from "@pages/Forms/Forms";
import { FrequentlyAskedQuestion } from "@pages/FrequentlyAskedQuestion/FrequentlyAskedQuestion";
import { FrequentlyAskedQuestions } from "@pages/FrequentlyAskedQuestions/FrequentlyAskedQuestions";
import { Home } from "@pages/Home/Home";
import { PartnerAddRequest } from "@pages/PartnerAddRequest/PartnerAddRequest";
import { PartnerBid } from "@pages/PartnerBid/PartnerBid";
import { PartnerEmployeeReport } from "@pages/PartnerEmployeeReport/PartnerEmployeeReport";
import { PartnerEmployees } from "@pages/PartnerEmployees/PartnerEmployees";
import { PartnerRequests } from "@pages/PartnerRequests/PartnerRequests";
import { PartnerSettings } from "@pages/PartnerSettings/PartnerSettings";
import { PartnerTreaties } from "@pages/PartnerTreaties/PartnerTreaties";
import { PartnerCategories } from "@pages/PartnerСategories/PartnerСategories";
import { Payouts } from "@pages/Payouts/Payouts";
import { Profile } from "@pages/Profile/Profile";
import { ProfileCandidate } from "@pages/ProfileCandidate/ProfileCandidate";
import { ProjectTracker } from "@pages/ProjectTracker/ProjectTracker";
import { PassingTests } from "@pages/Quiz/PassingTests";
import { QuizPage } from "@pages/Quiz/QuizPage";
import { QuizReportPage } from "@pages/Quiz/QuizReportPage";
import { RegistrationForCandidate } from "@pages/RegistrationForCandidate/RegistrationForCandidate";
import { RegistrationSetting } from "@pages/RegistrationSetting/RegistrationSetting";
import { SingleReportPage } from "@pages/SingleReportPage/SingleReportPage";
import Statistics from "@pages/Statistics/Statistics";
import { Summary } from "@pages/Summary/Summary";
import { Tracker } from "@pages/Tracker/Tracker";
import { TrackerAuth } from "@pages/TrackerAuth/TrackerAuth";
import { TrackerIntro } from "@pages/TrackerIntro/TrackerIntro";
import { TrackerRegistration } from "@pages/TrackerRegistration/TrackerRegistration";
import { ViewReport } from "@pages/ViewReport/ViewReport";
import { Calendar } from "@components/Calendar/Calendar";
import { Candidate } from "@components/Candidate/Candidate";
import { FreeDevelopers } from "@components/FreeDevelopers/FreeDevelopers";
import { TicketFullScreen } from "@components/Modal/Tracker/TicketFullScreen/TicketFullScreen";
import { Notification } from "@components/Notification/Notification"; import { Notification } from "@components/Notification/Notification";
import { ProfileCalendar } from "@components/ProfileCalendar/ProfileCalendar";
import { ReportForm } from "@components/ReportForm/ReportForm";
import "assets/fonts/stylesheet.css"; import "assets/fonts/stylesheet.css";
import "assets/global.scss"; import "assets/global.scss";
@ -64,109 +23,7 @@ const App = () => {
<> <>
<Router> <Router>
<Routes> <Routes>
<Route exact path="/auth" element={<Auth />} /> <Route path="*" element={<MainPage />} />
<Route exact path="/tracker-intro" element={<TrackerIntro />} />
<Route exact path="/tracker-auth" element={<TrackerAuth />} />
<Route exact path="/forms" element={<Forms />} />
<Route
exact
path="/tracker-registration"
element={<TrackerRegistration />}
/>
<Route exact path="/company" element={<CompanyInfo />} />
<Route
exact
path="/registration-setting"
element={<RegistrationSetting />}
/>
<Route
exact
path="/catalog-specialists"
element={<CatalogSpecialists />}
/>
<Route exact path="/worker/:id" element={<FreeDevelopers />} />
<Route
exact
path="/tracker/task/:id"
element={<TicketFullScreen />}
></Route>
<Route
exact
path="/tracker/project/:id"
element={<ProjectTracker />}
/>
<Route exact path="/auth-candidate" element={<AuthForCandidate />} />
<Route
exact
path="/registration-candidate"
element={<RegistrationForCandidate />}
/>
<Route exact path="/blog" element={<Blog />}></Route>
<Route exact path="/blog/article/:id" element={<Article />}></Route>
<Route
exact
path="/frequently-asked-questions"
element={<FrequentlyAskedQuestions />}
/>
<Route
exact
path="/frequently-asked-question/:id"
element={<FrequentlyAskedQuestion />}
/>
<Route exact path="/candidate/:id" element={<Candidate />} />
<Route exact path="/candidate/:id/form" element={<FormPage />} />
<Route path="/:userId/calendar" element={<Calendar />} />
<Route path="/report/:id" element={<SingleReportPage />} />
<Route exact path="profile">
<Route index element={<Profile />} />
<Route exact path="catalog" element={<Home />} />
<Route exact path="calendar" element={<ProfileCalendar />} />
<Route exact path="calendar/report" element={<ReportForm />} />
<Route
exact
path="calendar/view/:date/:id"
element={<ViewReport />}
/>
<Route exact path="summary" element={<Summary />} />
<Route exact path="tracker" element={<Tracker />} />
<Route exact path="statistics/:id" element={<Statistics />} />
<Route exact path="payouts" element={<Payouts />} />
<Route exact path="settings" element={<PartnerSettings />} />
<Route exact path="requests" element={<PartnerRequests />} />
<Route exact path="requests-add" element={<PartnerAddRequest />} />
<Route exact path="requests-edit" element={<PartnerAddRequest />} />
<Route exact path="requests-bid" element={<PartnerBid />} />
<Route exact path="employees" element={<PartnerCategories />} />
<Route
exact
path="employees/report/:uuid"
element={<PartnerEmployeeReport />}
/>
<Route exact path="treaties" element={<PartnerTreaties />} />
<Route exact path="quiz">
<Route index element={<QuizPage />} />
<Route exact path="test/:uuid" element={<PassingTests />} />
<Route exact path="report/:uuid" element={<QuizReportPage />} />
</Route>
<Route
exact
path="categories/employees"
element={<PartnerEmployees />}
/>
</Route>
<Route exact path="profile-candidate/:id">
<Route index element={<ProfileCandidate />} />
</Route>
<Route path="*" element={<Navigate to="/auth" replace />} />
</Routes> </Routes>
</Router> </Router>
{notification.show && <Notification />} {notification.show && <Notification />}

View File

@ -41,7 +41,7 @@ export const apiRequest = (
if (response.data?.redirect || response.status === 401) { if (response.data?.redirect || response.status === 401) {
window.location.replace("/auth"); window.location.replace("/auth");
localStorage.clear(); localStorage.clear();
// dispatch(auth(false)); store.dispatch(auth(false));
store.dispatch(setProfileInfo({})); store.dispatch(setProfileInfo({}));
} }
return resolve(response); return resolve(response);

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -394,3 +394,16 @@
url('LabGrotesque-Light.ttf') format('truetype'); url('LabGrotesque-Light.ttf') format('truetype');
font-weight: 300; font-weight: 300;
} }
@font-face {
font-family: 'Geraspoheko';
src: url('GeraspohekoRegular.eot');
src: local('Geraspoheko'), local('GeraspohekoRegular'),
url('GeraspohekoRegular.eot?#iefix') format('embedded-opentype'),
url('GeraspohekoRegular.woff2') format('woff2'),
url('GeraspohekoRegular.woff') format('woff'),
url('GeraspohekoRegular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

View File

@ -0,0 +1,10 @@
<svg width="27" height="15" viewBox="0 0 27 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_76_12)">
<path d="M22.7685 8.75957H22.345C15.3781 8.75957 8.41109 8.75957 1.44414 8.75957C1.22469 8.76628 1.00541 8.7416 0.792717 8.68623C0.259454 8.5302 -0.0459255 8.00204 0.00624986 7.38338C0.0222895 7.10907 0.131776 6.84899 0.315899 6.64783C0.500021 6.44667 0.747298 6.31697 1.01524 6.28102C1.18606 6.26022 1.35806 6.2511 1.53009 6.25372C8.46839 6.25372 15.407 6.25372 22.3458 6.25372H22.7931C22.6734 6.12655 22.6066 6.05322 22.5376 5.98222C21.3043 4.73398 20.0705 3.48392 18.8362 2.23203C18.4764 1.86848 18.2991 1.45188 18.438 0.941657C18.4905 0.730456 18.5968 0.537048 18.7462 0.38089C18.8956 0.224732 19.0828 0.111333 19.289 0.052101C19.4952 -0.00713091 19.7131 -0.0101062 19.9208 0.043474C20.1284 0.0970542 20.3186 0.205301 20.472 0.357322C20.9393 0.80435 21.3828 1.27556 21.8355 1.73663C23.3982 3.32762 24.9604 4.9181 26.5221 6.50805C27.1582 7.15557 27.1597 7.85069 26.5275 8.4951C24.5402 10.5235 22.5445 12.5363 20.568 14.5725C19.717 15.4494 18.6574 14.8432 18.4418 14.0724C18.377 13.8673 18.3702 13.6478 18.4221 13.439C18.4741 13.2301 18.5827 13.0403 18.7357 12.8913C19.0672 12.541 19.4086 12.2 19.747 11.856L22.5092 9.04745C22.5798 8.96787 22.6465 8.88985 22.7685 8.75957Z" fill="#A7CA60"/>
</g>
<defs>
<clipPath id="clip0_76_12">
<rect width="27" height="15" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="9" height="6" viewBox="0 0 9 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.11629 5.8584L8.07227 0.858398H0.160323L4.11629 5.8584Z" fill="#2E3A59"/>
</svg>

After

Width:  |  Height:  |  Size: 184 B

View File

@ -0,0 +1,11 @@
<svg width="15" height="18" viewBox="0 0 15 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_68_166)">
<path d="M7.83533 -0.000427246C8.01139 0.0347969 8.19098 0.0603351 8.36352 0.106127C10.1325 0.574608 11.2721 1.69782 11.6378 3.48501C12.1598 6.0326 10.4182 8.17423 8.22135 8.59868C7.24129 8.79102 6.22489 8.63913 5.34382 8.16864C4.46275 7.69816 3.771 6.93791 3.38538 6.0163C2.7159 4.41052 3.02929 2.48332 4.40345 1.19367C5.09537 0.544667 5.89733 0.14003 6.8441 0.0330362C6.87122 0.0255363 6.89714 0.014272 6.92113 -0.000427246H7.83533Z" fill="#838383"/>
<path d="M7.5037 17.9969C5.99838 17.9969 4.49305 18.0057 2.98817 17.993C2.33244 17.997 1.69302 17.7886 1.16549 17.399C0.50526 16.9094 0.11176 16.2485 0.0408957 15.4344C-0.0880693 13.9523 0.0822738 12.4997 0.633346 11.1106C0.967863 10.2674 1.46655 9.54135 2.29624 9.0962C2.79777 8.82462 3.36124 8.68806 3.93142 8.69993C4.11276 8.70345 4.30467 8.79195 4.47016 8.88178C4.7369 9.0262 4.98559 9.20408 5.24263 9.36611C6.01995 9.85749 6.86152 10.1243 7.78716 10.0547C8.46852 10.0037 9.09838 9.77647 9.67982 9.42071C9.87921 9.29874 10.0861 9.18338 10.2674 9.03764C10.7428 8.65414 11.2683 8.64798 11.8295 8.77346C12.8648 9.00374 13.5937 9.61928 14.0981 10.5311C14.6025 11.443 14.8301 12.4244 14.9273 13.4442C14.9789 13.9979 15.0026 14.5538 14.9982 15.1099C14.989 16.6108 13.8608 17.8252 12.3652 17.9648C12.1328 17.9863 11.8982 17.9987 11.6649 17.9991C10.2767 18.0026 8.88887 17.9991 7.50063 17.9991L7.5037 17.9969Z" fill="#838383"/>
</g>
<defs>
<clipPath id="clip0_68_166">
<rect width="15" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 1920 1920" xmlns="http://www.w3.org/2000/svg"> <path d="M14.569 2.26115H12.9023V1.42782C12.9023 1.2068 12.8145 0.99484 12.6583 0.83856C12.502 0.68228 12.29 0.594482 12.069 0.594482C11.848 0.594482 11.636 0.68228 11.4798 0.83856C11.3235 0.99484 11.2357 1.2068 11.2357 1.42782V2.26115H6.23568V1.42782C6.23568 1.2068 6.14788 0.99484 5.9916 0.83856C5.83532 0.68228 5.62336 0.594482 5.40234 0.594482C5.18133 0.594482 4.96937 0.68228 4.81309 0.83856C4.65681 0.99484 4.56901 1.2068 4.56901 1.42782V2.26115H2.90234C2.2393 2.26115 1.60342 2.52454 1.13458 2.99338C0.665736 3.46222 0.402344 4.09811 0.402344 4.76115V14.7611C0.402344 15.4242 0.665736 16.0601 1.13458 16.5289C1.60342 16.9978 2.2393 17.2612 2.90234 17.2612H14.569C15.2321 17.2612 15.8679 16.9978 16.3368 16.5289C16.8056 16.0601 17.069 15.4242 17.069 14.7611V4.76115C17.069 4.09811 16.8056 3.46222 16.3368 2.99338C15.8679 2.52454 15.2321 2.26115 14.569 2.26115ZM15.4023 14.7611C15.4023 14.9822 15.3145 15.1941 15.1583 15.3504C15.002 15.5067 14.79 15.5945 14.569 15.5945H2.90234C2.68133 15.5945 2.46937 15.5067 2.31309 15.3504C2.15681 15.1941 2.06901 14.9822 2.06901 14.7611V8.92782H15.4023V14.7611ZM15.4023 7.26115H2.06901V4.76115C2.06901 4.54014 2.15681 4.32817 2.31309 4.17189C2.46937 4.01561 2.68133 3.92782 2.90234 3.92782H4.56901V4.76115C4.56901 4.98216 4.65681 5.19412 4.81309 5.3504C4.96937 5.50669 5.18133 5.59448 5.40234 5.59448C5.62336 5.59448 5.83532 5.50669 5.9916 5.3504C6.14788 5.19412 6.23568 4.98216 6.23568 4.76115V3.92782H11.2357V4.76115C11.2357 4.98216 11.3235 5.19412 11.4798 5.3504C11.636 5.50669 11.848 5.59448 12.069 5.59448C12.29 5.59448 12.502 5.50669 12.6583 5.3504C12.8145 5.19412 12.9023 4.98216 12.9023 4.76115V3.92782H14.569C14.79 3.92782 15.002 4.01561 15.1583 4.17189C15.3145 4.32817 15.4023 4.54014 15.4023 4.76115V7.26115Z" fill="#406128"/>
<path d="M1411.824 0c31.17 0 56.47 25.3 56.47 56.471v56.47h169.412c93.403 0 169.412 76.01 169.412 169.412V1920H113V282.353c0-93.402 76.009-169.412 169.412-169.412h169.41v-56.47c0-31.172 25.3-56.47 56.472-56.47s56.47 25.298 56.47 56.47v56.47h790.589v-56.47c0-31.172 25.299-56.47 56.47-56.47Zm282.352 564.705H225.942v1242.353h1468.234V564.705Zm-1016.47 677.648v338.824H338.882v-338.824h338.824Zm451.765 0v338.824H790.647v-338.824h338.824Zm451.764 0v338.824h-338.823v-338.824h338.823Zm-1016.47 112.941H451.824v112.941h112.941v-112.941Zm451.764 0H903.588v112.941h112.941v-112.941Zm451.765 0h-112.941v112.941h112.941v-112.941ZM677.706 790.588v338.824H338.882V790.588h338.824Zm451.765 0v338.824H790.647V790.588h338.824Zm451.764 0v338.824h-338.823V790.588h338.823ZM564.765 903.53H451.824v112.941h112.941V903.53Zm451.764 0H903.588v112.941h112.941V903.53Zm451.765 0h-112.941v112.941h112.941V903.53ZM451.823 225.882H282.412c-31.06 0-56.47 25.3-56.47 56.471v169.412h1468.234V282.353c0-31.172-25.411-56.47-56.47-56.47h-169.412v56.47c0 31.172-25.3 56.471-56.47 56.471-31.172 0-56.471-25.299-56.471-56.47v-56.472H564.765v56.471c0 31.172-25.3 56.471-56.471 56.471-31.171 0-56.471-25.299-56.471-56.47v-56.472Z" fill-rule="evenodd"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -0,0 +1,15 @@
<svg width="23" height="22" viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_64_66)">
<mask id="mask0_64_66" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="23" height="22">
<path d="M22.0015 0H0V22H22.0015V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_64_66)">
<path d="M10.9461 22.0001C4.87347 21.9702 -0.0423218 16.9977 0.000274756 10.9273C0.0436186 4.86661 4.99678 -0.0297562 11.0545 0.000136144C17.1159 0.0307758 22.0317 4.98468 22.0018 11.0349C21.9719 17.1247 17.0255 22.0293 10.9461 22.0001ZM16.0487 7.6025C16.0711 6.87985 15.8103 6.63324 15.2715 6.68256C14.9591 6.71096 14.664 6.8126 14.3777 6.93217C12.3622 7.77513 10.3475 8.61809 8.335 9.46778C7.12361 9.97969 5.91447 10.4968 4.71056 11.0259C4.48935 11.1231 4.25096 11.2531 4.27338 11.567C4.2958 11.8749 4.56782 11.9234 4.77557 11.9937C5.37491 12.1962 5.97874 12.392 6.59303 12.5415C7.0997 12.6655 7.61161 12.6199 8.06298 12.3173C9.56955 11.3069 11.0709 10.2898 12.5782 9.28021C12.7307 9.17857 12.9108 8.94392 13.0901 9.15316C13.2747 9.36764 13.0087 9.5029 12.8801 9.62995C11.8354 10.6612 10.7809 11.6828 9.7362 12.7133C9.26839 13.1752 9.31397 13.6011 9.85727 13.9718C10.1547 14.1743 10.4603 14.3656 10.76 14.5667C11.5544 15.0995 12.3361 15.6525 13.1447 16.1629C14.0243 16.7182 14.4457 16.5523 14.7476 15.5852C14.7768 15.4911 14.8029 15.3947 14.8239 15.2983C15.1318 13.9016 15.3246 12.4847 15.5465 11.073C15.7378 9.85713 15.8993 8.63678 16.0472 7.6025H16.0487Z" fill="#9F9F9F"/>
</g>
</g>
<defs>
<clipPath id="clip0_64_66">
<rect width="23" height="22" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,16 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_64_59)">
<mask id="mask0_64_59" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="22" height="22">
<path d="M21.9955 0H0V22H21.9955V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_64_59)">
<path d="M10.9755 22C4.9123 21.9881 -0.0230861 17.0228 8.12304e-05 10.9589C0.0232485 4.91 4.97209 -0.0119356 11.0195 2.17408e-05C17.07 0.0119791 21.9994 4.95335 21.9956 11.003C21.9919 17.0743 17.0438 22.012 10.9755 22ZM11.4724 9.84166C11.4724 9.53076 11.4807 9.21913 11.4702 8.90824C11.4545 8.42621 11.3454 8.29393 10.8716 8.20798C10.2289 8.09215 9.58239 8.10336 8.93968 8.20873C8.77303 8.23638 8.60861 8.31336 8.4569 8.39332C8.28875 8.48151 8.26708 8.58987 8.47858 8.66012C8.78797 8.76325 8.99424 8.96653 9.04132 9.29162C9.12203 9.84689 9.16313 10.4044 9.05552 10.9634C8.95164 11.503 8.5974 11.6315 8.1774 11.2691C8.07502 11.1801 7.98758 11.0718 7.90014 10.9671C7.31797 10.2669 6.96373 9.44856 6.68348 8.59436C6.58409 8.29019 6.37633 8.13923 6.07739 8.134C5.45561 8.12279 4.83308 8.12129 4.2113 8.13998C3.84959 8.15119 3.76739 8.28645 3.91685 8.62126C4.63728 10.2355 5.43095 11.8101 6.52953 13.2084C7.66324 14.6515 9.10634 15.4354 10.9874 15.2972C11.314 15.2733 11.515 15.2045 11.4702 14.8226C11.4538 14.6784 11.4821 14.5237 11.5158 14.3795C11.6234 13.9139 11.9208 13.7756 12.3491 13.9841C12.6428 14.1276 12.8931 14.3376 13.127 14.5581C13.6898 15.0909 14.3302 15.3689 15.1202 15.2987C15.5155 15.2636 15.9161 15.2927 16.3144 15.292C16.6387 15.292 17.0117 15.3241 17.1589 14.9684C17.3001 14.6261 17.0109 14.3854 16.7957 14.1702C16.3024 13.6792 15.7838 13.2136 15.2868 12.7256C14.9468 12.3916 14.9274 12.1935 15.2046 11.8116C15.4819 11.4297 15.789 11.0688 16.0768 10.6944C16.5401 10.0913 16.9661 9.465 17.1634 8.71617C17.2785 8.27898 17.2037 8.15567 16.7576 8.14521C15.999 8.12802 15.2397 8.14297 14.4812 8.15119C14.2189 8.15418 14.0874 8.32307 14.0066 8.554C13.8788 8.91646 13.757 9.28265 13.6038 9.63464C13.334 10.2557 12.9701 10.8192 12.4634 11.2758C12.2885 11.4335 12.0793 11.5874 11.8252 11.4963C11.5509 11.3984 11.4919 11.1368 11.4784 10.8849C11.4597 10.5374 11.4739 10.1884 11.4724 9.84016V9.84166Z" fill="#9F9F9F"/>
<path d="M11.4733 9.84153C11.4733 10.1898 11.4598 10.5388 11.4785 10.8863C11.492 11.1381 11.5518 11.3997 11.8253 11.4976C12.0794 11.588 12.2894 11.4341 12.4635 11.2772C12.9702 10.8205 13.3334 10.257 13.6039 9.63601C13.7571 9.28402 13.879 8.91782 14.0067 8.55537C14.0882 8.32369 14.219 8.15479 14.4813 8.15255C15.2398 8.14433 15.9991 8.13013 16.7577 8.14657C17.2038 8.15629 17.2793 8.2796 17.1635 8.71754C16.9662 9.46636 16.5402 10.0926 16.0769 10.6957C15.7891 11.0701 15.482 11.4311 15.2047 11.813C14.9275 12.1956 14.9461 12.3929 15.2869 12.727C15.7839 13.215 16.3026 13.6806 16.7958 14.1716C17.011 14.3861 17.3002 14.6267 17.159 14.9697C17.0125 15.3255 16.6396 15.2933 16.3145 15.2933C15.9162 15.2933 15.5156 15.2649 15.1203 15.3C14.3303 15.3703 13.6906 15.0923 13.1271 14.5594C12.894 14.3382 12.6436 14.129 12.3492 13.9855C11.9209 13.7762 11.6235 13.9152 11.5159 14.3808C11.4823 14.5251 11.4539 14.6798 11.4703 14.824C11.5144 15.2059 11.3141 15.2746 10.9875 15.2986C9.10646 15.4368 7.66337 14.6529 6.52967 13.2098C5.43109 11.8115 4.63817 10.2369 3.91699 8.62262C3.76752 8.28782 3.84973 8.15255 4.21144 8.14134C4.83321 8.12266 5.45575 8.12415 6.07753 8.13536C6.37721 8.1406 6.58422 8.29156 6.68362 8.59572C6.96312 9.44992 7.3181 10.2683 7.90028 10.9685C7.98772 11.0739 8.07441 11.1822 8.17754 11.2704C8.59679 11.6329 8.95178 11.5051 9.05566 10.9648C9.16327 10.4058 9.12217 9.84825 9.04146 9.29298C8.99437 8.96715 8.78811 8.76387 8.47871 8.66149C8.26647 8.59124 8.28889 8.48287 8.45704 8.39469C8.60875 8.31472 8.77315 8.237 8.93981 8.2101C9.58177 8.10472 10.229 8.09351 10.8717 8.20935C11.3455 8.29455 11.4546 8.42682 11.4703 8.9096C11.48 9.22049 11.4725 9.53213 11.4725 9.84302L11.4733 9.84153Z" fill="#FBFBFB"/>
</g>
</g>
<defs>
<clipPath id="clip0_64_59">
<rect width="22" height="22" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,12 @@
<svg width="1005" height="969" viewBox="0 0 1005 969" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_f_53_13)">
<circle cx="502.5" cy="466.5" r="281.5" fill="white"/>
</g>
<defs>
<filter id="filter0_f_53_13" x="0.300003" y="-35.7" width="1004.4" height="1004.4" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="110.35" result="effect1_foregroundBlur_53_13"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
src/assets/images/cat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
src/assets/images/clue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -108,7 +108,7 @@ export const AuthBlock = ({ title, description, img, resetModal }) => {
> >
{isLoading ? <Loader /> : "Войти"} {isLoading ? <Loader /> : "Войти"}
</button> </button>
<NavLink to="/tracker-registration" className="auth__registration"> <NavLink to="/auth" className="auth__registration">
Регистрация Регистрация
</NavLink> </NavLink>
</div> </div>

View File

@ -70,6 +70,7 @@ export const AuthBox = ({ title }) => {
dispatch(setUserInfo(res)); dispatch(setUserInfo(res));
dispatch(loading(false)); dispatch(loading(false));
dispatch(setRole("ROLE_PARTNER")); dispatch(setRole("ROLE_PARTNER"));
navigate("/profile");
} }
}); });
} }

View File

@ -23,10 +23,14 @@
.calendar__hours { .calendar__hours {
margin: 0 5px; margin: 0 5px;
line-height: 0; font-size: 22px;
font-weight: 500; font-weight: 500;
display: flex; display: flex;
align-items: center; align-items: center;
span {
font-size: 22px;
font-weight: 500;
}
} }
} }
@ -54,21 +58,33 @@
} }
&-box { &-box {
background-color: #e5f1fb;
height: 47px;
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: 20px; margin: 0 15px;
cursor: pointer; padding: 0 20px;
border-radius: 5px;
font-weight: 400;
font-size: 16px;
color: #406128;
img { img {
margin: 0px 10px; width: 17px;
width: 12px; height: 17px;
height: 12px; margin: 0 10px 0 0;
&:first-child {
transform: rotate(180deg);
margin: 0;
}
} }
}
&-arrow {
display: flex;
align-items: center;
cursor: pointer;
height: 47px;
padding: 0 8px;
background-color: #e5f1fb;
border-radius: 5px;
span { span {
color: #18586e; color: #18586e;
@ -107,12 +123,9 @@
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
p { p {
color: #398208; color: #9babc5;
font-size: 25px; font-size: 14px;
font-weight: 400; font-weight: 500;
font-style: normal;
letter-spacing: normal;
line-height: 30px;
text-align: center; text-align: center;
} }
} }
@ -127,24 +140,48 @@
button { button {
margin: 0 auto; margin: 0 auto;
width: 125px; width: 135px;
height: 42px; height: 82px;
padding: 0px; padding: 0px;
box-shadow: 0 0 59px rgba(44, 44, 44, 0.05); box-shadow: 0 0 59px rgba(44, 44, 44, 0.05);
border-radius: 5px; border-radius: 10px;
border: 1px solid #c4c4c4; border: 1px solid #c4c4c4;
background-color: #ffffff; background-color: #ffffff;
margin-top: 20px; margin-top: 20px;
font-size: 1.2em; font-size: 1.2em;
text-align: center; text-align: center;
.form-date {
width: 100%;
padding: 5px 0 0 15px;
height: 100%;
}
.form-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
border-top: #eff5fa 2px solid;
.form-hours {
display: none;
}
}
a { a {
color: black; color: black;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-weight: 500; font-weight: 400;
font-size: 12px; font-size: 16px;
height: 100%;
@media (max-width: 500px) { @media (max-width: 500px) {
font-size: 10px; font-size: 10px;
@ -152,20 +189,6 @@
} }
} }
img {
width: 16px;
height: 16px;
margin: 0 5px 0 0;
@media (max-width: 968px) {
margin-right: 2px;
@media (max-width: 610px) {
display: none;
}
}
}
@media (max-width: 1200px) { @media (max-width: 1200px) {
width: 110px; width: 110px;
} }
@ -214,17 +237,6 @@
} }
} }
.calendar__icon {
margin-right: 10px;
margin-top: -4px;
}
@media (max-width: 575.98px) {
.calendar__icon {
display: none;
}
}
.select-days { .select-days {
border-style: dashed !important; border-style: dashed !important;
@ -246,11 +258,48 @@
} }
.before { .before {
background-color: #e5f9b6 !important; background-color: #ffffff !important;
.form-date {
height: 35% !important;
}
.form-box {
background-color: #fafbfe;
.form-hours {
color: #6dd077;
background-color: #6dd0772f;
border-spacing: 10px;
border-radius: 5px;
display: flex !important;
flex-direction: row;
width: 95%;
font-weight: 400;
font-size: 14px;
padding: 0 7px;
position: relative;
span {
color: #1458dd;
font-weight: 700;
font-size: 14px;
margin: auto;
}
}
.form-hours::before {
content: "";
position: absolute;
top: 10%;
left: 0;
height: 75%;
border-left: 2px solid #6dd077;
border-radius: 5px;
}
}
} }
.pass { .pass {
background-color: #f7d7c9 !important; background-color: #ba5c33 !important;
} }
.today { .today {
@ -259,7 +308,7 @@
} }
.selected { .selected {
background-color: #f9f9c3 !important; background-color: #cbcbb4 !important;
} }
.block { .block {
@ -271,18 +320,25 @@
align-items: center; align-items: center;
column-gap: 16px; column-gap: 16px;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 400;
position: relative; position: relative;
.select { .select {
background-color: #e5f1fb;
color: #406128;
border-radius: 5px; border-radius: 5px;
border: 2px solid #c4c4c4; padding: 15px;
box-shadow: 0 0 59px rgba(44, 44, 44, 0.05);
padding: 5px 8px;
cursor: pointer; cursor: pointer;
text-align: center; text-align: center;
} }
.hint {
color: #6f6f6f;
font-size: 14px;
font-weight: 300;
margin: 0 0 0 20px;
}
.close { .close {
cursor: pointer; cursor: pointer;
margin-left: 8px; margin-left: 8px;

View File

@ -32,7 +32,7 @@ export const Candidate = () => {
if (localStorage.getItem("role_status") !== "18") { if (localStorage.getItem("role_status") !== "18") {
return <Navigate to="/profile" replace />; return <Navigate to="/profile" replace />;
} }
// const { id: candidateId } = useParams(); const { id: candidateId } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
@ -47,7 +47,7 @@ export const Candidate = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
apiRequest(`/user/me`, {}).then((el) => apiRequest(`/resume?userId=${candidateId}`).then((el) =>
dispatch(currentCandidate(el.userCard)) dispatch(currentCandidate(el.userCard))
); );
}, [dispatch]); }, [dispatch]);
@ -103,9 +103,7 @@ export const Candidate = () => {
link: "/profile/catalog" link: "/profile/catalog"
}, },
{ {
name: `${currentCandidateObj.specification} ${ name: `${currentCandidateObj.fio}`,
SKILLS[currentCandidateObj.position_id]
}, ${LEVELS[currentCandidateObj.level]}`,
link: `/candidate/${currentCandidateObj.id}` link: `/candidate/${currentCandidateObj.id}`
} }
]} ]}
@ -115,6 +113,7 @@ export const Candidate = () => {
<div className="col-12 candidate__header"> <div className="col-12 candidate__header">
<div className="candidate__header__left"> <div className="candidate__header__left">
<h3> <h3>
{currentCandidateObj.fio} &nbsp;{" "}
{currentCandidateObj.specification} &nbsp;{" "} {currentCandidateObj.specification} &nbsp;{" "}
{SKILLS[currentCandidateObj.position_id]} &nbsp;{" "} {SKILLS[currentCandidateObj.position_id]} &nbsp;{" "}
{LEVELS[currentCandidateObj.level]} {LEVELS[currentCandidateObj.level]}

View File

@ -3,7 +3,6 @@
.candidate { .candidate {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100vh;
background: #f1f1f1; background: #f1f1f1;
.container { .container {

View File

@ -10,7 +10,7 @@ export const CardControl = ({ title, path, description, img }) => {
<Link to={`/${path}`} className="control-card"> <Link to={`/${path}`} className="control-card">
<div className="control-card__about"> <div className="control-card__about">
<img src={img} alt="itemImg" /> <img src={img} alt="itemImg" />
<h3>{title}</h3> <h3 dangerouslySetInnerHTML={{ __html: title }}></h3>
</div> </div>
<div className="control-card__info"> <div className="control-card__info">
<p dangerouslySetInnerHTML={{ __html: description }}></p> <p dangerouslySetInnerHTML={{ __html: description }}></p>

View File

@ -38,7 +38,7 @@
font-weight: 500; font-weight: 500;
font-size: 18px; font-size: 18px;
line-height: 22px; line-height: 22px;
max-width: 125px; max-width: 165px;
margin-bottom: 0; margin-bottom: 0;
} }
} }

View File

@ -24,9 +24,9 @@ export const AuthHeader = () => {
<span>Главная</span> <span>Главная</span>
</NavLink> </NavLink>
</li> </li>
<li> {/*<li>*/}
<NavLink to={"/profile"}>Кабинет разработчика</NavLink> {/* <NavLink to={"/profile"}>Кабинет разработчика</NavLink>*/}
</li> {/*</li>*/}
<li> <li>
<NavLink to={"/tracker-intro"}>Трекер</NavLink> <NavLink to={"/tracker-intro"}>Трекер</NavLink>
</li> </li>

View File

@ -4,10 +4,15 @@ import { backendImg } from "@utils/helper";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
import { useNotification } from "@hooks/useNotification";
import close from "assets/icons/closeProjectPersons.svg"; import close from "assets/icons/closeProjectPersons.svg";
const FileTracker = ({ file, setDeletedTask, taskId }) => { const FileTracker = ({ file, setDeletedTask, taskId }) => {
const [openImg, setOpenImg] = useState(false); const [openImg, setOpenImg] = useState(false);
const { showNotification } = useNotification();
function deleteFile(file) { function deleteFile(file) {
apiRequest("/file/detach", { apiRequest("/file/detach", {
method: "DELETE", method: "DELETE",
@ -17,9 +22,22 @@ const FileTracker = ({ file, setDeletedTask, taskId }) => {
entity_id: taskId, entity_id: taskId,
status: 0 status: 0
} }
}).then(() => { })
setDeletedTask(file); .then(() => {
}); setDeletedTask(file);
showNotification({
show: true,
text: "Файл успешно удален",
type: "success"
});
})
.catch(() => {
showNotification({
show: true,
text: "Ошибка при удалении файла",
type: "error"
});
});
} }
return ( return (

View File

@ -7,6 +7,8 @@ import withReactContent from "sweetalert2-react-content";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
import { useNotification } from "@hooks/useNotification";
import { Loader } from "@components/Common/Loader/Loader"; import { Loader } from "@components/Common/Loader/Loader";
import "./form.scss"; import "./form.scss";
@ -18,6 +20,8 @@ const Form = () => {
const urlParams = useParams(); const urlParams = useParams();
const { showNotification } = useNotification();
const [status, setStatus] = useState(null); const [status, setStatus] = useState(null);
const [data, setData] = useState({ const [data, setData] = useState({
email: "", email: "",
@ -75,10 +79,23 @@ const Form = () => {
profile_id: urlParams.id, profile_id: urlParams.id,
...data ...data
} }
}).then((res) => { })
setStatus(res); .then((res) => {
setIsFetching(false); setStatus(res);
}); setIsFetching(false);
showNotification({
show: true,
text: "Отправка успешно завершена",
type: "success"
});
})
.catch(() => {
showNotification({
show: true,
text: "Ошибка отправки",
type: "error"
});
});
}; };
return ( return (

View File

@ -1,4 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { useFormValidation } from "@hooks/useFormValidation"; import { useFormValidation } from "@hooks/useFormValidation";
import { useNotification } from "@hooks/useNotification"; import { useNotification } from "@hooks/useNotification";
@ -17,6 +18,7 @@ import "./modalRegistration.scss";
export const ModalRegistration = ({ active, setActive }) => { export const ModalRegistration = ({ active, setActive }) => {
const [loader, setLoader] = useState(false); const [loader, setLoader] = useState(false);
const [isPartner, setIsPartner] = useState(false); const [isPartner, setIsPartner] = useState(false);
const navigate = useNavigate();
const fields = { const fields = {
username: "", username: "",
@ -145,6 +147,7 @@ export const ModalRegistration = ({ active, setActive }) => {
onClick={async (e) => { onClick={async (e) => {
e.preventDefault(); e.preventDefault();
await handleSubmit(e); await handleSubmit(e);
navigate("/welcome-page");
}} }}
styles="button-box__submit" styles="button-box__submit"
> >

View File

@ -7,7 +7,7 @@ import { useNotification } from "@hooks/useNotification";
import { Loader } from "@components/Common/Loader/Loader"; import { Loader } from "@components/Common/Loader/Loader";
import ModalLayout from "@components/Common/ModalLayout/ModalLayout"; import ModalLayout from "@components/Common/ModalLayout/ModalLayout";
import arrow from "assets/icons/arrows/arrowCalendar.png"; import arrow from "assets/icons/arrows/arrowRight.png";
import close from "assets/icons/close.png"; import close from "assets/icons/close.png";
import "./modalResetPassword.scss"; import "./modalResetPassword.scss";

View File

@ -10,6 +10,8 @@ import { caseOfNum, removeLast, urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
import { useNotification } from "@hooks/useNotification";
import ModalLayout from "@components/Common/ModalLayout/ModalLayout"; import ModalLayout from "@components/Common/ModalLayout/ModalLayout";
import close from "assets/icons/close.png"; import close from "assets/icons/close.png";
@ -24,6 +26,7 @@ const ListEmployees = ({
setModalAdd setModalAdd
}) => { }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { showNotification } = useNotification();
function deletePerson(userId) { function deletePerson(userId) {
apiRequest("/project/del-user", { apiRequest("/project/del-user", {
@ -32,9 +35,22 @@ const ListEmployees = ({
project_id: projectBoard.id, project_id: projectBoard.id,
user_id: userId user_id: userId
} }
}).then(() => { })
dispatch(deletePersonOnProject(userId)); .then(() => {
}); dispatch(deletePersonOnProject(userId));
showNotification({
show: true,
text: "Участник успешно удален",
type: "success"
});
})
.catch(() => {
showNotification({
show: true,
text: "Ошибка удаления участника",
type: "error"
});
});
} }
return ( return (

View File

@ -118,15 +118,23 @@ export const ModalTiсket = ({
task_id: task.id, task_id: task.id,
status: 0 status: 0
} }
}).then(() => { })
closeModal(); .then(() => {
dispatch(setProjectBoardFetch(projectId)); closeModal();
showNotification({ dispatch(setProjectBoardFetch(projectId));
show: true, showNotification({
text: "Задача успешно была перемещена в архив", show: true,
type: "archive" text: "Задача успешно удалена",
type: "success"
});
})
.catch(() => {
showNotification({
show: true,
text: "Ошибка удаления",
type: "error"
});
}); });
});
} }
const priority = { const priority = {
@ -169,15 +177,23 @@ export const ModalTiсket = ({
title: inputsValue.title, title: inputsValue.title,
description: inputsValue.description description: inputsValue.description
} }
}).then((res) => { })
setEditOpen(!editOpen); .then((res) => {
dispatch(setProjectBoardFetch(projectId)); setEditOpen(!editOpen);
showNotification({ dispatch(setProjectBoardFetch(projectId));
show: true, showNotification({
text: "Изменения сохранены", show: true,
type: "success" text: "Изменения сохранены",
type: "success"
});
})
.catch(() => {
showNotification({
show: true,
text: "Ошибка при сохранении изменений",
type: "error"
});
}); });
});
} }
function createComment() { function createComment() {
@ -648,11 +664,8 @@ export const ModalTiсket = ({
"EasyImage", "EasyImage",
"Image", "Image",
"ImageCaption", "ImageCaption",
"ImageStyle",
"ImageToolbar",
"ImageUpload", "ImageUpload",
"MediaEmbed", "MediaEmbed"
"BlockQuote"
] ]
}} }}
onChange={(event, editor) => { onChange={(event, editor) => {

View File

@ -982,7 +982,7 @@
position: relative; position: relative;
row-gap: 10px; row-gap: 10px;
padding: 10px 40px 0px 20px; padding: 10px 20px 0px 20px;
border-radius: 10px; border-radius: 10px;
margin-bottom: 10px; margin-bottom: 10px;
@ -1113,7 +1113,7 @@
&-priority { &-priority {
position: relative; position: relative;
padding: 10px 40px 0 20px; padding: 10px 20px 0 20px;
border-radius: 10px; border-radius: 10px;
margin-bottom: 10px; margin-bottom: 10px;
.priority { .priority {
@ -1173,7 +1173,7 @@
} }
&-bottom { &-bottom {
padding: 0px 90px 10px 35px; padding: 0px 30px 10px 20px;
font-weight: 400; font-weight: 400;
font-size: 14px; font-size: 14px;
line-height: 32px; line-height: 32px;
@ -1196,9 +1196,10 @@
.edit { .edit {
background: #52b709; background: #52b709;
border-radius: 50px; border-radius: 50px;
width: fit-content;
p { p {
font-weight: 700; font-weight: 700;
padding-right: 10px;
} }
} }

View File

@ -32,7 +32,7 @@ import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadc
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader"; import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import TrackerTaskComment from "@components/TrackerTaskComment/TrackerTaskComment"; import TrackerTaskComment from "@components/TrackerTaskComment/TrackerTaskComment";
import arrow from "assets/icons/arrows/arrowCalendar.png"; import arrow from "assets/icons/arrows/arrowRight.png";
import arrowStart from "assets/icons/arrows/arrowStart.png"; import arrowStart from "assets/icons/arrows/arrowStart.png";
import arrowDown from "assets/icons/arrows/selectArrow.png"; import arrowDown from "assets/icons/arrows/selectArrow.png";
import calendarIcon from "assets/icons/calendar.svg"; import calendarIcon from "assets/icons/calendar.svg";
@ -683,11 +683,8 @@ export const TicketFullScreen = () => {
"EasyImage", "EasyImage",
"Image", "Image",
"ImageCaption", "ImageCaption",
"ImageStyle",
"ImageToolbar",
"ImageUpload", "ImageUpload",
"MediaEmbed", "MediaEmbed"
"BlockQuote"
] ]
}} }}
onChange={(event, editor) => { onChange={(event, editor) => {

View File

@ -137,14 +137,22 @@ export const TrackerModal = ({
: 1, : 1,
title: valueColumn title: valueColumn
} }
}).then(() => { })
dispatch(setProjectBoardFetch(projectBoard.id)); .then(() => {
showNotification({ dispatch(setProjectBoardFetch(projectBoard.id));
show: true, showNotification({
text: "Колонка создана", show: true,
type: "success" text: "Колонка успешно создана",
type: "success"
});
})
.catch(() => {
showNotification({
show: true,
text: "Ошибка при создании колонки",
type: "error"
});
}); });
});
setValueColumn(""); setValueColumn("");
setActive(false); setActive(false);
} }
@ -221,7 +229,7 @@ export const TrackerModal = ({
setDeadLineDate(""); setDeadLineDate("");
showNotification({ showNotification({
show: true, show: true,
text: "Задача создана", text: "Задача успешно создана",
type: "success" type: "success"
}); });
} }
@ -291,13 +299,25 @@ export const TrackerModal = ({
title: columnName title: columnName
} }
}).then(() => { }).then(() => {
setActive(false); const existingColumn = projectBoard.columns.find(
dispatch(editColumnName({ id: columnId, title: columnName })); (column) => column.title === columnName
showNotification({ );
show: true,
text: "Колонка создана", if (existingColumn) {
type: "success" showNotification({
}); show: true,
text: "Колонка с таким названием уже существует",
type: "error"
});
} else {
setActive(false);
dispatch(editColumnName({ id: columnId, title: columnName }));
showNotification({
show: true,
text: "Колонка успешно изменена",
type: "success"
});
}
}); });
} }
@ -316,6 +336,11 @@ export const TrackerModal = ({
dispatch(setProject(result)); dispatch(setProject(result));
setActive(false); setActive(false);
setNameProject(""); setNameProject("");
showNotification({
show: true,
text: "Проект успешно создан",
type: "success"
});
} else { } else {
showNotification({ showNotification({
show: true, show: true,
@ -362,14 +387,29 @@ export const TrackerModal = ({
email: emailWorker, email: emailWorker,
project_id: projectBoard.id project_id: projectBoard.id
} }
}).then((el) => { }).then((response) => {
setActive(false); if (response.status === 400) {
setEmailWorker(""); showNotification({
showNotification({ show: true,
show: true, text: "Участник уже добавлен в проект",
text: "Приглашение отправлено", type: "error"
type: "success" });
}); } else if (response.status === 404) {
showNotification({
show: true,
text: "Данной почты не существует",
type: "error"
});
} else {
setActive(false);
setEmailWorker("");
dispatch(addPersonToProject(response));
showNotification({
show: true,
text: "Приглашение отправлено",
type: "success"
});
}
}); });
} else { } else {
setEmailError("Некорректный e-mail адрес"); setEmailError("Некорректный e-mail адрес");
@ -584,7 +624,15 @@ export const TrackerModal = ({
"bulletedList", "bulletedList",
"numberedList" "numberedList"
], ],
removePlugins: ["BlockQuote"], removePlugins: [
"CKFinderUploadAdapter",
"CKFinder",
"EasyImage",
"Image",
"ImageCaption",
"ImageUpload",
"MediaEmbed"
],
placeholder: "Описание задачи" placeholder: "Описание задачи"
}} }}
onChange={(event, editor) => { onChange={(event, editor) => {

View File

@ -719,7 +719,6 @@
.button-add { .button-add {
margin: 0 30px; margin: 0 30px;
align-self: flex-end;
} }
} }

View File

@ -43,11 +43,11 @@ export const Navigation = () => {
} }
], ],
partner: [ partner: [
{ // {
path: "/catalog", // path: "/catalog",
active: "candidate", // active: "candidate",
name: "Каталог" // name: "Каталог"
}, // },
{ {
path: "/requests", path: "/requests",
name: "Мои вакансии" name: "Мои вакансии"
@ -81,7 +81,7 @@ export const Navigation = () => {
<NavLink <NavLink
key={index} key={index}
end end
to={link.path === "/quiz" ? link.path : `/profile${link.path}`} to={`/profile${link.path}`}
className={ className={
currentPath.includes(link.path) || currentPath.includes(link.path) ||
currentPath.includes(link.active) currentPath.includes(link.active)

View File

@ -95,11 +95,21 @@ export const ProfileCalendar = () => {
alt="avatar" alt="avatar"
/> />
<p className="summary__name"> <p className="summary__name">
{profileInfo?.fio || profileInfo?.username},{" "} {profileInfo?.fio || profileInfo?.username}{" "}
{profileInfo.specification} разработчик {profileInfo.specification}
</p> </p>
<hr />
<div className="summary__direction">Front End</div>
<div className="summary__level">Middle+</div>
</div> </div>
<Link to="/profile/calendar/report"> <div className="summary__skill">
<p>Ключевые навыки:</p>
<div>Java</div>
<div>Java</div>
<div>Solid</div>
<div>Java</div>
</div>
{/* <Link to="/profile/calendar/report">
<button <button
className="calendar__btn" className="calendar__btn"
onClick={() => { onClick={() => {
@ -109,7 +119,7 @@ export const ProfileCalendar = () => {
> >
Заполнить отчет Заполнить отчет
</button> </button>
</Link> </Link> */}
</div> </div>
{loader ? ( {loader ? (
<div className="loader__wrapper"> <div className="loader__wrapper">

View File

@ -14,7 +14,6 @@ import {
import { import {
calendarHelper, calendarHelper,
correctDay, correctDay,
currentMonthAndDay,
getCorrectDate, getCorrectDate,
getReports, getReports,
hourOfNum hourOfNum
@ -26,9 +25,9 @@ import { apiRequest } from "@api/request";
import "@components/Calendar/calendarComponent.scss"; import "@components/Calendar/calendarComponent.scss";
import BaseButton from "@components/Common/BaseButton/BaseButton"; import BaseButton from "@components/Common/BaseButton/BaseButton";
import arrow from "assets/icons/arrows/arrowCalendar.png"; import arrowLeft from "assets/icons/arrows/arrowCalendar_left.png";
import arrowRight from "assets/icons/arrows/arrowCalendar_right.png";
import calendarIcon from "assets/icons/calendar.svg"; import calendarIcon from "assets/icons/calendar.svg";
// import close from "assets/icons/closeProjectPersons.svg";
import rectangle from "assets/images/rectangle__calendar.png"; import rectangle from "assets/images/rectangle__calendar.png";
export const ProfileCalendarComponent = React.memo( export const ProfileCalendarComponent = React.memo(
@ -45,6 +44,7 @@ export const ProfileCalendarComponent = React.memo(
}) => { }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
// const [l, setL] = useState(1);
const [calendar, setCalendar] = useState([]); const [calendar, setCalendar] = useState([]);
const [month, setMonth] = useState(""); const [month, setMonth] = useState("");
const [endDate, setEndDate] = useState(null); const [endDate, setEndDate] = useState(null);
@ -173,11 +173,18 @@ export const ProfileCalendarComponent = React.memo(
}); });
} }
const countHours = (day) => {
let hours =
reports
.find((item) => moment(item.created_at).isSame(day, "date"))
?.task.reduce((acc, task) => acc + task.hours_spent, 0) || 0;
return `${hours} ${hourOfNum(hours)}`;
};
return ( return (
<div className="calendar-component"> <div className="calendar-component">
<div className="calendar-component__header"> <div className="calendar-component__header">
<div className="calendar-component__header-info"> <div className="calendar-component__header-info">
{!userId && <h3>Мои отчеты за </h3>}
<p className="calendar__hours"> <p className="calendar__hours">
{month}&nbsp; {month}&nbsp;
<span> <span>
@ -187,27 +194,26 @@ export const ProfileCalendarComponent = React.memo(
</div> </div>
<div className="calendar-component__header-switcher"> <div className="calendar-component__header-switcher">
<div <div
className="calendar-component__header-box" className="calendar-component__header-arrow"
onClick={() => { onClick={() => {
setValueHandler(prevMonth()); setValueHandler(prevMonth());
dispatch(setRequestDate(getReports(prevMonth()))); dispatch(setRequestDate(getReports(prevMonth())));
}} }}
> >
<img src={arrow} alt="" /> <img src={arrowLeft} alt="" />
<span>{prevMonth().format("MMMM")}</span>
</div> </div>
<div className="calendar-component__header-box"> <div className="calendar-component__header-box">
<span>{value.format("YYYY")}</span> <img className={"calendar__icon"} src={calendarIcon} alt="" />
<span>{value.format("MMMM, YYYY")}</span>
</div> </div>
<div <div
className="calendar-component__header-box" className="calendar-component__header-arrow"
onClick={() => { onClick={() => {
setValueHandler(nextMonth()); setValueHandler(nextMonth());
dispatch(setRequestDate(getReports(nextMonth()))); dispatch(setRequestDate(getReports(nextMonth())));
}} }}
> >
<span>{nextMonth().format("MMMM")}</span> <img src={arrowRight} alt="" />
<img src={arrow} alt="" />
</div> </div>
</div> </div>
</div> </div>
@ -218,13 +224,13 @@ export const ProfileCalendarComponent = React.memo(
<div className="calendar-component__body"> <div className="calendar-component__body">
<div> <div>
<p>Пн</p> <p>Понедельник</p>
<p>Вт</p> <p>Вторник</p>
<p>Ср</p> <p>Среда</p>
<p>Чт</p> <p>Четверг</p>
<p>Пт</p> <p>Пятница</p>
<p>Сб</p> <p>Суббота</p>
<p>Вс</p> <p>Воскресенье</p>
</div> </div>
<div className="calendar-component__form"> <div className="calendar-component__form">
@ -260,12 +266,12 @@ export const ProfileCalendarComponent = React.memo(
id="btn" id="btn"
> >
<Link to={startRangeDays ? "#" : correctRoute(day)}> <Link to={startRangeDays ? "#" : correctRoute(day)}>
<img <div className="form-date">{day.format("D")}</div>
className={"calendar__icon"} <div className="form-box">
src={calendarIcon} <div className="form-hours">
alt="" <span>{countHours(day)}</span>
/> </div>
{currentMonthAndDay(day)} </div>
</Link> </Link>
</button> </button>
)) ))
@ -289,6 +295,7 @@ export const ProfileCalendarComponent = React.memo(
? "Выберите диапазон на календаре" ? "Выберите диапазон на календаре"
: "Выбрать диапазон"} : "Выбрать диапазон"}
</span> </span>
<span> <span>
{totalRangeHours {totalRangeHours
? `${totalRangeHours} ${hourOfNum(totalRangeHours)}` ? `${totalRangeHours} ${hourOfNum(totalRangeHours)}`
@ -308,6 +315,7 @@ export const ProfileCalendarComponent = React.memo(
Сбросить Сбросить
</BaseButton> </BaseButton>
)} )}
<span className="hint">Для общего просчета - выберите диапазон</span>
</div> </div>
</div> </div>
); );

View File

@ -22,6 +22,25 @@
@media (max-width: 500px) { @media (max-width: 500px) {
padding-right: 5px; padding-right: 5px;
} }
.summary__skill {
color: #6f6f6f;
font-size: 14px;
font-weight: 300;
display: flex;
flex-direction: row;
align-items: center;
column-gap: 10px;
div {
font-size: 12px;
font-weight: 400;
border: #8dc63f 0.5px solid;
border-radius: 44px;
padding: 3px 20px;
}
}
} }
.loader__wrapper { .loader__wrapper {
@ -36,7 +55,6 @@
} }
.calendar__wrapper { .calendar__wrapper {
@media (max-width: 1000px) { @media (max-width: 1000px) {
min-height: auto; min-height: auto;
} }

View File

@ -91,11 +91,10 @@ export const ProfileHeader = () => {
}); });
}, []); }, []);
const handler = () => { const handler = (e) => {
setIsLoggingOut(true); e.preventDefault();
localStorage.clear(); localStorage.clear();
dispatch(auth(false)); dispatch(auth(false));
setIsLoggingOut(false);
navigate("/auth"); navigate("/auth");
dispatch(setProfileInfo({})); dispatch(setProfileInfo({}));
}; };
@ -144,9 +143,6 @@ export const ProfileHeader = () => {
</div> </div>
<div className={active ? "auth-body active" : "auth-body"}> <div className={active ? "auth-body active" : "auth-body"}>
{/* <div className="auth-body__title">
<img src={ITguild}></img>
</div> */}
<nav className="auth-body__navigation"> <nav className="auth-body__navigation">
<div className="profile-header__personal-info"> <div className="profile-header__personal-info">
<h3 className="profile-header__personal-info-name"> <h3 className="profile-header__personal-info-name">

View File

@ -101,16 +101,16 @@ export const ProjectTicket = ({ project, index }) => {
alt="avatar" alt="avatar"
className="project__avatar" className="project__avatar"
/> />
<span>{project.owner_info.fio}</span> <div>
<p>Создатель проекта</p>
<span>{project.owner_info.fio}</span>
</div>
</div> </div>
</Link> </Link>
{/* <Link <Link to={`/profile/statistics/${project.id}`} className="project-stats">
to={`/profile/statistics/${project.id}`}
className="project__statistics"
>
Посмотреть статистику Посмотреть статистику
</Link> */} </Link>
<span <span
className="menu-settings" className="menu-settings"
@ -159,14 +159,14 @@ export const ProjectTicket = ({ project, index }) => {
setAcceptModalOpen(true); setAcceptModalOpen(true);
}} }}
> >
<img src={archiveSet}></img> {/* <img src={archiveSet}></img>
<p>в архив</p> <p>в архив</p>
</div> </div>
<div <div
onClick={() => { onClick={() => {
navigate(`/profile/statistics/${project.id}`); navigate(`/profile/statistics/${project.id}`);
}} }}
> > */}
<img src={archiveSet}></img> <img src={archiveSet}></img>
<p>статистика</p> <p>статистика</p>
</div> </div>

View File

@ -2,7 +2,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
width: 300px; width: 22%;
height: 140px;
background: #f1f1f1; background: #f1f1f1;
border-radius: 12px; border-radius: 12px;
@ -29,7 +30,7 @@
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: 18px; font-size: 18px;
color: #111112; color: #111112;
margin-bottom: 10px; margin: 0 0 15px 0;
&:hover { &:hover {
color: black; color: black;
@ -44,14 +45,15 @@
p { p {
color: #6f6f6f; color: #6f6f6f;
margin-bottom: 0; margin-bottom: 0;
font-size: 12px; font-size: 9px;
font-weight: 500; font-weight: 300;
line-height: 17px; line-height: 17px;
} }
span { span {
color: blue; color: blue;
font-size: 15px; font-size: 15px;
font-weight: 400;
} }
.count { .count {
@ -80,18 +82,29 @@
} }
} }
&-stats {
font-size: 12px;
font-weight: 300;
line-height: 17px;
text-decoration: underline;
color: #678eda;
position: absolute;
left: 18px;
bottom: 16px;
}
.menu-settings { .menu-settings {
position: absolute; position: absolute;
font-size: 30px; font-size: 30px;
color: #6f6f6f; color: #6f6f6f;
right: 15px; right: 15px;
top: -10px; bottom: 10px;
} }
&__avatar { &__avatar {
width: 25px; width: 30px;
height: 25px; height: 30px;
margin-right: 10px; margin: 0 10px 0 0;
} }
&__open-tracker { &__open-tracker {

View File

@ -34,11 +34,15 @@
display: flex; display: flex;
align-items: center; align-items: center;
grid-column-gap: 30px; grid-column-gap: 30px;
column-gap: 30px; column-gap: 10px;
margin-top: 20px; margin-top: 20px;
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
img {
width: 23px;
}
p { p {
margin-bottom: 0; margin-bottom: 0;
font-size: 14px; font-size: 14px;

View File

@ -0,0 +1,193 @@
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { movePositionProjectTask } from "@redux/projectsTrackerSlice";
import { getCorrectDate } from "@utils/calendarHelper";
import { removeLast, urlForLocal } from "@utils/helper";
import TrackerSelectColumn from "@components/TrackerSelectColumn/TrackerSelectColumn";
import commentsBoard from "assets/icons/commentsBoard.svg";
import filesBoard from "assets/icons/filesBoard.svg";
import avatarMok from "assets/images/avatarMok.png";
import "./trackerCardTask.scss";
const TrackerCardTask = ({
task,
projectBoard,
titleColor,
column,
openTicket,
startWrapperIndexTest,
setWrapperHover
}) => {
const dispatch = useDispatch();
const [taskHover, setTaskHover] = useState({});
const priority = {
2: "Высокий",
1: "Средний",
0: "Низкий"
};
const priorityClass = {
2: "high",
1: "middle",
0: "low"
};
function dragDropTaskHandler(e, task, column) {
e.preventDefault();
if (task.id === startWrapperIndexTest.current.task.id) {
return;
}
const finishTask = column.tasks.indexOf(task);
dispatch(
movePositionProjectTask({
startTask: startWrapperIndexTest.current.task,
finishTask: task,
finishIndex: finishTask
})
);
}
function dragOverTaskHandler(e, task) {
e.preventDefault();
if (startWrapperIndexTest.current.task.id === task.id) {
return;
}
setTaskHover((prevState) => ({ [prevState]: false, [task.id]: true }));
}
function dragStartHandler(e, task, columnId) {
startWrapperIndexTest.current = { task: task, index: columnId };
}
function dragLeaveTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
}
function dragEndTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
setWrapperHover((prevState) => ({
[prevState]: false
}));
}
useEffect(() => {
const tasksHover = {};
const columnHover = {};
if (Object.keys(projectBoard).length) {
projectBoard.columns.forEach((column) => {
columnHover[column.id] = false;
column.tasks.forEach((task) => (tasksHover[task.id] = false));
});
}
setWrapperHover(columnHover);
setTaskHover(tasksHover);
}, [projectBoard]);
return (
<div
key={task.id}
className={`tasks__board__item ${
taskHover[task.id] ? "task__hover" : ""
}`}
draggable={true}
onDragStart={(e) => dragStartHandler(e, task, column.id)}
onDragOver={(e) => dragOverTaskHandler(e, task)}
onDragLeave={(e) => dragLeaveTaskHandler(e)}
onDragEnd={() => dragEndTaskHandler()}
onDrop={(e) => dragDropTaskHandler(e, task, column)}
onClick={(e) => openTicket(e, task)}
>
<div
className="tasks__board__item__title"
onClick={() => {
if (window.innerWidth < 985) {
window.location.replace(`/tracker/task/${task.id}`);
}
}}
>
<p className="task__board__item__title">{task.title}</p>
</div>
<p
dangerouslySetInnerHTML={{
__html: task.description
}}
className="tasks__board__item__description"
></p>
{Boolean(task.mark.length) && (
<div className="tasks__board__item__tags">
{task.mark.map((tag) => {
return (
<div
className="tag-item"
key={tag.id}
style={{ background: tag.color }}
>
<p>{tag.slug}</p>
</div>
);
})}
</div>
)}
<div className="tasks__board__item__container">
{typeof task.execution_priority === "number" && (
<div className="tasks__board__item__priority">
<p></p>
<span className={priorityClass[task.execution_priority]}>
{priority[task.execution_priority]}
</span>
</div>
)}
{task.dead_line && (
<div className="tasks__board__item__dead-line">
<p></p>
<span style={{ color: titleColor }}>
{getCorrectDate(task.dead_line)}
</span>
</div>
)}
</div>
<div className="tasks__board__item__info">
<div className="tasks__board__item__executor">
<img
src={
task.executor?.avatar
? urlForLocal(task.executor?.avatar)
: avatarMok
}
alt="avatar"
/>
<span>
{removeLast(task.executor?.fio) || "Исполнитель не назначен"}
</span>
</div>
<div className="tasks__board__item__info__tags">
<div className="tasks__board__item__info__more">
<img src={commentsBoard} alt="commentsImg" />
<span>{task.comment_count}</span>
</div>
<div className="tasks__board__item__info__more">
<img src={filesBoard} alt="filesImg" />
<span>{task.file_count}</span>
</div>
</div>
</div>
<TrackerSelectColumn
columns={projectBoard.columns.filter((item) => item.id !== column.id)}
currentColumn={column}
task={task}
/>
</div>
);
};
export default TrackerCardTask;

View File

@ -0,0 +1,365 @@
.tasks {
&__board {
background: #f5f7f9;
box-shadow: 0px 2px 5px rgba(60, 66, 87, 0.04),
0px 0px 0px 1px rgba(60, 66, 87, 0.08), 0px 1px 1px rgba(0, 0, 0, 0.06);
border-radius: 8px;
padding: 12px 10px 12px 8px;
width: 360px;
display: flex;
flex-direction: column;
row-gap: 10px;
height: fit-content;
position: relative;
transition: all 0.3s ease;
transform: scaleY(-1);
min-height: 815px;
@media (max-width: 900px) {
min-width: auto;
width: 100%;
max-width: none;
transform: scaleX(1);
}
.tasks-container {
display: flex;
flex-direction: column;
row-gap: 8px;
max-height: 750px;
overflow: auto;
padding: 5px;
&::-webkit-scrollbar {
width: 3px;
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
background: #cbd9f9;
border-radius: 20px;
}
&::-webkit-scrollbar-track {
background: #c5c0c6;
border-radius: 20px;
}
}
&__hover {
box-shadow: 0px 2px 10px #9cc480, 0px 0px 0px 1px rgba(60, 66, 87, 0.08),
0px 1px 1px rgba(0, 0, 0, 0.06);
}
.task__hover {
box-shadow: 0 0 5px gray;
}
&__item {
width: 328px;
padding: 6px 10px 8px 10px;
position: relative;
box-shadow: 0px 3px 2px -2px rgba(0, 0, 0, 0.06),
0px 5px 3px -2px rgba(0, 0, 0, 0.02);
border-radius: 6px;
background: #ffffff;
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: space-between;
transition: 0.4s;
&:hover {
transform: scale(1.025);
transition: 0.3s;
}
@media (max-width: 900px) {
width: 100%;
max-height: none;
&:hover {
transform: none;
}
}
&__hide {
opacity: 0;
}
&__title {
display: flex;
justify-content: space-between;
position: relative;
p {
color: #1a1919;
font-weight: 500;
font-size: 16px;
line-height: 20px;
margin-bottom: 0;
max-height: 100px;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
}
span {
cursor: pointer;
display: flex;
border-radius: 6px;
align-items: center;
justify-content: center;
font-size: 20px;
padding-bottom: 10px;
width: 24px;
height: 24px;
border: 1px solid #dddddd;
}
}
&__description {
margin: 4px 0;
color: #5c6165;
font-weight: 400;
font-size: 14px;
line-height: 120%;
max-height: 100px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
&__container {
display: flex;
justify-content: space-between;
}
&__info {
display: flex;
column-gap: 10px;
align-items: center;
justify-content: space-between;
pointer-events: none;
margin-top: 5px;
&__tags {
display: flex;
column-gap: 5px;
}
&__more {
cursor: pointer;
display: flex;
align-items: center;
span {
font-weight: 500;
font-size: 12px;
line-height: 15px;
color: #6e7c87;
margin-left: 5px;
}
}
&__avatars {
position: relative;
img {
position: relative;
}
img:first-child {
right: -15px;
z-index: 2;
}
}
}
&__priority {
display: flex;
align-items: center;
column-gap: 5px;
margin-top: 3px;
p {
font-weight: 500;
font-size: 14px;
}
span {
font-weight: 500;
font-size: 14px;
}
.high {
color: red;
}
.middle {
color: #cece00;
}
.low {
color: green;
}
}
&__dead-line {
display: flex;
align-items: center;
column-gap: 5px;
p {
font-weight: 500;
font-size: 14px;
color: #1458dd;
}
span {
font-weight: 500;
font-size: 14px;
}
img {
margin-top: -2px;
width: 18px;
}
}
&__executor {
display: flex;
align-items: center;
font-size: 14px;
font-weight: 500;
column-gap: 5px;
span {
max-width: 210px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
img {
width: 25px;
height: 25px;
}
}
&__tags {
display: flex;
flex-wrap: wrap;
column-gap: 6px;
row-gap: 3px;
margin: 3px 0;
.tag-item {
padding: 3px 10px;
border-radius: 10px;
color: white;
font-size: 12px;
}
}
}
.open-items {
cursor: pointer;
border-radius: 44px;
width: 33px;
height: 33px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
bottom: -15px;
font-size: 20px;
left: 165px;
color: white;
}
.more-items {
background: #8bcc60;
}
.less-items {
background: #f92828;
}
&__more {
padding-bottom: 50px;
}
.column__select {
position: absolute;
padding: 15px;
background: #e1fccf;
border-radius: 12px;
right: -20px;
top: 5px;
z-index: 7;
row-gap: 10px;
display: flex;
flex-direction: column;
@media (max-width: 910px) {
right: 10px;
top: 40px;
}
&__item {
cursor: pointer;
display: flex;
align-content: center;
img {
margin-right: 5px;
}
span {
font-size: 14px;
}
}
}
&__no-items {
font-weight: 500;
font-size: 25px;
transform: scaleY(-1);
@media (max-width: 900px) {
transform: none;
}
}
&__no-tasks {
display: flex;
flex-direction: column;
transform: scaleY(-1);
&-info {
display: flex;
align-items: center;
margin-bottom: 15px;
img {
width: 27px;
height: 27px;
margin-right: 5px;
}
p {
font-weight: 700;
font-size: 22px;
line-height: 32px;
}
}
&-more {
font-size: 14px;
line-height: 22px;
}
@media (max-width: 900px) {
transform: none;
}
}
}
}

View File

@ -0,0 +1,113 @@
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { filteredExecutorTasks } from "@redux/projectsTrackerSlice";
import { removeLast, urlForLocal } from "@utils/helper";
import arrowDown from "assets/icons/arrows/selectArrow.png";
import close from "assets/icons/close.png";
import avatarMok from "assets/images/avatarMok.png";
import "./trackerSelectExecutor.scss";
const TrackerSelectExecutor = ({
selectedExecutor,
setSelectedExecutor,
deleteSelectedExecutor,
projectBoard
}) => {
const [selectExecutorOpen, setSelectedExecutorOpen] = useState(false);
const dispatch = useDispatch();
const initListeners = () => {
document.addEventListener("click", closeByClickingOut);
};
const closeByClickingOut = (event) => {
const path = event.path || (event.composedPath && event.composedPath());
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("tasks__head__executor") ||
div.classList.contains("tasks__head__executor-dropdown"))
)
) {
setSelectedExecutorOpen(false);
}
};
function executorFilter(user) {
dispatch(filteredExecutorTasks(user.user_id));
setSelectedExecutor(user);
}
useEffect(() => {
initListeners();
}, []);
if (selectedExecutor) {
return (
<div className="tasks__head__executor-selected">
<p>{removeLast(selectedExecutor.user.fio)}</p>
<img
className="avatar"
src={
selectedExecutor.user?.avatar
? urlForLocal(selectedExecutor.user.avatar)
: avatarMok
}
alt="avatar"
/>
<img
className="delete"
src={close}
alt="delete"
onClick={deleteSelectedExecutor}
/>
</div>
);
} else {
return (
<div
className="tasks__head__executor"
onClick={() => setSelectedExecutorOpen(!selectExecutorOpen)}
>
<p>Выберите исполнителя</p>
<img
className={selectExecutorOpen ? "open" : ""}
src={arrowDown}
alt="arrow"
/>
{selectExecutorOpen && (
<div className="tasks__head__executor-dropdown">
{projectBoard.projectUsers.map((user) => {
return (
<div
className="executor-dropdown__person"
key={user.user_id}
onClick={() => executorFilter(user)}
>
<p>{removeLast(user.user?.fio)}</p>
<img
src={
user.user?.avatar
? urlForLocal(user.user.avatar)
: avatarMok
}
alt="avatar"
/>
</div>
);
})}
</div>
)}
</div>
);
}
};
export default TrackerSelectExecutor;

View File

@ -0,0 +1,129 @@
.tasks {
&__head {
&__executor {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
margin-right: 10px;
border-radius: 8px;
border: 1px solid #e3e2e2;
padding: 2px 6px;
position: relative;
max-width: 190px;
width: 100%;
@media (max-width: 915px) {
margin-right: 0;
width: 100%;
max-width: none;
}
@media (max-width: 650px) {
border-color: gray;
}
&-selected {
display: flex;
align-items: center;
border-radius: 8px;
max-width: 220px;
width: 100%;
margin-right: 10px;
justify-content: center;
p {
color: #252c32;
font-weight: 400;
font-size: 14px;
line-height: 24px;
max-width: 155px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.avatar {
margin: 0 5px;
}
.delete {
cursor: pointer;
}
img {
display: flex;
width: 20px;
height: 20px;
}
@media (max-width: 915px) {
width: 100%;
max-width: none;
justify-content: start;
p {
font-size: 16px;
max-width: none;
}
}
}
p {
color: #252c32;
font-weight: 400;
font-size: 14px;
line-height: 24px;
}
img {
transition: all 0.15s ease;
margin-left: 5px;
}
.open {
transform: rotate(180deg);
}
&-dropdown {
position: absolute;
top: 33px;
left: 0;
background: white;
border-radius: 8px;
z-index: 5;
padding: 10px 10px;
display: flex;
flex-direction: column;
row-gap: 7px;
width: 100%;
.executor-dropdown__person {
display: flex;
justify-content: space-between;
align-items: center;
p {
max-width: 155px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@media (max-width: 915px) {
max-width: none;
}
}
img {
width: 20px;
height: 20px;
}
&:hover {
p {
font-weight: 600;
}
}
}
}
}
}
}

View File

@ -0,0 +1,275 @@
import React, { useEffect, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { useDispatch } from "react-redux";
import { useParams } from "react-router-dom";
import {
addNewTagToProject,
deleteTagProject,
setProjectBoardFetch
} from "@redux/projectsTrackerSlice";
import { apiRequest } from "@api/request";
import { useNotification } from "@hooks/useNotification";
import arrowDown from "assets/icons/arrows/selectArrow.png";
import close from "assets/icons/close.png";
import edit from "assets/icons/edit.svg";
import "./trackerTagList.scss";
const TrackerTagList = ({ projectBoard }) => {
const dispatch = useDispatch();
const projectId = useParams();
const { showNotification } = useNotification();
const [tagInfo, setTagInfo] = useState({ description: "", name: "" });
const [color, setColor] = useState("#aabbcc");
const [tags, setTags] = useState({
open: false,
add: false,
edit: false
});
function deleteTag(tagId) {
apiRequest("/mark/detach", {
method: "DELETE",
data: {
mark_id: tagId,
entity_type: 1,
entity_id: projectId.id
}
}).then(() => {
dispatch(deleteTagProject(tagId));
showNotification({
show: true,
text: "Тег удален",
type: "success"
});
});
}
function addNewTag() {
apiRequest("/mark/create", {
method: "POST",
data: {
title: tagInfo.description,
slug: tagInfo.name,
color: color,
status: 1
}
}).then((data) => {
apiRequest("/mark/attach", {
method: "POST",
data: {
mark_id: data.id,
entity_type: 1,
entity_id: projectId.id
}
}).then((data) => {
dispatch(addNewTagToProject(data.mark));
setTags((prevState) => ({
...prevState,
add: false
}));
showNotification({
show: true,
text: "Тег успешно создан",
type: "success"
});
});
});
}
function editTag() {
apiRequest("/mark/update", {
method: "PUT",
data: {
mark_id: tagInfo.editMarkId,
title: tagInfo.description,
slug: tagInfo.name,
color: color
}
}).then(() => {
dispatch(setProjectBoardFetch(projectId.id));
setTags((prevState) => ({
...prevState,
edit: false
}));
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
showNotification({
show: true,
text: "Тег успешно изменён",
type: "success"
});
});
}
const initListeners = () => {
document.addEventListener("click", closeByClickingOut);
};
const closeByClickingOut = (event) => {
const path = event.path || (event.composedPath && event.composedPath());
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("tasks__head__tags") ||
div.classList.contains("tags__list"))
)
) {
setTags({ open: false, add: false, edit: false });
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
}
};
useEffect(() => {
initListeners();
}, []);
return (
<div className="tasks__head__tags">
<div
className="tags__add"
onClick={() => {
setTags((prevState) => ({
...prevState,
open: !tags.open
}));
}}
>
<p>Список тегов</p>
<img className={tags.open ? "open" : ""} src={arrowDown} alt="arrow" />
</div>
{tags.open && (
<div className="tags__list">
{!tags.add && !tags.edit && (
<div
className="add-new-tag"
onClick={() =>
setTags((prevState) => ({
...prevState,
add: true
}))
}
>
<p>Добавить новый тег</p>
<span>+</span>
</div>
)}
{!tags.add && !tags.edit && (
<div className="tags__list__created">
{projectBoard.mark.map((tag) => {
return (
<div className="tag-item" key={tag.id}>
<div className="tag-item__info">
<span
className="tag-item__color"
style={{ background: tag.color }}
/>
<div>
<span className="tag-item__info__name">{tag.slug}</span>
<p className="tag-item__description">{tag.title}</p>
</div>
</div>
<div className="tag-item__images">
<img
src={edit}
alt="edit"
onClick={() => {
setTags((prevState) => ({
...prevState,
edit: true
}));
setTagInfo({
description: tag.title,
name: tag.slug,
editMarkId: tag.id
});
setColor(tag.color);
}}
/>
<img
onClick={() => deleteTag(tag.id)}
className="delete"
src={close}
alt="delete"
/>
</div>
</div>
);
})}
</div>
)}
{(tags.add || tags.edit) && (
<div className="form-tag">
<input
className="form-tag__input"
placeholder="Описание метки"
maxLength="25"
value={tagInfo.description}
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
description: e.target.value
}))
}
/>
<input
className="form-tag__input"
placeholder="Тег"
value={tagInfo.name}
maxLength="10"
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
name: e.target.value
}))
}
/>
<HexColorPicker color={color} onChange={setColor} />
<button
onClick={() => {
tags.add ? addNewTag() : editTag();
}}
className={
tagInfo.name && tagInfo.description
? "form-tag__btn"
: "form-tag__btn disable"
}
>
{tags.add ? "Добавить" : "Изменить"}
</button>
{(tags.add || tags.edit) && (
<button
className={"form-tag__btn"}
onClick={() => {
setTags((prevState) => ({
...prevState,
add: false,
edit: false
}));
setTagInfo({
description: "",
name: ""
});
setColor("#aabbcc");
}}
>
Отмена
</button>
)}
</div>
)}
</div>
)}
</div>
);
};
export default TrackerTagList;

View File

@ -0,0 +1,208 @@
.tasks {
&__head {
&__tags {
position: relative;
img {
transition: all 0.15s ease;
margin-left: 5px;
}
.open {
transform: rotate(180deg);
}
.tags {
&__add {
display: flex;
align-items: center;
margin: 0 10px;
column-gap: 5px;
cursor: pointer;
padding: 5px;
border-radius: 8px;
border: 1px solid #e3e2e2;
max-height: 30px;
p {
white-space: nowrap;
font-weight: 400;
font-size: 14px;
line-height: 17px;
}
span {
width: 14px;
height: 14px;
font-weight: 400;
line-height: 16px;
border-radius: 50px;
align-items: center;
justify-content: center;
display: flex;
background: #99b4f3;
color: white;
font-size: 14px;
transition: all 0.15s ease;
}
}
&__list {
position: absolute;
border-radius: 8px;
background: #d9d9d9;
z-index: 8;
top: 30px;
left: -35px;
width: 216px;
display: flex;
flex-direction: column;
@media (max-width: 900px) {
left: 0px;
}
.close {
cursor: pointer;
width: 20px;
height: 20px;
position: absolute;
right: 10px;
top: 2px;
}
&__created {
display: flex;
flex-direction: column;
row-gap: 8px;
margin-top: 8px;
padding: 0 8px 8px;
.tag-item {
display: flex;
align-items: center;
flex-direction: row;
justify-content: space-between;
column-gap: 5px;
padding: 0px 8px;
border-radius: 8px;
height: 40px;
max-height: 40px;
background: #fff;
&__description {
font-size: 12px;
word-break: break-word;
max-width: 115px;
max-height: 40px;
overflow: hidden;
text-wrap: wrap;
text-overflow: ellipsis;
}
&__color {
width: 22.25px;
height: 23.217px;
border-radius: 8px;
}
&__images {
display: flex;
flex-direction: column-reverse;
row-gap: 3px;
img {
height: 14px;
width: 14px;
cursor: pointer;
}
.delete {
width: 16px;
height: 16px;
}
}
&__info {
display: flex;
align-items: center;
column-gap: 10px;
&__name {
font-size: 12px;
font-weight: 600;
}
}
}
}
.add-new-tag {
display: flex;
align-items: center;
column-gap: 15px;
border-radius: 8px;
background: white;
color: #252c32;
height: 40px;
cursor: pointer;
justify-content: center;
margin: 8px 8px 0px;
p {
font-size: 15px;
}
span {
width: 19px;
height: 19px;
border-radius: 50px;
align-items: center;
justify-content: center;
display: flex;
background: #52b709;
color: white;
font-size: 16px;
transition: all 0.15s ease;
}
}
.form-tag {
display: flex;
flex-direction: column;
padding: 8px;
row-gap: 8px;
.arrow {
position: absolute;
cursor: pointer;
top: 5px;
width: 15px;
height: 15px;
transform: rotate(180deg);
}
&__input {
outline: none;
border-radius: 8px;
border: 1px solid #e3e2e2;
font-size: 15px;
padding: 5px;
}
&__btn {
outline: none;
border: none;
background: #252c32;
color: whitesmoke;
margin: 0 auto 0;
border-radius: 10px;
font-size: 15px;
padding: 5px 12px;
}
.disable {
opacity: 0.5;
pointer-events: none;
}
}
}
}
}
}
}

View File

@ -127,11 +127,8 @@ export const TrackerTaskComment = ({
"EasyImage", "EasyImage",
"Image", "Image",
"ImageCaption", "ImageCaption",
"ImageStyle",
"ImageToolbar",
"ImageUpload", "ImageUpload",
"MediaEmbed", "MediaEmbed"
"BlockQuote"
] ]
}} }}
onChange={(event, editor) => { onChange={(event, editor) => {

View File

@ -34,6 +34,11 @@ export const QuizPassingInformation = ({ setStartTest, uuid, timer }) => {
} }
dispatch(setQuestions(res)); dispatch(setQuestions(res));
setStartTest(true); setStartTest(true);
showNotification({
show: true,
text: "Тест успешно запущен",
type: "success"
});
restart( restart(
moment() moment()
.add(res[0]?.time_limit.split(":")[0], "hours") .add(res[0]?.time_limit.split(":")[0], "hours")

View File

@ -1,5 +1,7 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { Fallback } from "./Fallback";
class ErrorBoundary extends Component { class ErrorBoundary extends Component {
state = { state = {
error: null error: null
@ -13,7 +15,7 @@ class ErrorBoundary extends Component {
const { error } = this.state; const { error } = this.state;
if (error) { if (error) {
return <div>Что-то пошло не так =( {error}</div>; return <Fallback />;
} }
return this.props.children; return this.props.children;
} }

19
src/hoc/Fallback.jsx Normal file
View File

@ -0,0 +1,19 @@
import React from "react";
import rightArrow from "assets/icons/arrows/arrowRight.svg";
import logo from "assets/images/logo/ITguild.svg";
import "./fallback.scss";
export const Fallback = () => {
return (
<div className="fallback">
<img src={logo} alt="logo" className="logo" />
<h1>Произошла непредвиденная ошибка</h1>
<a href="/profile">
Вернуться назад
<img src={rightArrow} alt="arrow" />
</a>
</div>
);
};

24
src/hoc/fallback.scss Normal file
View File

@ -0,0 +1,24 @@
.fallback {
display: flex;
flex-direction: column;
align-items: center;
padding: 100px;
gap: 15px;
img {
max-width: 250px;
}
a {
display: flex;
column-gap: 10px;
align-items: center;
font-size: 20px;
color: black;
transition: all 0.3s ease;
&:hover {
scale: 1.1;
}
}
}

View File

@ -99,7 +99,7 @@ export const useFormValidation = (
setLoader(false); setLoader(false);
if ("errors" in data) { if ("errors" in data) {
return showNotificationError( return showNotificationError(
"Аккаунт с таким логином или email уже существуе" "Аккаунт с таким логином или email уже существует"
); );
} }
if (!data.id) { if (!data.id) {

View File

@ -3,11 +3,14 @@ import ReactDOM from "react-dom/client";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import App from "./App"; import App from "./App";
import ErrorBoundary from "./hoc/ErrorBoundary";
import "./index.css"; import "./index.css";
import { store } from "./store/store"; import { store } from "./store/store";
ReactDOM.createRoot(document.getElementById("root")).render( ReactDOM.createRoot(document.getElementById("root")).render(
<Provider store={store}> <ErrorBoundary>
<App /> <Provider store={store}>
</Provider> <App />
</Provider>
</ErrorBoundary>
); );

View File

@ -19,16 +19,16 @@ import cross from "assets/images/cross.png";
import "./auth.scss"; import "./auth.scss";
export const Auth = () => { export const Auth = () => {
const isAuth = useSelector(selectAuth); // const isAuth = useSelector(selectAuth);
let navigate = useNavigate(); // let navigate = useNavigate();
//
// const getToken = localStorage.getItem("auth_token");
const getToken = localStorage.getItem("auth_token"); // useEffect(() => {
// if (isAuth || getToken) {
useEffect(() => { // navigate("/profile");
if (isAuth || getToken) { // }
navigate("/profile"); // }, [getToken]);
}
}, [getToken]);
return ( return (
<section className="auth-partners"> <section className="auth-partners">

View File

@ -111,13 +111,6 @@ export const AuthForCandidate = () => {
<div className="auth-candidate"> <div className="auth-candidate">
<AuthHeader /> <AuthHeader />
<div className="container"> <div className="container">
<AuthBlock
resetModal={setModalReset}
title="Войти, если есть доступ"
description="Если вы получили доступ, пройдя
2 шага для входа, или хотите узнать
свои результаты в кабинете"
/>
<div className="auth-candidate__start"> <div className="auth-candidate__start">
<h2 className="auth-candidate__start__title"> <h2 className="auth-candidate__start__title">
Хочу в команду <span>IT-специалистов</span> Хочу в команду <span>IT-специалистов</span>

View File

@ -0,0 +1,104 @@
import React from "react";
import SVG from "react-inlinesvg";
import { Link } from "react-router-dom";
import { Footer } from "@components/Common/Footer/Footer";
import arrow from "assets/icons/arrows/arrowLanding.svg";
import authIcon from "assets/icons/authIcon.svg";
import clue from "assets/icons/landingClue.svg";
import codeBg from "assets/images/landingBackgroundCode.svg";
import cat from "assets/images/landingCat.png";
import "./landing.scss";
export const Landing = () => {
const opportunities = [
{
name: "<span>Аутстаффинг</span> сотрудников",
class: "outstaffing__employees",
img: cat
},
{
name: "Модуль для видеоконференций"
},
{
name: "Система контроля версий GIT"
},
{
name: "Управление задачами"
},
{
name: "Система для отчётности"
},
{
name: "Все наши предложения",
class: "outstaffing__offers",
img: arrow
}
];
return (
<section className="landing">
<div className="landing__container">
<div className="landing__head">
<h2 className="head__logo">ITGUILD</h2>
<Link className="head__signIn" to="/auth">
войти в систему
</Link>
<Link className="head__signUp" to="/auth">
<SVG src={authIcon} />
авторизация
</Link>
</div>
<div className="landing__info">
<p className="info__title">
<SVG className="code" src={codeBg} />
<span>Экосистема</span> для диджитализации бизнеса
</p>
<div className="landing__background">
<h3>ITGuild</h3>
<SVG className="clue" src={clue} />
<SVG className="code" src={codeBg} />
</div>
<div className="info__block">
<p>
Подберем и документально оформим IT-специалистов, после чего
передадим исполнителей под ваше руководство.
<br />
<br />
<span>Вы получаете полное управление над сотрудниками,</span> имея
возможность контролировать и заменять IT штат.
</p>
</div>
</div>
<div className="landing__opportunities">
{opportunities.map((opportunity, index) => {
return (
<div
className={
opportunity.class ? opportunity.class : "landing__opportunity"
}
key={index}
>
{opportunity.class ? (
<div>
<p dangerouslySetInnerHTML={{ __html: opportunity.name }} />
{opportunity.img ? (
<img src={opportunity.img} alt="img" />
) : (
""
)}
</div>
) : (
<p>{opportunity.name}</p>
)}
</div>
);
})}
</div>
<Footer />
</div>
</section>
);
};

View File

@ -0,0 +1,246 @@
.landing {
background: #EEEEEE;
min-height: 100vh;
padding: 20px 0;
font-family: "GT Eesti Pro Display";
&__container {
max-width: 1100px;
margin: 0 auto;
display: flex;
flex-direction: column;
position: relative;
height: 100%;
}
&__head {
display: flex;
column-gap: 35px;
align-items: center;
.head {
&__logo {
margin-bottom: 0;
color: rgba(74, 74, 74, 1);
font-size: 35px;
font-weight: 900;
position: relative;
&:before {
content: '';
position: absolute;
background: rgba(167, 202, 96, 1);
width: 31px;
height: 31px;
border-radius: 50px;
left: 39%;
top: -35px;
}
}
&__signIn {
padding: 12px 30px 15px;
color: rgba(30, 30, 30, 1);
font-size: 13px;
background: rgba(167, 202, 96, 1);
font-weight: 400;
border-radius: 32px;
}
&__signUp {
display: flex;
column-gap: 8px;
align-items: center;
color: rgba(131, 131, 131, 1);
font-weight: 400;
font-size: 13px;
text-decoration: underline;
}
}
}
&__info {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 75px;
column-gap: 45px;
.info {
&__title {
font-weight: 900;
font-size: 49px;
color: #4A4A4A;
max-width: 444px;
line-height: 1;
position: relative;
.code {
position: absolute;
left: -260px;
top: -55px;
}
span {
color: #A7CA60;
}
}
&__block {
backdrop-filter: blur(8.699999809265137px);
box-shadow: 10px 9px 14px 0 rgba(0, 0, 0, 0.03);
background: linear-gradient(137deg, rgba(255, 255, 255, 0.34) 0%, rgba(239, 239, 239, 0.34) 100%);
border: 0.5px solid;
border-image-source: linear-gradient(137.79deg, #FFFFFF 9.15%, #F4F4F4 76.22%);
border-radius: 8px;
padding: 59px 89px 68px 102px;
position: relative;
z-index: 1;
p {
font-weight: 250;
font-size: 14px;
line-height: 137%;
color: #4a4a4a;
span {
font-weight: 400;
}
}
}
}
}
&__background {
position: absolute;
right: -200px;
top: -100px;
h3 {
font-family: 'Geraspoheko';
color: rgba(255, 255, 255, 1);
font-size: 343px;
font-weight: 400;
margin-bottom: 0;
}
.clue {
width: 130px;
height: 120px;
top: 165px;
position: absolute;
right: 147px;
}
.code {
position: absolute;
width: 360px;
height: 134px;
right: -110px;
}
&:after {
content: '';
position: absolute;
width: 82px;
height: 82px;
border-radius: 50px;
background: rgba(167, 202, 96, 0.8);
right: 160px;
bottom: -75px;
}
}
&__opportunities {
margin: 120px 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
}
&__opportunity {
padding: 40px 60px;
p {
font-weight: 500;
font-size: 20px;
line-height: 107%;
color: rgba(131, 131, 131, 1);
}
}
&__opportunity:nth-child(-n+3) {
border-bottom: 1px solid rgba(245, 245, 245, 1);
}
&__opportunity:nth-child(-n+2) {
border-right: 1px solid rgba(245, 245, 245, 1);
}
&__opportunity:nth-child(4) {
border-right: 1px solid rgba(245, 245, 245, 1);
}
&__opportunity:nth-child(5) {
border-right: 1px solid rgba(245, 245, 245, 1);
}
.outstaffing__employees {
padding: 16px 12px 14px 2px;
border-right: 1px solid rgba(245, 245, 245, 1);
border-bottom: 1px solid rgba(245, 245, 245, 1);
div {
display: flex;
background: linear-gradient(95.54deg, #FFFFFF 5.13%, #EEEEEE 97.48%);
border-radius: 5px;
padding: 24px 0px 25px 33px;
height: 100%;
align-items: center;
position: relative;
p {
font-weight: 700;
font-size: 20px;
line-height: 21.4px;
color: rgba(74, 74, 74, 1);
max-width: 135px;
span {
color: rgba(167, 202, 96, 1);
}
}
}
img {
position: absolute;
right: -50px;
bottom: -15px;
}
}
.outstaffing__offers {
padding: 10px 0 25px 10px;
div {
display: flex;
background: rgba(255, 255, 255, 1);
border-radius: 5px;
height: 91px;
padding: 24px 35px 24px 42px;
align-items: center;
justify-content: space-between;
p {
color: rgba(167, 202, 96, 1);
font-weight: 500;
font-size: 20px;
line-height: 21.4px;
max-width: 215px;
}
img {
width: 27px;
height: 15px;
}
}
}
}

View File

@ -0,0 +1,33 @@
import React from "react";
import { useMemo } from "react";
import { useSelector } from "react-redux";
import { selectAuth } from "@redux/outstaffingSlice";
import { DeveloperPage } from "@pages/roles/DeveloperPage";
import { GuestPage } from "@pages/roles/GuestPage";
import { PartnerPage } from "@pages/roles/PartnerPage";
export const MainPage = () => {
const roleId = localStorage.getItem("role_status");
const isAuth = useSelector(selectAuth);
const user_roles = {
developer: 4,
partner: 18
};
const CurrentRolePage = useMemo(() => getRolePage(Number(roleId)), [isAuth]);
function getRolePage(roleId) {
switch (roleId) {
case user_roles.developer:
return DeveloperPage;
case user_roles.partner:
return PartnerPage;
default:
return GuestPage;
}
}
return <CurrentRolePage />;
};

View File

@ -6,6 +6,8 @@ import { getPartnerRequestInfo } from "@redux/outstaffingSlice";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
import { useNotification } from "@hooks/useNotification";
import { Footer } from "@components/Common/Footer/Footer"; import { Footer } from "@components/Common/Footer/Footer";
import { Navigation } from "@components/Navigation/Navigation"; import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs"; import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
@ -32,21 +34,25 @@ export const PartnerAddRequest = () => {
const [specializationList, setSpecializationList] = useState([]); const [specializationList, setSpecializationList] = useState([]);
const [levelList, setLevelList] = useState([]); const [levelList, setLevelList] = useState([]);
const [countList] = useState([1, 2, 3, 4, 5]); const [countList] = useState([1, 2, 3, 4, 5]);
const [locationList] = useState(["РФ", "Беларусь"]);
const [openSkillsSelect, setOpenSkillsSelect] = useState(false); const [openSkillsSelect, setOpenSkillsSelect] = useState(false);
const [openSpecializationList, setOpenSpecializationListOpen] = const [openSpecializationList, setOpenSpecializationListOpen] =
useState(false); useState(false);
const [openLevelList, setOpenLevelList] = useState(false); const [openLevelList, setOpenLevelList] = useState(false);
const [openCountList, setOpenCountList] = useState(false); const [openCountList, setOpenCountList] = useState(false);
const [openLocationList, setOpenLocationList] = useState(false);
const [editRequest, setEditRequest] = useState(false); const [editRequest, setEditRequest] = useState(false);
const [selectedSkills, setSelectedSkills] = useState([]); const [selectedSkills, setSelectedSkills] = useState([]);
const [selectedSpecialization, setSelectedSpecialization] = useState( const [selectedSpecialization, setSelectedSpecialization] = useState(
"Выберите специализацию" "Выберите специализацию"
); );
const [selectedLevel, setSelectedLevel] = useState("Выберите уровень"); const [selectedLevel, setSelectedLevel] = useState("Выберите уровень");
const [selectedLocation, setSelectedLocation] = useState("Выберите локацию");
const [selectedCount, setSelectedCount] = useState( const [selectedCount, setSelectedCount] = useState(
"Выберите кол-во сотрудников" "Выберите кол-во сотрудников"
); );
const [inputs, setInputs] = useState({ title: "", description: "" }); const [inputs, setInputs] = useState({ title: "", description: "" });
const { showNotification } = useNotification();
if ( if (
currentUrl[0] === "/profile/requests-edit" && currentUrl[0] === "/profile/requests-edit" &&
@ -120,6 +126,11 @@ export const PartnerAddRequest = () => {
} }
}).then(() => { }).then(() => {
navigate("/profile/requests"); navigate("/profile/requests");
showNotification({
show: true,
text: "Вакансия успешно изменена",
type: "success"
});
}); });
} else { } else {
apiRequest("/request/create-request", { apiRequest("/request/create-request", {
@ -138,6 +149,11 @@ export const PartnerAddRequest = () => {
} }
}).then(() => { }).then(() => {
navigate("/profile/requests"); navigate("/profile/requests");
showNotification({
show: true,
text: "Вакансия успешно создана",
type: "success"
});
}); });
} }
}; };
@ -164,6 +180,7 @@ export const PartnerAddRequest = () => {
setOpenSpecializationListOpen(false); setOpenSpecializationListOpen(false);
setOpenLevelList(false); setOpenLevelList(false);
setOpenCountList(false); setOpenCountList(false);
setOpenLocationList(false);
} }
}; };
@ -189,280 +206,329 @@ export const PartnerAddRequest = () => {
? "Страница редактирования заявки" ? "Страница редактирования заявки"
: "Страница добавления заявки"} : "Страница добавления заявки"}
</h2> </h2>
<div className="partner-add-request__section"> <div className="partner-add-request__main">
<div className="partner-add-request__form"> <div className="partner-add-request__section">
<div className="partner-add-request__form__block form__block"> <div className="partner-add-request__form">
<h3 className="form__block__title">Данные открытой позиции</h3> <div className="partner-add-request__form__block form__block">
<div className="form__block__section"> <h3 className="form__block__title">Данные открытой позиции</h3>
<h3>Название вакансии</h3> <div className="form__block__section">
<div className="form__block__section__input"> <h3>Название вакансии</h3>
<input <div className="form__block__section__input">
value={inputs.title} <input
onChange={(e) => value={inputs.title}
setInputs((prevValue) => ({ onChange={(e) =>
...prevValue, setInputs((prevValue) => ({
title: e.target.value ...prevValue,
})) title: e.target.value
} }))
type="text" }
placeholder="Вакансия" type="text"
/> placeholder="Вакансия"
</div>
</div>
<div className="form__block__section">
<h3>Выберите специализацию</h3>
<div
className="form__block__section__selects"
onClick={() => {
setOpenSpecializationListOpen(!openSpecializationList);
}}
>
<div className="form__block__section__select">
<span>
{typeof selectedSpecialization === "string"
? selectedSpecialization
: selectedSpecialization.name}
</span>
<img
className={openSpecializationList ? "rotate" : ""}
src={arrowDown}
/> />
</div> </div>
</div> </div>
{openSpecializationList && <div className="form__block__section">
Boolean(specializationList.length) && ( <h3>Выберите специализацию</h3>
<div className="form__block__dropDown"> <div
{specializationList.map((specialization) => { className="form__block__section__selects"
onClick={() => {
setOpenSpecializationListOpen(!openSpecializationList);
}}
>
<div className="form__block__section__select">
<span>
{typeof selectedSpecialization === "string"
? selectedSpecialization
: selectedSpecialization.name}
</span>
<img
className={openSpecializationList ? "rotate" : ""}
src={arrowDown}
/>
</div>
</div>
{openSpecializationList &&
Boolean(specializationList.length) && (
<div className="form__block__dropDown">
{specializationList.map((specialization) => {
return (
<p
key={specialization.id}
onClick={() => {
setOpenSpecializationListOpen(false);
setSelectedSpecialization(specialization);
}}
>
{specialization.name}
</p>
);
})}
</div>
)}
</div>
<div className="form__block__section">
<h3>Навыки</h3>
<div
className="form__block__skills"
onClick={() => {
setOpenSkillsSelect(true);
}}
>
{Boolean(selectedSkills.length) &&
selectedSkills.map((skill, index) => {
return ( return (
<p <div className="skill" key={`selected-${skill.id}`}>
key={specialization.id} <span>{skill.name}</span>
<img
src={deleteIcon}
alt="delete"
onClick={() => {
setSkills((prevArray) => [...prevArray, skill]);
setFilteredSkills((prevArray) => [
...prevArray,
skill
]);
setSelectedSkills(
selectedSkills.filter((skill, indexSkill) => {
return indexSkill !== index;
})
);
}}
/>
</div>
);
})}
<input
type="text"
placeholder="Выберите навыки"
onChange={(e) => {
setFilteredSkills(
skills.filter((skill) => {
return skill.name
.toLowerCase()
.includes(e.target.value.toLowerCase());
})
);
}}
/>
</div>
{openSkillsSelect && Boolean(filteredSkills.length) && (
<div className="form__block__dropDown">
{filteredSkills.map((skill, index) => {
return (
<span
key={skill.id}
onClick={() => { onClick={() => {
setOpenSpecializationListOpen(false); setSelectedSkills((prevArray) => [
setSelectedSpecialization(specialization); ...prevArray,
skill
]);
setFilteredSkills(
filteredSkills.filter((skill, skillIndex) => {
return skillIndex !== index;
})
);
setSkills(
skills.filter((initSkill) => {
return initSkill.id !== skill.id;
})
);
setOpenSkillsSelect(false);
}} }}
> >
{specialization.name} {skill.name}
</span>
);
})}
</div>
)}
</div>
</div>
<div className="partner-add-request__form__block form__block">
<h3 className="form__block__title">Квалификация</h3>
<div className="form__block__section">
<h3>Выберите уровень знаний </h3>
<div
className="form__block__section__select"
onClick={() => setOpenLevelList(!openLevelList)}
>
<span>
{typeof selectedLevel === "string"
? selectedLevel
: selectedLevel.name}
</span>
<img
className={openLevelList ? "rotate" : ""}
src={arrowDown}
/>
</div>
{openLevelList &&
Boolean(Object.values(levelList).length) && (
<div className="form__block__dropDown">
{Object.values(levelList).map((level, index) => {
return (
<p
key={level}
onClick={() => {
setOpenLevelList(false);
setSelectedLevel({
name: level,
id: index + 1
});
}}
>
{level}
</p>
);
})}
</div>
)}
</div>
<div className="form__block__section">
<h3>Введите необходимое описание</h3>
<textarea
value={inputs.description}
onChange={(e) =>
setInputs((prevValue) => ({
...prevValue,
description: e.target.value
}))
}
/>
</div>
<div className="form__block__section">
<h3>Необходимое количество человек на позицию</h3>
<div
className="form__block__section__select"
onClick={() => setOpenCountList(true)}
>
<span>{selectedCount}</span>
<img
className={openCountList ? "rotate" : ""}
src={arrowDown}
/>
</div>
{openCountList && (
<div className="form__block__dropDown">
{countList.map((count) => {
return (
<p
key={count}
onClick={() => {
setOpenCountList(false);
setSelectedCount(count);
}}
>
{count}
</p> </p>
); );
})} })}
</div> </div>
)} )}
</div>
<div className="form__block__section">
<h3>Навыки</h3>
<div
className="form__block__skills"
onClick={() => {
setOpenSkillsSelect(true);
}}
>
{Boolean(selectedSkills.length) &&
selectedSkills.map((skill, index) => {
return (
<div className="skill" key={`selected-${skill.id}`}>
<span>{skill.name}</span>
<img
src={deleteIcon}
alt="delete"
onClick={() => {
setSkills((prevArray) => [...prevArray, skill]);
setFilteredSkills((prevArray) => [
...prevArray,
skill
]);
setSelectedSkills(
selectedSkills.filter((skill, indexSkill) => {
return indexSkill !== index;
})
);
}}
/>
</div>
);
})}
<input
type="text"
placeholder="Выберите навыки"
onChange={(e) => {
setFilteredSkills(
skills.filter((skill) => {
return skill.name
.toLowerCase()
.includes(e.target.value.toLowerCase());
})
);
}}
/>
</div> </div>
{openSkillsSelect && Boolean(filteredSkills.length) && (
<div className="form__block__dropDown">
{filteredSkills.map((skill, index) => {
return (
<span
key={skill.id}
onClick={() => {
setSelectedSkills((prevArray) => [
...prevArray,
skill
]);
setFilteredSkills(
filteredSkills.filter((skill, skillIndex) => {
return skillIndex !== index;
})
);
setSkills(
skills.filter((initSkill) => {
return initSkill.id !== skill.id;
})
);
setOpenSkillsSelect(false);
}}
>
{skill.name}
</span>
);
})}
</div>
)}
</div> </div>
</div> </div>
<div className="partner-add-request__form__block form__block"> <div className="partner-add-request__info">
<h3 className="form__block__title">Квалификация</h3> <div className="partner-add-request__info__block">
<div className="form__block__section"> <div className="partner-add-request__info__block__title">
<h3>Выберите уровень знаний </h3> <img src={processImg} alt="process" />
<div <h4>Процесс:</h4>
className="form__block__section__select"
onClick={() => setOpenLevelList(!openLevelList)}
>
<span>
{typeof selectedLevel === "string"
? selectedLevel
: selectedLevel.name}
</span>
<img
className={openLevelList ? "rotate" : ""}
src={arrowDown}
/>
</div> </div>
{openLevelList && Boolean(Object.values(levelList).length) && ( <p>
<div className="form__block__dropDown"> При аутстаффе мы предоставляем вам IT-специалистов при этом
{Object.values(levelList).map((level, index) => { они находятся в нашем штате.
return ( <br />
<p <br />
key={level} Вы сможете прособеседовать наших специалистов, посмотреть
onClick={() => { проекты и Git.
setOpenLevelList(false); </p>
setSelectedLevel({ name: level, id: index + 1 });
}}
>
{level}
</p>
);
})}
</div>
)}
</div> </div>
<div className="form__block__section"> <div className="partner-add-request__info__block">
<h3>Введите необходимое описание</h3> <div className="partner-add-request__info__block__title">
<textarea <img src={reportImg} alt="reportImg" />
value={inputs.description} <h4>Отчетность:</h4>
onChange={(e) =>
setInputs((prevValue) => ({
...prevValue,
description: e.target.value
}))
}
/>
</div>
<div className="form__block__section">
<h3>Необходимое количество человек на позицию</h3>
<div
className="form__block__section__select"
onClick={() => setOpenCountList(true)}
>
<span>{selectedCount}</span>
<img
className={openCountList ? "rotate" : ""}
src={arrowDown}
/>
</div> </div>
{openCountList && ( <p>
<div className="form__block__dropDown"> Вы можете обратиться к специалисту напрямую.
{countList.map((count) => { <br />
return ( <br />
<p Каждый день специалисты описывают выполненные работы и
key={count} затраченные на это часы.
onClick={() => { <br />
setOpenCountList(false); <br />
setSelectedCount(count); Можем выделить руководителя проекта и тестировщиков.
}} </p>
>
{count}
</p>
);
})}
</div>
)}
</div> </div>
<div className="form__block__buttons"> <div className="partner-add-request__info__block">
<Link to="/profile/requests" className="form__block__cancel"> <div className="partner-add-request__info__block__title">
Отмена <img src={documentsImg} alt="documentsImg" />
</Link> <h4>
<button Обмен <br />
onClick={() => handler()} документами:
className={ </h4>
disableBtn() </div>
? "form__block__save" <p>
: "form__block__save disable" В Личном кабинете платформы получайте отчеты выполненных работ
} и счеты на согласование и оплату
> </p>
Сохранить
</button>
</div> </div>
</div> </div>
</div> </div>
<div className="partner-add-request__info"> <div className="partner-add-request__special">
<div className="partner-add-request__info__block"> <h4>Основные требования по вакансии</h4>
<div className="partner-add-request__info__block__title"> <div className="form__block__section special__select">
<img src={processImg} alt="process" /> <h3>Локация</h3>
<h4>Процесс:</h4> <div
className="form__block__section__select"
onClick={() => setOpenLocationList(true)}
>
<span>{selectedLocation}</span>
<img
className={openLocationList ? "rotate" : ""}
src={arrowDown}
/>
</div> </div>
<p> {openLocationList && (
При аутстаффе мы предоставляем вам IT-специалистов при этом они <div className="form__block__dropDown">
находятся в нашем штате. {locationList.map((location, index) => {
<br /> return (
<br /> <p
Вы сможете прособеседовать наших специалистов, посмотреть key={index}
проекты и Git. onClick={() => {
</p> setOpenLocationList(false);
setSelectedLocation(location);
}}
>
{location}
</p>
);
})}
</div>
)}
</div> </div>
<div className="partner-add-request__info__block"> <div className="form__block__section">
<div className="partner-add-request__info__block__title"> <h3>Ставка</h3>
<img src={reportImg} alt="reportImg" /> <div className="form__block__section__input special__select">
<h4>Отчетность:</h4> <input type="text" placeholder="Оплата" />
</div> </div>
<p>
Вы можете обратиться к специалисту напрямую.
<br />
<br />
Каждый день специалисты описывают выполненные работы и
затраченные на это часы.
<br />
<br />
Можем выделить руководителя проекта и тестировщиков.
</p>
</div> </div>
<div className="partner-add-request__info__block"> <div className="form__block__buttons">
<div className="partner-add-request__info__block__title"> <Link to="/profile/requests" className="form__block__cancel">
<img src={documentsImg} alt="documentsImg" /> Отмена
<h4> </Link>
Обмен <br /> <button
документами: onClick={() => handler()}
</h4> className={
</div> disableBtn()
? "form__block__save"
: "form__block__save disable"
}
>
Сохранить
</button>
<p> <p>
В Личном кабинете платформы получайте отчеты выполненных работ и Нажимая "Сохранить", вы соглашаетесь с Правилами обработки и
счеты на согласование и оплату использования персональных данных
</p> </p>
</div> </div>
</div> </div>

View File

@ -23,6 +23,31 @@
line-height: 32px; line-height: 32px;
} }
&__main {
display: flex;
flex-direction: column;
row-gap: 30px;
}
&__special {
background: rgba(255, 255, 255, 1);
border-radius: 12px;
padding: 41px 45px 35px 55px;
display: flex;
flex-direction: column;
h4 {
font-weight: 700;
color: rgba(91, 104, 113, 1);
font-size: 20px;
line-height: 24px;
}
.special__select {
max-width: 450px;
}
}
&__section { &__section {
margin-top: 25px; margin-top: 25px;
display: flex; display: flex;
@ -99,6 +124,7 @@
font-size: 15px; font-size: 15px;
line-height: 18px; line-height: 18px;
outline: none; outline: none;
width: 100%;
} }
} }
@ -164,13 +190,23 @@
&__buttons { &__buttons {
display: flex; display: flex;
margin-top: 50px;
button { button {
max-width: 150px; max-width: 150px;
width: 100%; width: 100%;
height: 40px; height: 40px;
} }
p {
font-weight: 300;
font-size: 12px;
line-height: 18px;
color: rgba(0, 0, 0, 1);
margin-left: 50px;
max-width: 360px;
display: flex;
align-items: center;
}
} }
&__cancel { &__cancel {
@ -321,7 +357,7 @@
background: #ffffff; background: #ffffff;
border-radius: 12px; border-radius: 12px;
width: 100%; width: 100%;
padding: 74px 48px 136px 62px; padding: 74px 48px 67px 62px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
row-gap: 61px; row-gap: 61px;

View File

@ -14,6 +14,8 @@ import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
import { useNotification } from "@hooks/useNotification";
import { Footer } from "@components/Common/Footer/Footer"; import { Footer } from "@components/Common/Footer/Footer";
import { Loader } from "@components/Common/Loader/Loader"; import { Loader } from "@components/Common/Loader/Loader";
import ModalLayout from "@components/Common/ModalLayout/ModalLayout"; import ModalLayout from "@components/Common/ModalLayout/ModalLayout";
@ -37,6 +39,7 @@ export const PartnerBid = () => {
const requestId = useSelector(getPartnerRequestId); const requestId = useSelector(getPartnerRequestId);
const partnerRequests = useSelector(getPartnerRequests); const partnerRequests = useSelector(getPartnerRequests);
const navigate = useNavigate(); const navigate = useNavigate();
const { showNotification } = useNotification();
if (!requestId) { if (!requestId) {
return <Navigate to="/profile/requests" replace />; return <Navigate to="/profile/requests" replace />;
@ -61,6 +64,11 @@ export const PartnerBid = () => {
} }
}).then(() => { }).then(() => {
navigate("/profile/requests"); navigate("/profile/requests");
showNotification({
show: true,
text: "Вакансия удалена",
type: "success"
});
}); });
}; };

View File

@ -1,7 +1,7 @@
import moment from "moment/moment"; import moment from "moment/moment";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Navigate, useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { getRequestDates, setRequestDate } from "@redux/reportSlice"; import { getRequestDates, setRequestDate } from "@redux/reportSlice";

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { Link, Navigate } from "react-router-dom"; import { Link } from "react-router-dom";
import { getPartnerEmployees } from "@redux/outstaffingSlice"; import { getPartnerEmployees } from "@redux/outstaffingSlice";
@ -16,12 +16,7 @@ import "./partnerEmployees.scss";
export const PartnerEmployees = () => { export const PartnerEmployees = () => {
const partnerEmployees = useSelector(getPartnerEmployees); const partnerEmployees = useSelector(getPartnerEmployees);
// if (
// localStorage.getItem("role_status") !== "18" ||
// !partnerEmployees.length
// ) {
// return <Navigate to="/profile/categories" replace />;
// }
return ( return (
<div className="partner-employees"> <div className="partner-employees">
<ProfileHeader /> <ProfileHeader />

View File

@ -265,7 +265,7 @@
height: 46px; height: 46px;
font-weight: 400; font-weight: 400;
font-size: 15px; font-size: 15px;
line-height: 32px; line-height: 20px;
transition: 0.3s all ease; transition: 0.3s all ease;
a { a {
@ -280,7 +280,7 @@
color: white; color: white;
font-weight: 700; font-weight: 700;
font-size: 20px; font-size: 20px;
margin-right: 8px; margin-left: 8px;
} }
&:hover { &:hover {

View File

@ -139,7 +139,6 @@
&__body { &__body {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-around;
margin-top: 50px; margin-top: 50px;
} }

View File

@ -1,5 +1,6 @@
import { getTheme } from "@table-library/react-table-library/baseline"; import { getTheme } from "@table-library/react-table-library/baseline";
import { CompactTable } from "@table-library/react-table-library/compact"; import { CompactTable } from "@table-library/react-table-library/compact";
import { usePagination } from "@table-library/react-table-library/pagination";
import { useSort } from "@table-library/react-table-library/sort"; import { useSort } from "@table-library/react-table-library/sort";
import { useTheme } from "@table-library/react-table-library/theme"; import { useTheme } from "@table-library/react-table-library/theme";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
@ -43,12 +44,28 @@ export const PartnerCategories = () => {
const theme = useTheme(getTheme()); const theme = useTheme(getTheme());
const [nodes, setNodes] = useState([]); const [nodes, setNodes] = useState([]);
const [initialNodes, setInitialNodes] = useState([]); const [initialNodes, setInitialNodes] = useState([]);
const [activeTab, setActiveTab] = useState(1);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const tabs = [
{
id: 1,
name: "Все"
},
{
id: 2,
name: "Фронтенд"
},
{
id: 3,
name: "Бэкенд"
}
];
const COLUMNS = [ const COLUMNS = [
{ {
label: "Аватар", label: "",
renderCell: (item) => ( renderCell: (item) => (
<img <img
className="table__avatar" className="table__avatar"
@ -59,18 +76,29 @@ export const PartnerCategories = () => {
}, },
{ {
label: "ФИО", label: "ФИО",
renderCell: (item) => item?.employee.fio, renderCell: (item) => <p>{item?.employee.fio}</p>,
sort: { sortKey: "NAME" } sort: { sortKey: "NAME" }
}, },
{ {
label: "Резюме", label: "Резюме",
renderCell: (item) => (
<Link className="table__link" to={`/candidate/${item.user_id}`}>
Резюме
<div className="img__wrapper">
<img src={rightArrow} alt="arrow" />
</div>
</Link>
)
},
{
label: "Отчетность",
renderCell: (item) => ( renderCell: (item) => (
<Link <Link
className="table__link" className="table__link"
to={`/profile/employees/report/${item.user_id}`} to={`/profile/employees/report/${item.user_id}`}
> >
Подробный отчет Подробный отчет
<div> <div className="img__wrapper">
<img src={rightArrow} alt="arrow" /> <img src={rightArrow} alt="arrow" />
</div> </div>
</Link> </Link>
@ -93,6 +121,13 @@ export const PartnerCategories = () => {
} }
); );
const pagination = usePagination(data, {
state: {
page: 0,
size: 10
}
});
useEffect(() => { useEffect(() => {
setLoader(true); setLoader(true);
apiRequest("/project/my-employee").then((el) => { apiRequest("/project/my-employee").then((el) => {
@ -237,12 +272,28 @@ export const PartnerCategories = () => {
<div className="partner-categories__items"> <div className="partner-categories__items">
{Boolean(initialNodes.length) ? ( {Boolean(initialNodes.length) ? (
<> <>
<div className="table__tabs">
{tabs.map((tab) => {
return (
<button
onClick={() => setActiveTab(tab.id)}
key={tab.id}
className={`table__tab ${
tab.id === activeTab ? "table__tab--active" : ""
}`}
>
{tab.name}
</button>
);
})}
</div>
<label className="table__search" htmlFor="search"> <label className="table__search" htmlFor="search">
Поиск по имени: Поиск по имени:
<input <input
id="search" id="search"
type="text" type="text"
value={search} value={search}
placeholder="Поиск по сотрудникам"
onChange={handleSearch} onChange={handleSearch}
/> />
</label> </label>
@ -251,7 +302,56 @@ export const PartnerCategories = () => {
data={data} data={data}
theme={theme} theme={theme}
sort={sort} sort={sort}
pagination={pagination}
/> />
<div className="table__pagination">
<button
className={
pagination.state.page === 0 ? "switch disable" : "switch"
}
type="button"
disabled={pagination.state.page === 0}
onClick={() =>
pagination.fns.onSetPage(pagination.state.page - 1)
}
>
{"<"}
</button>
<span className="table__pages">
{pagination.state.getPages(data.nodes).map((_, index) => (
<button
key={index}
type="button"
className={
pagination.state.page === index
? "page page--active "
: "page"
}
onClick={() => pagination.fns.onSetPage(index)}
>
{index + 1}
</button>
))}
</span>
<button
className={
pagination.state.page + 1 ===
pagination.state.getPages(data.nodes).length
? "switch disable"
: "switch"
}
type="button"
disabled={
pagination.state.page + 1 ===
pagination.state.getPages(data.nodes).length
}
onClick={() =>
pagination.fns.onSetPage(pagination.state.page + 1)
}
>
{">"}
</button>
</div>
</> </>
) : ( ) : (
<div className="partner-categories__empty"> <div className="partner-categories__empty">

View File

@ -13,10 +13,13 @@
&__items { &__items {
display: flex; display: flex;
gap: 10px;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
background: white;
padding: 20px 0 30px;
border-radius: 7px;
} }
&__empty { &__empty {
font-size: 18px; font-size: 18px;
font-weight: 500; font-weight: 500;
@ -161,8 +164,18 @@
display: flex; display: flex;
width: 100%; width: 100%;
gap: 10px; gap: 10px;
font-size: 18px;
align-items: center; align-items: center;
padding: 0 18px 35px;
margin-bottom: 0;
font-size: 16px;
input {
background: #F0F2F5;
outline: none;
border-radius: 6px;
padding: 6px;
border: none;
}
} }
&__avatar { &__avatar {
@ -175,6 +188,108 @@
color: black; color: black;
font-size: 16px; font-size: 16px;
align-items: center; align-items: center;
.img__wrapper {
width: 22px;
height: 22px;
border-radius: 50px;
background: #D9D7D7;
display: flex;
align-items: center;
justify-content: center;
img {
width: 14px;
}
}
}
&__tabs {
margin: 0 auto 36px 18px;
background: rgba(240, 242, 245, 1);
padding: 4px 8px 4px 8px;
border-radius: 6px;
display: flex;
}
&__tab {
color: rgba(46, 58, 89, 1);
font-size: 15px;
outline: none;
background: none;
border: none;
padding: 0 12px;
&--active {
background: rgba(255, 255, 255, 1);
border-radius: 5px;
font-size: 16px;
}
}
&__pagination {
width: 100%;
display: flex;
font-size: 14px;
align-items: center;
padding: 39px 34px 0;
column-gap: 12px;
button {
font-size: 14px;
width: 32px;
border-radius: 5px;
height: 32px;
color: #2E3A59;
}
.switch {
border: none;
background: #F0F2F5;
font-weight: 600;
}
.disable {
opacity: 0.7;
}
}
&__pages {
display: flex;
column-gap: 4px;
color: black;
background: white;
.page {
border: 1px solid #E8ECF8;
background: none;
&--active {
border: none;
background: #9DA65D;
color: white;
}
}
}
}
table {
th {
border-top: 0;
border-bottom: 1px solid #EDEDED;
color: #1458DD;
font-size: 14px;
font-weight: 700;
}
td {
border-top: 0;
border-bottom: 1px solid #EDEDED;
color: #2E3A59;
p {
font-weight: 500;
}
} }
} }
} }

View File

@ -11,6 +11,7 @@ import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs"; import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader"; import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import financeIcon from "assets/icons/financeIcon.png";
import paymentIcon from "assets/icons/paymentIcon.png"; import paymentIcon from "assets/icons/paymentIcon.png";
import settingIcon from "assets/icons/settingIcon.png"; import settingIcon from "assets/icons/settingIcon.png";
import summaryIcon from "assets/icons/summaryIcon.png"; import summaryIcon from "assets/icons/summaryIcon.png";
@ -28,34 +29,40 @@ export const Profile = () => {
const [profileItemsInfo] = useState({ const [profileItemsInfo] = useState({
developer: [ developer: [
{ {
path: "profile/calendar", path: "profile",
img: reportsIcon, img: paymentIcon,
title: "Ваша отчетность", title: "Работа в IT <br/>открытые запросы",
description: "<span></span>Отработанных в этом месяце часов" description: "Перейдите чтобы посмотреть <br/>открытые позиции"
}, },
{ {
path: "profile/summary", path: "profile/summary",
img: summaryIcon, img: summaryIcon,
title: "Резюме", title: "Резюме",
description: "Ваше резюме<br/><span>заполнено</span>" description: "Ваше резюме <br/><span>заполнено</span>"
}, },
{ {
path: "profile/tracker", path: "profile/tracker",
img: timerIcon, img: timerIcon,
title: "Трекер времени", title: "Трекер <br/>времени",
description: "Сколько времени занимает<br/> выполнение задач" description: "Сколько времени занимает <br/>выполнение задач"
},
{
path: "profile/payouts",
img: financeIcon,
title: "Выплаты и <br/>финансы",
description: "У вас <span>подтвержден</span> <br/>статус самозанятого"
}, },
// {
// path: "profile/payouts",
// img: paymentIcon,
// title: "Выплаты",
// description: "У вас <span>подтвержден</span><br/> статус самозанятого"
// },
{ {
path: "profile/settings", path: "profile/settings",
img: settingIcon, img: settingIcon,
title: "Настройки профиля", title: "Настройки <br/>профиля",
description: "Перейдите чтобы начать<br/> редактирование" description: "Перейдите чтобы начать <br/>редактирование"
},
{
path: "profile/calendar",
img: reportsIcon,
title: "Ваша <br/>отчетность",
description: "<span></span>Отработанных в этом месяце часов"
} }
], ],
partner: [ partner: [
@ -64,31 +71,31 @@ export const Profile = () => {
img: reportsIcon, img: reportsIcon,
title: "Мои вакансии", title: "Мои вакансии",
description: description:
"<span>У вас 2 вакансии<br/></span>открытые от лица компании" "Ваши открытые вакансии, <br/><span>которыми вы можете управлять</span>"
}, },
{ {
path: "profile/employees", path: "profile/employees",
img: summaryIcon, img: summaryIcon,
title: "Данные персонала", title: "Данные персонала",
description: "Наши специалисты <br/><span>уже работающие у вас</span>" description: "Наши специалисты, <br/><span>уже работающие у вас</span>"
}, },
{ {
path: "profile/tracker", path: "profile/tracker",
img: timerIcon, img: timerIcon,
title: "Трекер времени", title: "Трекер времени",
description: "Контроль времени и<br/> выполнение задач" description: "Контроль времени и <br/>выполнение задач"
}, },
// { // {
// path: "profile/treaties", // path: "profile/treaties",
// img: paymentIcon, // img: paymentIcon,
// title: "Договоры и отчетность", // title: "Договоры и отчетность",
// description: "Ключевые условия<br/> договора" // description: "Ключевые условия <br/>договора"
// }, // },
{ {
path: "profile/settings", path: "profile/settings",
img: settingIcon, img: settingIcon,
title: "Настройки профиля", title: "Настройки профиля",
description: "Перейдите чтобы начать<br/> редактирование" description: "Перейдите чтобы начать <br/>редактирование"
} }
] ]
}); });
@ -100,17 +107,10 @@ export const Profile = () => {
<div className="container"> <div className="container">
<ProfileBreadcrumbs links={[{ name: "Главная", link: "/profile" }]} /> <ProfileBreadcrumbs links={[{ name: "Главная", link: "/profile" }]} />
<h2 className="profile__title"> <h2 className="profile__title">
{user === "developer" ? ( <span>
<span> <p>Добрый день,&nbsp;</p>
<p>Добрый день,&nbsp;</p> {profileInfo?.fio || profileInfo?.username}
{profileInfo?.fio || profileInfo?.username} </span>
</span>
) : (
<span>
<p>Добрый день,&nbsp;</p>
{profileInfo?.fio || profileInfo?.username}
</span>
)}
</h2> </h2>
<div className="summary__info"> <div className="summary__info">
<div className="summary__person"> <div className="summary__person">
@ -122,14 +122,11 @@ export const Profile = () => {
alt="avatar" alt="avatar"
/> />
<p className="summary__name"> <p className="summary__name">
{user === "developer" ? ( <span>
<span> {profileInfo?.fio || profileInfo?.username}
{profileInfo?.fio || profileInfo?.username},{" "} {user === "developer" &&
{profileInfo?.specification} разработчик `, ${profileInfo?.specification} разработчик`}
</span> </span>
) : (
<span>{profileInfo?.fio || profileInfo?.username}</span>
)}
</p> </p>
</div> </div>
</div> </div>

View File

@ -1,20 +1,15 @@
import moment from "moment"; import moment from "moment";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { import {
activeLoader, activeLoader,
addNewTagToProject,
deleteTagProject,
filterCreatedByMe, filterCreatedByMe,
filteredExecutorTasks,
filteredParticipateTasks, filteredParticipateTasks,
getBoarderLoader, getBoarderLoader,
getProjectBoard, getProjectBoard,
modalToggle, modalToggle,
movePositionProjectTask,
moveProjectTask, moveProjectTask,
setColumnId, setColumnId,
setColumnName, setColumnName,
@ -23,7 +18,7 @@ import {
setToggleTab setToggleTab
} from "@redux/projectsTrackerSlice"; } from "@redux/projectsTrackerSlice";
import { removeLast, urlForLocal } from "@utils/helper"; import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
@ -39,25 +34,20 @@ import TrackerModal from "@components/Modal/Tracker/TrackerModal/TrackerModal";
import { Navigation } from "@components/Navigation/Navigation"; import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs"; import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader"; import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import TrackerSelectColumn from "@components/TrackerSelectColumn/TrackerSelectColumn"; import TrackerCardTask from "@components/TrackerCardTask/TrackerCardTask";
import TrackerSelectExecutor from "@components/TrackerSelectExecutor/TrackerSelectExecutor";
import TrackerTagList from "@components/TrackerTagList/TrackerTagList";
import arrow from "assets/icons/arrows/arrowCalendar.png"; import arrow from "assets/icons/arrows/arrowRight.png";
import arrowDown from "assets/icons/arrows/selectArrow.png";
import category from "assets/icons/category.svg"; import category from "assets/icons/category.svg";
import close from "assets/icons/close.png";
import commentsBoard from "assets/icons/commentsBoard.svg";
import del from "assets/icons/delete.svg"; import del from "assets/icons/delete.svg";
import edit from "assets/icons/edit.svg"; import edit from "assets/icons/edit.svg";
import filesBoard from "assets/icons/filesBoard.svg";
import trackerNoTasks from "assets/icons/trackerNoTasks.svg"; import trackerNoTasks from "assets/icons/trackerNoTasks.svg";
import project from "assets/icons/trackerProject.svg"; import project from "assets/icons/trackerProject.svg";
import tasks from "assets/icons/trackerTasks.svg"; import tasks from "assets/icons/trackerTasks.svg";
import accept from "assets/images/accept.png"; import accept from "assets/images/accept.png";
import archive from "assets/images/archiveIcon.png";
import avatarMok from "assets/images/avatarMok.png"; import avatarMok from "assets/images/avatarMok.png";
import { getCorrectDate } from "../../utils/calendarHelper";
export const ProjectTracker = () => { export const ProjectTracker = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const projectId = useParams(); const projectId = useParams();
@ -66,43 +56,22 @@ export const ProjectTracker = () => {
const [selectedTab, setSelectedTab] = useState(0); const [selectedTab, setSelectedTab] = useState(0);
const [priorityTask, setPriorityTask] = useState(0); const [priorityTask, setPriorityTask] = useState(0);
const [wrapperHover, setWrapperHover] = useState({}); const [wrapperHover, setWrapperHover] = useState({});
const [taskHover, setTaskHover] = useState({});
const [modalAdd, setModalAdd] = useState(false); const [modalAdd, setModalAdd] = useState(false);
const [modalActiveTicket, setModalActiveTicket] = useState(false); const [modalActiveTicket, setModalActiveTicket] = useState(false);
const [selectedTicket, setSelectedTicket] = useState({}); const [selectedTicket, setSelectedTicket] = useState({});
const [personListOpen, setPersonListOpen] = useState(false); const [personListOpen, setPersonListOpen] = useState(false);
const [tags, setTags] = useState({
open: false,
add: false,
edit: false
});
const [acceptModalOpen, setAcceptModalOpen] = useState(false); const [acceptModalOpen, setAcceptModalOpen] = useState(false);
const [currentColumnDelete, setCurrentColumnDelete] = useState(null); const [currentColumnDelete, setCurrentColumnDelete] = useState(null);
const [color, setColor] = useState("#aabbcc");
const [tagInfo, setTagInfo] = useState({ description: "", name: "" });
const [checkBoxParticipateTasks, setCheckBoxParticipateTasks] = const [checkBoxParticipateTasks, setCheckBoxParticipateTasks] =
useState(false); useState(false);
const [filteredNoTasks, setFilteredNoTasks] = useState(false); const [filteredNoTasks, setFilteredNoTasks] = useState(false);
const [checkBoxMyTasks, setCheckBoxMyTasks] = useState(false); const [checkBoxMyTasks, setCheckBoxMyTasks] = useState(false);
const [selectedExecutor, setSelectedExecutor] = useState(null); const [selectedExecutor, setSelectedExecutor] = useState(null);
const [selectExecutorOpen, setSelectedExecutorOpen] = useState(false);
const startWrapperIndexTest = useRef({}); const startWrapperIndexTest = useRef({});
const projectBoard = useSelector(getProjectBoard); const projectBoard = useSelector(getProjectBoard);
const loader = useSelector(getBoarderLoader); const loader = useSelector(getBoarderLoader);
const { showNotification } = useNotification(); const { showNotification } = useNotification();
const priority = {
2: "Высокий",
1: "Средний",
0: "Низкий"
};
const priorityClass = {
2: "high",
1: "middle",
0: "low"
};
useEffect(() => { useEffect(() => {
dispatch(activeLoader()); dispatch(activeLoader());
dispatch(setProjectBoardFetch(projectId.id)); dispatch(setProjectBoardFetch(projectId.id));
@ -110,8 +79,6 @@ export const ProjectTracker = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
const tasksHover = {};
const columnHover = {};
let columnsTasksEmpty = true; let columnsTasksEmpty = true;
if (Object.keys(projectBoard).length) { if (Object.keys(projectBoard).length) {
projectBoard.columns.forEach((column) => { projectBoard.columns.forEach((column) => {
@ -120,8 +87,6 @@ export const ProjectTracker = () => {
...prevState, ...prevState,
[column.id]: false [column.id]: false
})); }));
columnHover[column.id] = false;
column.tasks.forEach((task) => (tasksHover[task.id] = false));
}); });
} }
if ( if (
@ -132,48 +97,8 @@ export const ProjectTracker = () => {
} else { } else {
setFilteredNoTasks(false); setFilteredNoTasks(false);
} }
setWrapperHover(columnHover);
setTaskHover(tasksHover);
}, [projectBoard]); }, [projectBoard]);
function dragStartHandler(e, task, columnId) {
startWrapperIndexTest.current = { task: task, index: columnId };
}
function dragOverTaskHandler(e, task) {
e.preventDefault();
if (startWrapperIndexTest.current.task.id === task.id) {
return;
}
setTaskHover((prevState) => ({ [prevState]: false, [task.id]: true }));
}
function dragLeaveTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
}
function dragEndTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
setWrapperHover((prevState) => ({
[prevState]: false
}));
}
function dragDropTaskHandler(e, task, column) {
e.preventDefault();
if (task.id === startWrapperIndexTest.current.task.id) {
return;
}
const finishTask = column.tasks.indexOf(task);
dispatch(
movePositionProjectTask({
startTask: startWrapperIndexTest.current.task,
finishTask: task,
finishIndex: finishTask
})
);
}
function dragOverHandler(e) { function dragOverHandler(e) {
e.preventDefault(); e.preventDefault();
} }
@ -256,7 +181,11 @@ export const ProjectTracker = () => {
} else { } else {
dispatch(setProjectBoardFetch(projectBoard.id)); dispatch(setProjectBoardFetch(projectBoard.id));
} }
showNotification({ show: true, text: "Колонка удалена", type: "error" }); showNotification({
show: true,
text: "Колонка удалена",
type: "error"
});
}); });
} }
@ -284,11 +213,6 @@ export const ProjectTracker = () => {
setCheckBoxMyTasks(!checkBoxMyTasks); setCheckBoxMyTasks(!checkBoxMyTasks);
} }
function executorFilter(user) {
dispatch(filteredExecutorTasks(user.user_id));
setSelectedExecutor(user);
}
function deleteSelectedExecutorFilter() { function deleteSelectedExecutorFilter() {
setSelectedExecutor(null); setSelectedExecutor(null);
setCheckBoxParticipateTasks(false); setCheckBoxParticipateTasks(false);
@ -296,66 +220,6 @@ export const ProjectTracker = () => {
dispatch(setProjectBoardFetch(projectId.id)); dispatch(setProjectBoardFetch(projectId.id));
} }
function addNewTag() {
apiRequest("/mark/create", {
method: "POST",
data: {
title: tagInfo.description,
slug: tagInfo.name,
color: color,
status: 1
}
}).then((data) => {
apiRequest("/mark/attach", {
method: "POST",
data: {
mark_id: data.id,
entity_type: 1,
entity_id: projectId.id
}
}).then((data) => {
dispatch(addNewTagToProject(data.mark));
setTags((prevState) => ({
...prevState,
add: false
}));
});
});
}
function editTag() {
apiRequest("/mark/update", {
method: "PUT",
data: {
mark_id: tagInfo.editMarkId,
title: tagInfo.description,
slug: tagInfo.name,
color: color
}
}).then(() => {
dispatch(setProjectBoardFetch(projectId.id));
setTags((prevState) => ({
...prevState,
edit: false
}));
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
});
}
function deleteTag(tagId) {
apiRequest("/mark/detach", {
method: "DELETE",
data: {
mark_id: tagId,
entity_type: 1,
entity_id: projectId.id
}
}).then(() => {
dispatch(deleteTagProject(tagId));
});
}
const initListeners = () => { const initListeners = () => {
document.addEventListener("click", closeByClickingOut); document.addEventListener("click", closeByClickingOut);
}; };
@ -375,32 +239,6 @@ export const ProjectTracker = () => {
setPersonListOpen(false); setPersonListOpen(false);
} }
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("tasks__head__executor") ||
div.classList.contains("tasks__head__executor-dropdown"))
)
) {
setSelectedExecutorOpen(false);
}
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("tasks__head__tags") ||
div.classList.contains("tags__list"))
)
) {
setTags({ open: false, add: false, edit: false });
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
}
if ( if (
event && event &&
!path.find( !path.find(
@ -558,207 +396,16 @@ export const ProjectTracker = () => {
{checkBoxMyTasks && <img src={accept} alt="accept" />} {checkBoxMyTasks && <img src={accept} alt="accept" />}
</div> </div>
</div> </div>
{selectedExecutor ? (
<div className="tasks__head__executor-selected"> <TrackerSelectExecutor
<p>{removeLast(selectedExecutor.user.fio)}</p> deleteSelectedExecutor={deleteSelectedExecutorFilter}
<img projectBoard={projectBoard}
className="avatar" selectedExecutor={selectedExecutor}
src={ setSelectedExecutor={setSelectedExecutor}
selectedExecutor.user?.avatar />
? urlForLocal(selectedExecutor.user.avatar)
: avatarMok <TrackerTagList projectBoard={projectBoard} />
}
alt="avatar"
/>
<img
className="delete"
src={close}
alt="delete"
onClick={deleteSelectedExecutorFilter}
/>
</div>
) : (
<div
className="tasks__head__executor"
onClick={() =>
setSelectedExecutorOpen(!selectExecutorOpen)
}
>
<p>Выберите исполнителя</p>
<img
className={selectExecutorOpen ? "open" : ""}
src={arrowDown}
alt="arrow"
/>
{selectExecutorOpen && (
<div className="tasks__head__executor-dropdown">
{projectBoard.projectUsers.map((user) => {
return (
<div
className="executor-dropdown__person"
key={user.user_id}
onClick={() => executorFilter(user)}
>
<p>{removeLast(user.user?.fio)}</p>
<img
src={
user.user?.avatar
? urlForLocal(user.user.avatar)
: avatarMok
}
alt="avatar"
/>
</div>
);
})}
</div>
)}
</div>
)}
<div className="tasks__head__tags">
<div
className="tags__add"
onClick={() => {
setTags((prevState) => ({
...prevState,
open: !tags.open
}));
}}
>
<p>Список тегов</p>
<img
className={tags.open ? "open" : ""}
src={arrowDown}
alt="arrow"
/>
</div>
{tags.open && (
<div className="tags__list">
{!tags.add && !tags.edit && (
<div
className="add-new-tag"
onClick={() =>
setTags((prevState) => ({
...prevState,
add: true
}))
}
>
<p>Добавить новый тег</p>
<span>+</span>
</div>
)}
{!tags.add && !tags.edit && (
<div className="tags__list__created">
{projectBoard.mark.map((tag) => {
return (
<div className="tag-item" key={tag.id}>
<div className="tag-item__info">
<span
className="tag-item__color"
style={{ background: tag.color }}
/>
<div>
<span className="tag-item__info__name">
{tag.slug}
</span>
<p className="tag-item__description">
{tag.title}
</p>
</div>
</div>
<div className="tag-item__images">
<img
src={edit}
alt="edit"
onClick={() => {
setTags((prevState) => ({
...prevState,
edit: true
}));
setTagInfo({
description: tag.title,
name: tag.slug,
editMarkId: tag.id
});
setColor(tag.color);
}}
/>
<img
onClick={() => deleteTag(tag.id)}
className="delete"
src={close}
alt="delete"
/>
</div>
</div>
);
})}
</div>
)}
{(tags.add || tags.edit) && (
<div className="form-tag">
<input
className="form-tag__input"
placeholder="Описание метки"
maxLength="25"
value={tagInfo.description}
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
description: e.target.value
}))
}
/>
<input
className="form-tag__input"
placeholder="Тег"
value={tagInfo.name}
maxLength="10"
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
name: e.target.value
}))
}
/>
<HexColorPicker color={color} onChange={setColor} />
<button
onClick={() => {
tags.add ? addNewTag() : editTag();
}}
className={
tagInfo.name && tagInfo.description
? "form-tag__btn"
: "form-tag__btn disable"
}
>
{tags.add ? "Добавить" : "Изменить"}
</button>
{(tags.add || tags.edit) && (
<button
className={"form-tag__btn"}
onClick={() => {
setTags((prevState) => ({
...prevState,
add: false,
edit: false
}));
setTagInfo({
description: "",
name: ""
});
setColor("#aabbcc");
}}
>
Отмена
</button>
)}
</div>
)}
</div>
)}
</div>
<Link to="/profile/tracker" className="tasks__head__back"> <Link to="/profile/tracker" className="tasks__head__back">
<p>К списку проектов</p> <p>К списку проектов</p>
<img src={arrow} alt="arrow" /> <img src={arrow} alt="arrow" />
@ -859,7 +506,7 @@ export const ProjectTracker = () => {
</div> </div>
)} )}
<div className="tasks-container"> <div className="tasks-container">
{column.tasks.map((task) => { {column.tasks.map((task, index) => {
const dateDeadline = new Date(task.dead_line); const dateDeadline = new Date(task.dead_line);
const currentDate = moment().format( const currentDate = moment().format(
"YYYY-MM-DD HH:mm:ss" "YYYY-MM-DD HH:mm:ss"
@ -870,119 +517,16 @@ export const ProjectTracker = () => {
? "red" ? "red"
: "#1a1919"; : "#1a1919";
return ( return (
<div <TrackerCardTask
key={task.id} column={column}
className={`tasks__board__item ${ key={index}
taskHover[task.id] ? "task__hover" : "" openTicket={openTicket}
}`} projectBoard={projectBoard}
draggable={true} setWrapperHover={setWrapperHover}
onDragStart={(e) => startWrapperIndexTest={startWrapperIndexTest}
dragStartHandler(e, task, column.id) task={task}
} titleColor={titleColor}
onDragOver={(e) => dragOverTaskHandler(e, task)} />
onDragLeave={(e) => dragLeaveTaskHandler(e)}
onDragEnd={() => dragEndTaskHandler()}
onDrop={(e) =>
dragDropTaskHandler(e, task, column)
}
onClick={(e) => openTicket(e, task)}
>
<div
className="tasks__board__item__title"
onClick={() => {
if (window.innerWidth < 985) {
window.location.replace(
`/tracker/task/${task.id}`
);
}
}}
>
<p className="task__board__item__title">
{task.title}
</p>
</div>
<p
dangerouslySetInnerHTML={{
__html: task.description
}}
className="tasks__board__item__description"
></p>
{Boolean(task.mark.length) && (
<div className="tasks__board__item__tags">
{task.mark.map((tag) => {
return (
<div
className="tag-item"
key={tag.id}
style={{ background: tag.color }}
>
<p>{tag.slug}</p>
</div>
);
})}
</div>
)}
<div className="tasks__board__item__container">
{typeof task.execution_priority ===
"number" && (
<div className="tasks__board__item__priority">
<p></p>
<span
className={
priorityClass[task.execution_priority]
}
>
{priority[task.execution_priority]}
</span>
</div>
)}
{task.dead_line && (
<div className="tasks__board__item__dead-line">
<p></p>
<span style={{ color: titleColor }}>
{getCorrectDate(task.dead_line)}
</span>
</div>
)}
</div>
<div className="tasks__board__item__info">
<div className="tasks__board__item__executor">
<img
src={
task.executor?.avatar
? urlForLocal(task.executor?.avatar)
: avatarMok
}
alt="avatar"
/>
<span>
{removeLast(task.executor?.fio) ||
"Исполнитель не назначен"}
</span>
</div>
<div className="tasks__board__item__info__tags">
<div className="tasks__board__item__info__more">
<img
src={commentsBoard}
alt="commentsImg"
/>
<span>{task.comment_count}</span>
</div>
<div className="tasks__board__item__info__more">
<img src={filesBoard} alt="filesImg" />
<span>{task.file_count}</span>
</div>
</div>
</div>
<TrackerSelectColumn
columns={projectBoard.columns.filter(
(item) => item.id !== column.id
)}
currentColumn={column}
task={task}
/>
</div>
); );
})} })}
</div> </div>

View File

@ -11,7 +11,6 @@ import { Loader } from "@components/Common/Loader/Loader";
import { Navigation } from "@components/Navigation/Navigation"; import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs"; import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader"; import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
// import { HeadBottom } from "@components/features/Candidate-lk/HeadBottom";
import { AlertResult } from "@components/features/quiz/AlertResult"; import { AlertResult } from "@components/features/quiz/AlertResult";
import { QuizReport } from "@components/features/quiz/QuizReport"; import { QuizReport } from "@components/features/quiz/QuizReport";
@ -41,7 +40,6 @@ export const QuizReportPage = () => {
<div className="quiz-report-page"> <div className="quiz-report-page">
<ProfileHeader /> <ProfileHeader />
<Navigation /> <Navigation />
{/*<HeadBottom />*/}
<div className="quiz-report-page__container"> <div className="quiz-report-page__container">
<ProfileBreadcrumbs <ProfileBreadcrumbs
links={[ links={[

View File

@ -81,7 +81,6 @@ export const RegistrationForCandidate = () => {
<img src={arrowBtn} alt="img" /> <img src={arrowBtn} alt="img" />
</Link> </Link>
</div> </div>
{/* форма регистрации */}
<form <form
className="registration-candidate__form" className="registration-candidate__form"
onSubmit={handleSubmit} onSubmit={handleSubmit}

343
src/pages/Stack/Stack.jsx Normal file
View File

@ -0,0 +1,343 @@
import React from "react";
import SVG from "react-inlinesvg";
import { AuthHeader } from "@components/Common/AuthHeader/AuthHeader";
import arrowReviewsLeft from "assets/icons/arrows/arrowReviewsLeft.png";
import arrowReviewsRight from "assets/icons/arrows/arrowReviewsRight.png";
import Ellipse from "assets/images/EllipseIntro.svg";
import backgroundOpp from "assets/images/backgroundOpportunity.png";
import cat from "assets/images/cat.png";
import clue from "assets/images/clue.png";
import code1 from "assets/images/landingBackgroundCode1.png";
import code from "assets/images/landingBackgroundCode.png";
import reviewsImgMok from "assets/images/reviewsImgMok.png";
import flag from "assets/images/stackProjectsFlag.png";
import fly from "assets/images/stackProjectsFly.png";
import hand from "assets/images/stackProjectsHand.png";
import rabota from "assets/images/stackProjectsRabota.png";
import portfolio from "assets/images/stackSteptsPortfolio.png";
import "./stack.scss";
export const Stack = () => {
const subjects = [
{
name: "Backend",
skills: [
"php",
"yii2",
"laravel",
"symfony",
"django",
"nodejs",
"fastAPI",
"flask",
"python",
"exspress",
"adonis"
]
},
{
name: "Front",
skills: [
"react",
"next.js",
"typescript",
"redux",
"angular",
"vue",
"jquery",
"css (sass/scss, tailwind, bootstrap, БЭМ)"
]
}
];
const projects = [
{
description:
"Импортозамещение в управлении проектами <span>таск-трекер ITGuild</span>",
img: flag,
name: "flag"
},
{
description:
"<span>Работа Тудей</span> - это сервис, который специализируется на поиске работы на новых территориях Российской Федерации.",
img: rabota,
name: "rabota"
},
{
description:
"<span>Внедрение искусственного интеллекта</span> (ИИ) в IT-проекты. Интеграции любых популярных сервисов.",
img: hand,
name: hand
},
{
description:
"Новостной портал и удобный каталог компаний <span>DaInfo.pro</span> предоставляющих различные услуги и товары.",
img: fly,
name: "fly"
}
];
const steps = [
{
miniInfo: "Окунитесь в экосистему ITGUIL",
info: "<span>уточнение</span> деталей и <span>обсуждение</span> условий с менеджером ITGUILD"
},
{
miniInfo: "Окунитесь в экосистему ITGUIL",
info: "<span>подписание договора</span> без обязательств оплаты на данном этапе"
},
{
miniInfo: "Окунитесь в экосистему ITGUIL",
info: "<span>формирование</span> команды или подбор отдельных специалистов под требования клиентов"
},
{
miniInfo: "Окунитесь в экосистему ITGUIL",
info: "<span>интеграция специалистов</span> в команду клиента, ежедневная отчетность под контролем менеджера ITGUILD"
}
];
return (
<section className="stack">
<AuthHeader />
<section className="stack__intro">
<div className="stack__container intro__container">
<div className="intro__info">
<span className="intro__suptitle">
Все еще пытаетесь
<br /> пасти котов?*
</span>
<h1 className="intro__title">
Аутстаф
<br />
финг
</h1>
<span className="intro__subtitle">IT-специалистов</span>
<p className="intro__about">
Подберем и документально оформим IT-специалистов, после чего
передадим исполнителей под ваше руководство.{" "}
<span>Вы получаете полное управление над сотрудниками,</span> имея
возможность контролировать и заменять IT штат.
</p>
<div className="intro__links">
<button className="stack__button">оставить заявку</button>
<span className="intro__link">
Окунитесь в<br /> экосистему ITGUIL
</span>
</div>
</div>
<SVG className="intro__ellipse" src={Ellipse} />
<div className="intro__aside">
<h3 className="aside__logo">ITGu ild</h3>
<div className="aside__clue">
<img src={clue} alt="clue" />
<p>
<span>Каждый день</span> база специалистов пополняется на{" "}
<span>+15 резюме</span>
</p>
</div>
<img className="aside__cat" src={cat} alt="cat" />
</div>
</div>
</section>
<section className="stack__opportunity">
<img src={backgroundOpp} className="background__opportunity--left" />
<img src={backgroundOpp} className="background__opportunity--right" />
<div className="stack__container opportunity__container">
<img src={code} className="opportunity__code" />
<img src={code} className="opportunity__code--center" />
<div className="opportunity__block">
<h3 className="opportunity__title">Stack</h3>
<div className="opportunity__info">
<span className="info__subtitle">
Окунитесь в экосистему ITGUIL
</span>
<p className="info__about">
<span>Вы получаете полное управление над сотрудниками,</span>{" "}
имея возможность контролировать и заменять IT штат.
</p>
<div className="info__notification">
<img src={clue} alt="clue" />
<p>
Можем подготовить специалиста конкретно под ваш проект и
используемый стек. Таким образом вы сможете сэкономить ресурсы
на поиск кандидата.
</p>
</div>
</div>
</div>
<div className="opportunity__subjects">
{subjects.map((subject) => {
return (
<div className="subject" key={subject.name}>
<h4>{subject.name}</h4>
<div className="subject__skills">
{subject.skills.map((skill) => {
return <span key={skill}>{skill}</span>;
})}
</div>
</div>
);
})}
</div>
</div>
</section>
<section className="stack__projects projects">
<div className="stack__container projects__container">
<img className="projects__code" src={code} alt="code" />
<h3 className="projects__title">ITGUILD</h3>
<div className="projects__block">
<h4>Наши проекты</h4>
<div className="projects__examples">
{projects.map((project, index) => {
return (
<div key={index} className="stack__project">
<span className="project__img">
<img
className={project.name}
src={project.img}
alt="img"
/>
</span>
<p
dangerouslySetInnerHTML={{ __html: project.description }}
></p>
</div>
);
})}
</div>
<div className="projects__info">
<p>
<span>Мы обеспечиваем</span> финансовые, юридические и кадровые
гарантии, предоставляем SLA и берем на себя ответственность за
работу команды. Вам не требуется заниматься поиском, оформлением
или увольнением сотрудников {" "}
<span>мы берем на себя все хлопоты.</span>
</p>
<button>оставить заявку</button>
</div>
</div>
</div>
</section>
<section className="stack__steps">
<div className="stack__container steps__container">
<div className="steps__head">
<h4>как это работает?</h4>
<div className="steps__info">
<p>
Аутстаффинг представляет собой специфическую модель найма
персонала, отличающуюся от аутсорсинга.
</p>
<p>
<span>
В контексте аутстаффинга вы нанимаете специалистов в области
ИТ,
</span>{" "}
оплачивая их по их конкретным навыкам, и берете на себя
организацию их работы.
</p>
</div>
</div>
<div className="steps__items">
{steps.map((step, index) => {
return (
<div key={index} className="item__wrapper">
<div className="item__head">
<h4>{`${index + 1}.`}</h4>
<p>{step.miniInfo}</p>
</div>
<div className="steps__item" key={index}>
<p
className="item__info"
dangerouslySetInnerHTML={{ __html: step.info }}
/>
</div>
</div>
);
})}
</div>
<div className="steps__portfolio">
<img src={portfolio} alt="portfolio" />
</div>
<img
className="steps__code steps__code--first"
src={code}
alt="code"
/>
<img
className="steps__code steps__code--second"
src={code}
alt="code"
/>
</div>
<img src={backgroundOpp} className="steps__background" />
</section>
<section className="stack__reviews">
<div className="stack__container reviews__container">
<div className="reviews__info">
<div className="reviews__info-counter">375</div>
<span>Довольных клиентов</span>
<p>
Предоставляем на аутстаффинг frontend- и backend - разработчиков
уровня от junior до middle+
</p>
<p>
Можем сделать оценку проекта, ревью кода, составить коммерческое
предложение, рекомендации касаемо стека технологий и организации
архитектуры разрабатываемого проекта.
</p>
</div>
<div className="reviews__content">
<h4>Что о нас говорят</h4>
<div className="reviews__content-container">
<div className="review">
<div className="review__client">
<img src={reviewsImgMok} alt="reviewsImgMok" />
<span>Александр Гузеев</span>
<p>Руководитель проекта ООО ЭЛАР</p>
</div>
<div className="review__comment">
<p>
Команда ITGUILD берется за решение широкого круга задач, не
боясь при этом ни сжатых сроков, ни сложной специфики
проектов, и успешно доводит их ло решения. <br />
<br />
Разаработчики Кирилла смогли не только усилить существующую
команду разработки, став ее полноценной частью, но и
привести в проект новые идеи, свои знания и опыт.
</p>
</div>
</div>
<div className="reviews__content-buttons">
<button>
<img src={arrowReviewsLeft} alt="" />
</button>
<button>
<img src={arrowReviewsRight} alt="" />
</button>
</div>
</div>
</div>
<img
className="reviews__code reviews__code--first"
src={code1}
alt="code"
/>
<img
className="reviews__code reviews__code--second"
src={code1}
alt="code"
/>
</div>
</section>
<section className="stack__contact">
<div className="stack__container contact__container"></div>
</section>
</section>
);
};

830
src/pages/Stack/stack.scss Normal file
View File

@ -0,0 +1,830 @@
.stack {
font-family: "GT Eesti Pro Display";
&__container {
margin: 0 auto;
padding: 85px 0 90px;
max-width: 1020px;
position: relative;
display: flex;
}
&__intro {
background: #eeeeee;
.intro {
&__container {
background-image: url("../../assets/images/backgroundLandingIntro.svg");
background-repeat: no-repeat;
background-position: center bottom;
}
&__info {
display: flex;
flex-direction: column;
position: relative;
z-index: 2;
}
&__suptitle {
font-weight: 700;
font-size: 16px;
color: #838383;
}
&__title {
font-weight: 900;
color: #a7ca60;
font-size: 88px;
text-transform: uppercase;
letter-spacing: 0.03em;
margin: 39px 0 6px;
z-index: 2;
}
&__subtitle {
letter-spacing: 0.05em;
font-size: 39px;
font-weight: 700;
color: #4a4a4a;
}
&__about {
max-width: 380px;
color: #4a4a4a;
font-size: 14px;
font-weight: 250;
margin-bottom: 34px;
span {
font-weight: 400;
}
}
&__links {
display: flex;
column-gap: 30px;
align-items: center;
}
&__link {
font-weight: 700;
font-size: 12px;
color: #a7ca60;
}
&__ellipse {
z-index: 1;
top: 45%;
left: 50%;
transform: translate(-50%, -50%);
position: absolute;
}
&__aside {
position: relative;
border-radius: 24px 0 113px 0;
top: 55px;
width: 330px;
height: 517px;
background: rgba(167, 202, 96, 0.7);
margin-left: 96px;
z-index: 1;
&:before {
content: "";
width: 182px;
height: 106px;
position: absolute;
backdrop-filter: blur(8.699999809265137px);
box-shadow: 10px 9px 14px 0 rgba(0, 0, 0, 0.06);
background: linear-gradient(
137deg,
rgba(255, 255, 255, 0.34) 0%,
rgba(206, 198, 198, 0.34) 100%
);
border-radius: 8px;
top: -35px;
left: -25px;
z-index: 3;
}
.aside {
&__logo {
z-index: 2;
font-family: "Geraspoheko";
color: white;
font-size: 343px;
position: absolute;
line-height: 325.92px;
left: 80px;
top: -30px;
}
&__clue {
position: absolute;
backdrop-filter: blur(8.699999809265137px);
box-shadow: 10px 9px 14px 0 rgba(0, 0, 0, 0.03);
background: linear-gradient(
137deg,
rgba(255, 255, 255, 0.34) 0%,
rgba(206, 198, 198, 0.34) 100%
);
border-radius: 8px;
width: 182px;
height: 106px;
bottom: 35px;
right: -140px;
z-index: 2;
display: flex;
padding: 24px 20px 18px 30px;
border: 0.5px solid;
border-image-source: linear-gradient(
137.79deg,
#ffffff 9.15%,
#f4f4f4 76.22%
);
p {
color: rgba(141, 141, 141, 1);
font-size: 14px;
font-weight: 300;
letter-spacing: 0.03em;
line-height: 15.96px;
span {
font-weight: 700;
}
}
img {
position: absolute;
top: -25px;
left: 0;
}
&:before {
position: absolute;
content: "Подсказка";
font-weight: 700;
color: rgba(205, 205, 205, 1);
font-size: 14px;
line-height: 19.18px;
letter-spacing: 0.03em;
top: -22px;
right: 10px;
}
}
&__cat {
position: absolute;
z-index: 3;
bottom: 0;
left: -125px;
}
}
}
}
}
&__button {
max-width: 200px;
width: 100%;
background: #a7ca60;
font-size: 15px;
color: #4a4a4a;
padding: 14px 0;
border-radius: 44px;
border: none;
}
&__opportunity {
background: #1e1e1e;
position: relative;
.background__opportunity--left {
position: absolute;
top: -50%;
left: -5%;
}
.background__opportunity--right {
position: absolute;
bottom: -361px;
right: 0;
}
.opportunity {
&__container {
padding: 105px 0 0px;
flex-direction: column;
}
&__code {
position: absolute;
top: 35px;
left: 55px;
&--center {
position: absolute;
right: 31%;
top: 34%;
}
}
&__block {
display: flex;
}
&__title {
font-family: "Geraspoheko";
font-weight: 400;
font-size: 343px;
line-height: 1.03;
margin-bottom: 0;
z-index: 2;
background: linear-gradient(360deg, #171717 0%, #2a2a2a 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
&__info {
display: flex;
flex-direction: column;
margin-left: 15px;
.info {
&__subtitle {
padding-left: 31px;
color: rgba(167, 202, 96, 1);
font-weight: 700;
font-size: 14px;
line-height: 16.24px;
margin-bottom: 30px;
}
&__about {
padding-left: 31px;
font-size: 14px;
color: rgba(238, 238, 238, 1);
line-height: 19.18px;
font-weight: 250;
max-width: 355px;
margin-bottom: 27px;
span {
font-weight: 400;
}
}
&__notification {
padding: 21px 19px 23px 31px;
border-radius: 8px;
backdrop-filter: blur(8.699999809265137px);
box-shadow: 10px 9px 14px 0 rgba(0, 0, 0, 0.06);
background: linear-gradient(
137deg,
rgba(87, 87, 87, 0.34) 0%,
rgba(104, 104, 104, 0.34) 100%
);
position: relative;
border: 0.5px solid #717171;
img {
position: absolute;
width: 80.93px;
height: 74.19px;
left: -68px;
top: -10px;
}
p {
color: rgba(238, 238, 238, 1);
line-height: 19.18px;
font-size: 14px;
}
}
}
}
&__subjects {
display: flex;
column-gap: 100px;
position: relative;
top: -100px;
z-index: 3;
right: -35px;
.subject {
display: flex;
flex-direction: column;
h4 {
text-transform: uppercase;
color: rgba(167, 202, 96, 1);
letter-spacing: 0.03em;
font-weight: 900;
font-size: 88px;
line-height: 86.58px;
margin-bottom: 0;
}
&__skills {
display: flex;
flex-wrap: wrap;
gap: 14px;
margin-top: 20px;
span {
border: 0.5px solid rgba(167, 202, 96, 0.5);
border-radius: 56px;
padding: 8px 25px 8px;
color: rgba(167, 202, 96, 1);
font-size: 17px;
line-height: 20.88px;
}
}
}
}
}
}
&__projects {
background: rgba(238, 238, 238, 1);
.projects {
&__container {
padding-top: 190px;
padding-bottom: 81px;
}
&__code {
position: absolute;
left: -170px;
top: 24px;
}
&__title {
font-weight: 400;
font-size: 343px;
background: linear-gradient(to bottom, #ffffff, #dbdbdb);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-family: "Geraspoheko";
margin: 0 auto;
position: absolute;
width: 100%;
text-align: center;
top: -50px;
z-index: 2;
filter: drop-shadow(0px 0px 30px #00000021);
}
&__block {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
z-index: 3;
h4 {
font-weight: 900;
font-size: 46px;
line-height: 98%;
letter-spacing: 0.03em;
text-transform: uppercase;
color: #4a4a4a;
}
}
&__examples {
display: flex;
margin-top: 98px;
justify-content: space-between;
width: 100%;
.stack__project {
display: flex;
flex-direction: column;
align-items: center;
row-gap: 35px;
.project__img {
border-radius: 8px;
width: 99px;
height: 81px;
background: #a7ca60;
position: relative;
img {
position: relative;
}
.flag {
bottom: 21px;
right: -10px;
}
.rabota {
top: -40px;
right: 25px;
}
.hand {
top: -45px;
left: -44px;
}
.fly {
top: -30px;
}
}
p {
font-weight: 250;
font-size: 14px;
line-height: 129%;
text-align: center;
color: #4a4a4a;
max-width: 226px;
span {
font-weight: 500;
}
}
}
}
&__info {
display: flex;
margin: 56px auto 0;
column-gap: 50px;
align-items: center;
border: 1px solid #f8f8f8;
border-radius: 8px;
padding: 47px 91px 47px 55px;
p {
max-width: 620px;
font-weight: 250;
font-size: 14px;
line-height: 129%;
color: #4a4a4a;
span {
font-weight: 500;
}
}
button {
padding: 15px 43px;
font-weight: 700;
font-size: 15px;
color: #4a4a4a;
background: #a7ca60;
border-radius: 44px;
border: none;
max-height: 46px;
width: 201px;
display: flex;
align-items: center;
}
}
}
}
&__steps {
background: rgb(30, 30, 30);
padding: 90px 0 40px;
position: relative;
.steps {
&__container {
flex-direction: column;
}
&__head {
display: flex;
justify-content: space-between;
width: 100%;
h4 {
font-weight: 900;
font-size: 66px;
line-height: 98%;
letter-spacing: 0.03em;
text-transform: uppercase;
color: #a7ca60;
max-width: 380px;
margin-bottom: 0;
}
}
&__info {
display: flex;
flex-direction: column;
row-gap: 20px;
max-width: 499px;
p {
font-weight: 300;
font-size: 15px;
line-height: 140%;
color: #bdbdbd;
}
span {
font-weight: 700;
}
}
&__items {
margin-top: 115px;
display: flex;
justify-content: space-between;
.item {
&__wrapper {
position: relative;
}
&__head {
position: absolute;
display: flex;
color: #a7ca60;
top: -45px;
left: 20px;
h4 {
margin-bottom: 0;
font-weight: 700;
font-size: 100px;
text-transform: uppercase;
line-height: 0.8;
}
p {
font-weight: 700;
font-size: 12px;
letter-spacing: 0.01em;
color: #a7ca60;
max-width: 114px;
}
}
}
}
&__item {
position: relative;
width: 235px;
height: 153px;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(3px);
box-shadow: 10px 9px 14px 0 rgba(0, 0, 0, 0.06);
background: linear-gradient(
137deg,
rgba(87, 87, 87, 0.34) 0%,
rgba(104, 104, 104, 0.34) 100%
);
border: 0.5px solid #717171;
border-radius: 8px;
.item {
&__info {
font-size: 15px;
line-height: 131%;
text-align: center;
color: #fff;
font-weight: 250;
max-width: 160px;
span {
font-weight: 700;
}
}
}
}
&__portfolio {
width: 100%;
position: absolute;
bottom: -40px;
display: flex;
img {
margin: 0 auto;
}
}
&__code {
position: absolute;
&--first {
top: -40px;
left: 70px;
}
&--second {
bottom: -40px;
right: 0;
}
}
}
.steps__background {
position: absolute;
right: 0;
top: -260px;
}
}
&__reviews {
background-color: #eeeeee;
.reviews {
&__container {
align-items: center;
column-gap: 58px;
padding-bottom: 48px;
}
&__code {
position: absolute;
mix-blend-mode: plus-lighter;
&--first {
top: 90px;
left: -180px;
}
&--second {
bottom: 0;
left: 400px;
}
}
&__info {
display: flex;
flex-direction: column;
align-items: flex-start;
background-color: #a7ca60;
border-radius: 24px 0 113px 0;
padding: 50px 22px 64px 44px;
max-width: 311px;
z-index: 1;
&::before {
content: "";
width: 182px;
height: 106px;
position: absolute;
backdrop-filter: blur(8.699999809265137px);
box-shadow: 10px 9px 14px 0 rgba(0, 0, 0, 0.06);
background: linear-gradient(
137deg,
rgba(255, 255, 255, 0.34) 0%,
rgba(206, 198, 198, 0.34) 100%
);
border-radius: 8px;
top: 62px;
left: 200px;
}
&::after {
content: "";
width: 182px;
height: 106px;
position: absolute;
backdrop-filter: blur(8.699999809265137px);
box-shadow: 10px 9px 14px 0 rgba(0, 0, 0, 0.06);
background: linear-gradient(
137deg,
rgba(255, 255, 255, 0.34) 0%,
rgba(206, 198, 198, 0.34) 100%
);
border-radius: 8px;
bottom: -30px;
left: -100px;
}
&-counter {
color: #ffffff;
font-weight: 900;
font-size: 124px;
line-height: 122px;
}
span {
color: #ffffff;
font-weight: 900;
font-size: 29px;
line-height: 31.61px;
text-transform: uppercase;
margin-bottom: 28px;
}
p {
color: #607536;
font-weight: 700;
font-size: 14px;
line-height: 17.22px;
&:last-child {
color: #ffffff;
font-weight: 300;
font-size: 14px;
margin-top: 37px;
}
}
}
&__content {
margin: 0 0 45px 0;
h4 {
text-transform: uppercase;
color: #4a4a4a;
font-weight: 900;
font-size: 46px;
line-height: 45.26px;
margin-bottom: 24px;
}
&-container {
display: flex;
flex-direction: row;
align-items: center;
column-gap: 20px;
.review {
display: grid;
grid-template-columns: 45% 55%;
align-items: center;
max-width: 517px;
padding: 35px;
border-radius: 8px;
border: 0.5px solid #ffffff;
background: linear-gradient(137deg, #ffffff -10%, #dddddd 100%);
box-shadow: inset;
&__client {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
color: #7e7e7e;
font-weight: 300;
font-size: 14px;
line-height: 16.24px;
margin-right: 48px;
img {
width: 88px;
height: 88px;
border-radius: 100px;
}
span {
color: #1e1e1e;
font-weight: 500;
font-size: 17px;
line-height: 19.72px;
margin: 18px 0 16px 0;
}
}
&__comment {
color: #4a4a4a;
font-weight: 250;
font-size: 14px;
line-height: 17px;
}
}
}
&-buttons {
display: flex;
flex-direction: column;
align-items: center;
button {
background-color: #ffffff;
width: 70px;
height: 64px;
border-radius: 5px;
border: none;
}
button:first-child {
margin-bottom: 22px;
}
}
}
}
}
&__contact {
background-color: #1e1e1e;
.contact {
&__container {
}
}
}
}

View File

@ -15,7 +15,7 @@ import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs"; import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader"; import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import arrow from "assets/icons/arrows/arrowCalendar.png"; import arrow from "assets/icons/arrows/arrowRight.png";
import emailImg from "assets/icons/emailStatistics.svg"; import emailImg from "assets/icons/emailStatistics.svg";
import link from "assets/icons/link.svg"; import link from "assets/icons/link.svg";
import project from "assets/icons/trackerProject.svg"; import project from "assets/icons/trackerProject.svg";
@ -211,8 +211,6 @@ const Statistics = () => {
<p className="person-type"> <p className="person-type">
{person.role ? person.role : "-"} {person.role ? person.role : "-"}
</p> </p>
{/* <span className="status status-active"> */}
<span <span
className={ className={
person.status person.status

View File

@ -10,6 +10,8 @@ import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
import { useNotification } from "@hooks/useNotification";
import { Footer } from "@components/Common/Footer/Footer"; import { Footer } from "@components/Common/Footer/Footer";
import { Navigation } from "@components/Navigation/Navigation"; import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs"; import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
@ -31,12 +33,13 @@ export const Summary = () => {
const profileInfo = useSelector(getProfileInfo); const profileInfo = useSelector(getProfileInfo);
const [openGit, setOpenGit] = useState(false); const [openGit, setOpenGit] = useState(false);
const [gitInfo, setGitInfo] = useState([]); const [gitInfo, setGitInfo] = useState([]);
const [editSummeryOpen, setEditSummeryOpen] = useState(false); const [editSummaryOpen, setEditSummaryOpen] = useState(false);
const [editSkills, setEditSkills] = useState(false); const [editSkills, setEditSkills] = useState(false);
const [summery, setSummery] = useState(""); const [summary, setSummary] = useState("");
const [selectedSkills, setSelectedSkills] = useState([]); const [selectedSkills, setSelectedSkills] = useState([]);
const [selectSkillsOpen, setSelectSkillsOpen] = useState(false); const [selectSkillsOpen, setSelectSkillsOpen] = useState(false);
const [skillsList, seSkillsList] = useState([]); const [skillsList, seSkillsList] = useState([]);
const { showNotification } = useNotification();
useEffect(() => { useEffect(() => {
apiRequest( apiRequest(
@ -45,7 +48,7 @@ export const Summary = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
setSummery(profileInfo.vc_text); setSummary(profileInfo.vc_text);
setSelectedSkills(profileInfo.skillValues); setSelectedSkills(profileInfo.skillValues);
}, [profileInfo]); }, [profileInfo]);
@ -66,13 +69,19 @@ export const Summary = () => {
}).then(() => {}); }).then(() => {});
} }
function editSummery() { function editSummary() {
apiRequest("/resume/edit-text", { apiRequest("/resume/edit-text", {
method: "PUT", method: "PUT",
data: { data: {
resume: summery resume: summary
} }
}).then(() => {}); }).then(() => {
showNotification({
show: true,
text: "Изменения успешно сохранены",
type: "success"
});
});
} }
return ( return (
<div className="summary"> <div className="summary">
@ -107,9 +116,12 @@ export const Summary = () => {
alt="avatar" alt="avatar"
/> />
<p className="summary__name"> <p className="summary__name">
{profileInfo?.fio || profileInfo?.username},{" "} {profileInfo?.fio || profileInfo?.username}{" "}
{profileInfo.specification} разработчик {profileInfo.specification}
</p> </p>
<hr />
<div className="summary__direction">Front End</div>
<div className="summary__level">Middle+</div>
</div> </div>
{!openGit && ( {!openGit && (
<button className="summary__git" onClick={() => setOpenGit(true)}> <button className="summary__git" onClick={() => setOpenGit(true)}>
@ -209,21 +221,21 @@ export const Summary = () => {
<div className="summary__sections__head"> <div className="summary__sections__head">
<h3>Опыт работы</h3> <h3>Опыт работы</h3>
<button <button
className={editSummeryOpen ? "edit" : ""} className={editSummaryOpen ? "edit" : ""}
onClick={() => { onClick={() => {
if (editSummeryOpen) { if (editSummaryOpen) {
editSummery(); editSummary();
} }
setEditSummeryOpen(!editSummeryOpen); setEditSummaryOpen(!editSummaryOpen);
}} }}
> >
{editSummeryOpen ? "Сохранить" : "Редактировать"} {editSummaryOpen ? "Сохранить" : "Редактировать"}
</button> </button>
</div> </div>
{editSummeryOpen ? ( {editSummaryOpen ? (
<CKEditor <CKEditor
editor={ClassicEditor} editor={ClassicEditor}
data={summery} data={summary}
config={{ config={{
removePlugins: [ removePlugins: [
"CKFinderUploadAdapter", "CKFinderUploadAdapter",
@ -231,22 +243,19 @@ export const Summary = () => {
"EasyImage", "EasyImage",
"Image", "Image",
"ImageCaption", "ImageCaption",
"ImageStyle",
"ImageToolbar",
"ImageUpload", "ImageUpload",
"MediaEmbed", "MediaEmbed"
"BlockQuote"
] ]
}} }}
onChange={(event, editor) => { onChange={(event, editor) => {
const data = editor.getData(); const data = editor.getData();
setSummery(data); setSummary(data);
}} }}
/> />
) : ( ) : (
<div <div
className="experience__content" className="experience__content"
dangerouslySetInnerHTML={{ __html: summery }} dangerouslySetInnerHTML={{ __html: summary }}
></div> ></div>
)} )}
</div> </div>

View File

@ -1,5 +1,5 @@
.summary { .summary {
background: #f1f1f1; background: #f4f7ff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100vh; min-height: 100vh;
@ -36,6 +36,7 @@
} }
&__info { &__info {
width: 100%;
min-height: 110px; min-height: 110px;
background: white; background: white;
border-radius: 12px; border-radius: 12px;
@ -62,7 +63,13 @@
&__person { &__person {
display: flex; display: flex;
align-items: center; align-items: center;
column-gap: 45px; column-gap: 20px;
hr {
background-color: #00000081;
height: 30px;
width: 1px;
}
@media (max-width: 825px) { @media (max-width: 825px) {
column-gap: 20px; column-gap: 20px;
@ -74,8 +81,8 @@
} }
&__avatar { &__avatar {
width: 88px; width: 50px;
height: 88px; height: 50px;
border-radius: 100px; border-radius: 100px;
@media (max-width: 690px) { @media (max-width: 690px) {
@ -97,9 +104,9 @@
} }
&__name { &__name {
color: #1458dd;
font-size: 15px;
font-weight: 500; font-weight: 500;
font-size: 16px;
line-height: 32px;
position: relative; position: relative;
@media (max-width: 690px) { @media (max-width: 690px) {
@ -118,17 +125,20 @@
@media (max-width: 450px) { @media (max-width: 450px) {
max-width: 160px; max-width: 160px;
} }
}
&:after { &__direction {
content: ""; color: #6f6f6f;
position: absolute; font-size: 14px;
background: #52b709; }
border-radius: 12px;
width: 70%; &__level {
height: 8px; background-color: #1458dd;
bottom: -14px; color: #ffffff;
left: 0; border-radius: 44px;
} font-size: 14px;
font-weight: 400;
padding: 5px 17px;
} }
&__git { &__git {

View File

@ -1,5 +1,11 @@
import { getTheme } from "@table-library/react-table-library/baseline";
import { CompactTable } from "@table-library/react-table-library/compact";
import { usePagination } from "@table-library/react-table-library/pagination";
import { useSort } from "@table-library/react-table-library/sort";
import { useTheme } from "@table-library/react-table-library/theme";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { import {
getProjects, getProjects,
@ -9,7 +15,8 @@ import {
setToggleTab setToggleTab
} from "@redux/projectsTrackerSlice"; } from "@redux/projectsTrackerSlice";
import { caseOfNum } from "@utils/helper"; import { getCorrectDate } from "@utils/calendarHelper";
import { caseOfNum, urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
@ -26,9 +33,10 @@ import ProjectTicket from "@components/ProjectTicket/ProjectTicket";
import addProjectImg from "assets/icons/addProjectImg.svg"; import addProjectImg from "assets/icons/addProjectImg.svg";
import archiveTrackerProjects from "assets/icons/archiveTrackerProjects.svg"; import archiveTrackerProjects from "assets/icons/archiveTrackerProjects.svg";
import rightArrow from "assets/icons/arrows/arrowRight.svg";
import arrowViewReport from "assets/icons/arrows/arrowViewReport.svg"; import arrowViewReport from "assets/icons/arrows/arrowViewReport.svg";
import filterIcon from "assets/icons/filterIcon.svg"; import filterIcon from "assets/icons/filterIcon.svg";
import search from "assets/icons/serchIcon.png"; import searchImg from "assets/icons/serchIcon.png";
import project from "assets/icons/trackerProject.svg"; import project from "assets/icons/trackerProject.svg";
import tasks from "assets/icons/trackerTasks.svg"; import tasks from "assets/icons/trackerTasks.svg";
import archive from "assets/images/archiveIcon.png"; import archive from "assets/images/archiveIcon.png";
@ -43,15 +51,110 @@ export const Tracker = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const projects = useSelector(getProjects); const projects = useSelector(getProjects);
const tab = useSelector(getToggleTab); const tab = useSelector(getToggleTab);
const theme = useTheme(getTheme());
const [nodes, setNodes] = useState([]);
const [initialNodes, setInitialNodes] = useState([]);
const [allTasks, setAllTasks] = useState([]); const [allTasks, setAllTasks] = useState([]);
const [filteredAllTasks, setFilteredAllTasks] = useState([]); const [filteredAllTasks, setFilteredAllTasks] = useState([]);
const [loader, setLoader] = useState(false); const [loader, setLoader] = useState(false);
const [filterCompleteTasks, setFilterCompleteTasks] = useState([]); const [filterCompleteTasks, setFilterCompleteTasks] = useState([]);
const [allCompletedTasks, setAllCompletedTasks] = useState([]); const [allCompletedTasks, setAllCompletedTasks] = useState([]);
const [search, setSearch] = useState("");
const [modalCreateProject, setModalCreateProject] = useState(false); const [modalCreateProject, setModalCreateProject] = useState(false);
const COLUMNS = [
{
label: "Задача",
renderCell: (item) => (
<p dangerouslySetInnerHTML={{ __html: item.title }}></p>
),
sort: { sortKey: "NAME" }
},
{
label: "Создано",
renderCell: (item) => <span>{getCorrectDate(item.created_at)}</span>,
sort: { sortKey: "CREATE" }
},
{
label: "Дедлайн",
renderCell: (item) => (
<span>
{item.dead_line ? getCorrectDate(item.dead_line) : "Без дедлайна"}
</span>
),
sort: { sortKey: "DEADLINE" }
},
{
label: "Потраченное время",
renderCell: (item) => (
<span>
{item.timers.length
? getSpendTime(item.timers)
: "Трекер не был включен"}
</span>
),
sort: { sortKey: "SPEND" }
}
];
let data = { nodes };
const sort = useSort(
data,
{
onChange: onSortChange
},
{
sortFns: {
NAME: (array) => array.sort((a, b) => a.title.localeCompare(b.title)),
CREATE: (array) =>
array.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)),
DEADLINE: (array) =>
array.sort((a, b) => new Date(a.dead_line) - new Date(b.dead_line)),
SPEND: (array) =>
array.sort(
(a, b) =>
getSpendTime(a.timers, true) - getSpendTime(b.timers, true)
)
}
}
);
const pagination = usePagination(data, {
state: {
page: 0,
size: 11
}
});
function getSpendTime(times, seconds) {
let timerSeconds = 0;
times.forEach((time) => {
timerSeconds += time.deltaSeconds;
});
if (seconds) {
return timerSeconds;
}
return `${Math.floor(timerSeconds / 60 / 60)}:${Math.floor(
(timerSeconds / 60) % 60
)}:${timerSeconds % 60}`;
}
function onSortChange(action, state) {
console.log(action, state);
}
const handleSearch = (event) => {
setSearch(event.target.value);
setNodes(
initialNodes.filter((item) =>
item.title.toLowerCase().includes(event.target.value.toLowerCase())
)
);
};
useEffect(() => { useEffect(() => {
setLoader(true); setLoader(true);
apiRequest( apiRequest(
@ -84,6 +187,8 @@ export const Tracker = () => {
: []; : [];
setAllTasks(allTasks); setAllTasks(allTasks);
setFilteredAllTasks(allTasks); setFilteredAllTasks(allTasks);
setNodes(allTasks);
setInitialNodes(allTasks);
setAllCompletedTasks(completedTasks); setAllCompletedTasks(completedTasks);
setFilterCompleteTasks(completedTasks); setFilterCompleteTasks(completedTasks);
}) })
@ -225,8 +330,16 @@ export const Tracker = () => {
setModalCreateProject(true); setModalCreateProject(true);
}} }}
> >
<img src={addProjectImg} alt="#"></img> <p className="create-project-btn__text">
<p className="create-project-btn__text">Добавить проект</p> Добавить новый проект
</p>
<div className="create-project-btn__content">
<img src={addProjectImg} alt="#"></img>
<p>
Ставьте задачи, следите за прогрессом, ведите учёт
рабочего времени
</p>
</div>
</BaseButton> </BaseButton>
</> </>
)} )}
@ -238,53 +351,122 @@ export const Tracker = () => {
: "tracker__tabs__content__projects" : "tracker__tabs__content__projects"
} }
> >
<div className="task-list__head"> {/*<div className="task-list__head">*/}
<div className="task-list__tasks-period"> {/* <div className="task-list__tasks-period">*/}
<div className="month-period"> {/* <div className="month-period">*/}
<p> {/* <p>*/}
{25} - {35} {/* {25} - {35}*/}
</p> {/* </p>*/}
<h3>Сентября,</h3> {/* <h3>Сентября,</h3>*/}
<h3>2023</h3> {/* <h3>2023</h3>*/}
</div> {/* </div>*/}
<div className="buttons-month"> {/* <div className="buttons-month">*/}
<button> {/* <button>*/}
<img src={arrowViewReport} alt="<"></img> {/* <img src={arrowViewReport} alt="<"></img>*/}
</button> {/* </button>*/}
<button> {/* <button>*/}
<img src={arrowViewReport} alt=">"></img> {/* <img src={arrowViewReport} alt=">"></img>*/}
</button> {/* </button>*/}
</div> {/* </div>*/}
</div> {/* </div>*/}
<div className="task-list__head__search"> {/* <div className="task-list__head__search">*/}
<img src={search} alt="search" /> {/* <img src={search} alt="search" />*/}
<input {/* <input*/}
type="text" {/* type="text"*/}
placeholder="Найти задачу" {/* placeholder="Найти задачу"*/}
onChange={(event) => filterAllTask(event)} {/* onChange={(event) => filterAllTask(event)}*/}
{/* />*/}
{/* </div>*/}
{/* <div className="task-list__filters">*/}
{/* <BaseButton styles={"task-list__filters-filter"}>*/}
{/* <img src={filterIcon} alt="#" />*/}
{/* <p>Фильтр</p>*/}
{/* </BaseButton>*/}
{/* <BaseButton styles={"task-list__filters-clear"}>*/}
{/* <p> Очистить фильтр</p>*/}
{/* </BaseButton>*/}
{/* </div>*/}
{/*</div>*/}
{loader ? (
<Loader style="green" />
) : (
<>
<div className="table__search">
<img src={searchImg} alt="search" />
<input
type="text"
placeholder="Поиск по задачам"
value={search}
onChange={handleSearch}
/>
</div>
<CompactTable
columns={COLUMNS}
data={data}
theme={theme}
sort={sort}
pagination={pagination}
/> />
</div> <div className="table__pagination">
<button
className={
pagination.state.page === 0 ? "switch disable" : "switch"
}
type="button"
disabled={pagination.state.page === 0}
onClick={() =>
pagination.fns.onSetPage(pagination.state.page - 1)
}
>
{"<"}
</button>
<span className="table__pages">
{pagination.state.getPages(data.nodes).map((_, index) => (
<button
key={index}
type="button"
className={
pagination.state.page === index
? "page page--active "
: "page"
}
onClick={() => pagination.fns.onSetPage(index)}
>
{index + 1}
</button>
))}
</span>
<button
className={
pagination.state.page + 1 ===
pagination.state.getPages(data.nodes).length
? "switch disable"
: "switch"
}
type="button"
disabled={
pagination.state.page + 1 ===
pagination.state.getPages(data.nodes).length
}
onClick={() =>
pagination.fns.onSetPage(pagination.state.page + 1)
}
>
{">"}
</button>
</div>
</>
)}
<div className="task-list__filters"> {/*<AllTaskTableTracker*/}
<BaseButton styles={"task-list__filters-filter"}> {/* loader={loader}*/}
<img src={filterIcon} alt="#" /> {/* filteredAllTasks={filteredAllTasks}*/}
<p>Фильтр</p> {/* projects={projects}*/}
</BaseButton> {/*/>*/}
<BaseButton styles={"task-list__filters-clear"}>
<p> Очистить фильтр</p>
</BaseButton>
</div>
</div>
{loader && <Loader style="green" />}
<AllTaskTableTracker
loader={loader}
filteredAllTasks={filteredAllTasks}
projects={projects}
/>
<div className="task-list__time"> <div className="task-list__time">
<div className="task-list__time-compited"> <div className="task-list__time-compited">

Some files were not shown because too many files have changed in this diff Show More