diff --git a/frontend/package.json b/frontend/package.json
index f60681e2fd5bd1861ba38f2fda2689266debfaba..09643bb51f941f2d97e9afb780cdb74692b5ac0d 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -18,6 +18,7 @@
     "@hookform/resolvers": "^3.9.0",
     "@radix-ui/react-accordion": "^1.2.0",
     "@radix-ui/react-checkbox": "^1.1.1",
+    "@radix-ui/react-dialog": "^1.1.2",
     "@radix-ui/react-label": "^2.1.0",
     "@radix-ui/react-popover": "^1.1.1",
     "@radix-ui/react-progress": "^1.1.0",
@@ -30,8 +31,9 @@
     "@radix-ui/react-tabs": "^1.1.1",
     "@radix-ui/react-toggle": "^1.1.0",
     "@radix-ui/react-tooltip": "^1.1.2",
-    "@tanstack/react-router": "^1.16.5",
+    "@tanstack/react-router": "^1.62.0",
     "@tanstack/react-table": "^8.20.5",
+    "@tanstack/router-zod-adapter": "^1.62.0",
     "class-variance-authority": "^0.7.0",
     "clsx": "^2.1.1",
     "date-fns": "^3.6.0",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 3187db4342b075afc7acd2183c83ef7524fb964d..c036b207e8983a65926062d696e2e6c45b3f0b70 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -20,6 +20,9 @@ dependencies:
   '@radix-ui/react-checkbox':
     specifier: ^1.1.1
     version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
+  '@radix-ui/react-dialog':
+    specifier: ^1.1.2
+    version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
   '@radix-ui/react-label':
     specifier: ^2.1.0
     version: 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
@@ -57,11 +60,14 @@ dependencies:
     specifier: ^1.1.2
     version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
   '@tanstack/react-router':
-    specifier: ^1.16.5
-    version: 1.35.3(react-dom@18.3.1)(react@18.3.1)
+    specifier: ^1.62.0
+    version: 1.62.0(react-dom@18.3.1)(react@18.3.1)
   '@tanstack/react-table':
     specifier: ^8.20.5
     version: 8.20.5(react-dom@18.3.1)(react@18.3.1)
+  '@tanstack/router-zod-adapter':
+    specifier: ^1.62.0
+    version: 1.62.0(@tanstack/react-router@1.62.0)(zod@3.23.8)
   class-variance-authority:
     specifier: ^0.7.0
     version: 0.7.0
@@ -138,7 +144,7 @@ dependencies:
 devDependencies:
   '@tanstack/router-devtools':
     specifier: ^1.16.5
-    version: 1.35.3(@tanstack/react-router@1.35.3)(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1)
+    version: 1.35.3(@tanstack/react-router@1.62.0)(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1)
   '@tanstack/router-vite-plugin':
     specifier: ^1.16.5
     version: 1.34.8(vite@5.4.8)
@@ -1928,12 +1934,6 @@ packages:
     resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
     dev: false
 
-  /@radix-ui/primitive@1.0.1:
-    resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
-    dependencies:
-      '@babel/runtime': 7.24.7
-    dev: false
-
   /@radix-ui/primitive@1.1.0:
     resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==}
     dev: false
@@ -2063,20 +2063,6 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
-  /@radix-ui/react-compose-refs@1.0.1(@types/react@18.3.3)(react@18.3.1):
-    resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@types/react': 18.3.3
-      react: 18.3.1
-    dev: false
-
   /@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.3)(react@18.3.1):
     resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==}
     peerDependencies:
@@ -2090,20 +2076,6 @@ packages:
       react: 18.3.1
     dev: false
 
-  /@radix-ui/react-context@1.0.1(@types/react@18.3.3)(react@18.3.1):
-    resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@types/react': 18.3.3
-      react: 18.3.1
-    dev: false
-
   /@radix-ui/react-context@1.1.0(@types/react@18.3.3)(react@18.3.1):
     resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==}
     peerDependencies:
@@ -2130,38 +2102,37 @@ packages:
       react: 18.3.1
     dev: false
 
-  /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
+  /@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==}
     peerDependencies:
       '@types/react': '*'
       '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
     peerDependenciesMeta:
       '@types/react':
         optional: true
       '@types/react-dom':
         optional: true
     dependencies:
-      '@babel/runtime': 7.24.7
-      '@radix-ui/primitive': 1.0.1
-      '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
-      '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
-      '@radix-ui/react-id': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
-      '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
-      '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
-      '@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.3.1)
-      '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.3)(react@18.3.1)
+      '@radix-ui/primitive': 1.1.0
+      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
+      '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.3.1)
+      '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
+      '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.3)(react@18.3.1)
+      '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
+      '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1)
+      '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
+      '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1)
       '@types/react': 18.3.3
       '@types/react-dom': 18.3.0
       aria-hidden: 1.2.4
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
-      react-remove-scroll: 2.5.5(@types/react@18.3.3)(react@18.3.1)
+      react-remove-scroll: 2.6.0(@types/react@18.3.3)(react@18.3.1)
     dev: false
 
   /@radix-ui/react-direction@1.1.0(@types/react@18.3.3)(react@18.3.1):
@@ -2177,33 +2148,32 @@ packages:
       react: 18.3.1
     dev: false
 
-  /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==}
+  /@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==}
     peerDependencies:
       '@types/react': '*'
       '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
     peerDependenciesMeta:
       '@types/react':
         optional: true
       '@types/react-dom':
         optional: true
     dependencies:
-      '@babel/runtime': 7.24.7
-      '@radix-ui/primitive': 1.0.1
-      '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
-      '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.3.3)(react@18.3.1)
+      '@radix-ui/primitive': 1.1.0
+      '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1)
+      '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.3)(react@18.3.1)
       '@types/react': 18.3.3
       '@types/react-dom': 18.3.0
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
-  /@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==}
+  /@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==}
     peerDependencies:
       '@types/react': '*'
       '@types/react-dom': '*'
@@ -2226,20 +2196,6 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
-  /@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.3)(react@18.3.1):
-    resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@types/react': 18.3.3
-      react: 18.3.1
-    dev: false
-
   /@radix-ui/react-focus-guards@1.1.0(@types/react@18.3.3)(react@18.3.1):
     resolution: {integrity: sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==}
     peerDependencies:
@@ -2253,27 +2209,17 @@ packages:
       react: 18.3.1
     dev: false
 
-  /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==}
+  /@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.3)(react@18.3.1):
+    resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
     peerDependencies:
       '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
     peerDependenciesMeta:
       '@types/react':
         optional: true
-      '@types/react-dom':
-        optional: true
     dependencies:
-      '@babel/runtime': 7.24.7
-      '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
-      '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1)
       '@types/react': 18.3.3
-      '@types/react-dom': 18.3.0
       react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
     dev: false
 
   /@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
@@ -2298,21 +2244,6 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
-  /@radix-ui/react-id@1.0.1(@types/react@18.3.3)(react@18.3.1):
-    resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@types/react': 18.3.3
-      react: 18.3.1
-    dev: false
-
   /@radix-ui/react-id@1.1.0(@types/react@18.3.3)(react@18.3.1):
     resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
     peerDependencies:
@@ -2410,27 +2341,6 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
-  /@radix-ui/react-portal@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
-      '@types/react': 18.3.3
-      '@types/react-dom': 18.3.0
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
-    dev: false
-
   /@radix-ui/react-portal@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
     resolution: {integrity: sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==}
     peerDependencies:
@@ -2452,22 +2362,21 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
-  /@radix-ui/react-presence@1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
+  /@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==}
     peerDependencies:
       '@types/react': '*'
       '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
     peerDependenciesMeta:
       '@types/react':
         optional: true
       '@types/react-dom':
         optional: true
     dependencies:
-      '@babel/runtime': 7.24.7
-      '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.3)(react@18.3.1)
+      '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1)
       '@types/react': 18.3.3
       '@types/react-dom': 18.3.0
       react: 18.3.1
@@ -2516,27 +2425,6 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
-  /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.3.1)
-      '@types/react': 18.3.3
-      '@types/react-dom': 18.3.0
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
-    dev: false
-
   /@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
     resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==}
     peerDependencies:
@@ -2723,21 +2611,6 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
-  /@radix-ui/react-slot@1.0.2(@types/react@18.3.3)(react@18.3.1):
-    resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@types/react': 18.3.3
-      react: 18.3.1
-    dev: false
-
   /@radix-ui/react-slot@1.1.0(@types/react@18.3.3)(react@18.3.1):
     resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==}
     peerDependencies:
@@ -2858,20 +2731,6 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
-  /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.3.3)(react@18.3.1):
-    resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@types/react': 18.3.3
-      react: 18.3.1
-    dev: false
-
   /@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.3)(react@18.3.1):
     resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
     peerDependencies:
@@ -2885,21 +2744,6 @@ packages:
       react: 18.3.1
     dev: false
 
-  /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.3.3)(react@18.3.1):
-    resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@types/react': 18.3.3
-      react: 18.3.1
-    dev: false
-
   /@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.3)(react@18.3.1):
     resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
     peerDependencies:
@@ -2914,21 +2758,6 @@ packages:
       react: 18.3.1
     dev: false
 
-  /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.3.3)(react@18.3.1):
-    resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1)
-      '@types/react': 18.3.3
-      react: 18.3.1
-    dev: false
-
   /@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.3)(react@18.3.1):
     resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==}
     peerDependencies:
@@ -2943,20 +2772,6 @@ packages:
       react: 18.3.1
     dev: false
 
-  /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.3.3)(react@18.3.1):
-    resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.24.7
-      '@types/react': 18.3.3
-      react: 18.3.1
-    dev: false
-
   /@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.3)(react@18.3.1):
     resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
     peerDependencies:
@@ -3582,31 +3397,35 @@ packages:
       '@swc/counter': 0.1.3
     dev: true
 
-  /@tanstack/history@1.31.16:
-    resolution: {integrity: sha512-rahAZXlR879P7dngDH7BZwGYiODA9D5Hqo6nUHn9GAURcqZU5IW0ZiT54dPtV5EPES7muZZmknReYueDHs7FFQ==}
+  /@tanstack/history@1.61.1:
+    resolution: {integrity: sha512-2CqERleeqO3hkhJmyJm37tiL3LYgeOpmo8szqdjgtnnG0z7ZpvzkZz6HkfOr9Ca/ha7mhAiouSvLYuLkM37AMg==}
     engines: {node: '>=12'}
 
-  /@tanstack/react-router@1.35.3(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-9B4BAK/WzAa+xsc5LVkDgOcIIFQlfZv7xykg7VWW88PpiY0dQku4IxkUNY5vtQ4YkdJ9Z0QIt33tVnvVIFS4OQ==}
+  /@tanstack/react-router@1.62.0(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-Vry/GwXiIHHpFilXy3M82Oyh1O1ULJNHVv8XbZ2QtcvftxkXcotsWD1Rt3KhgvXjBA8Zb/ueYN9ASPtkTdMoqQ==}
     engines: {node: '>=12'}
     peerDependencies:
-      react: '>=16.8'
-      react-dom: '>=16.8'
+      '@tanstack/router-generator': 1.58.12
+      react: '>=18'
+      react-dom: '>=18'
+    peerDependenciesMeta:
+      '@tanstack/router-generator':
+        optional: true
     dependencies:
-      '@tanstack/history': 1.31.16
-      '@tanstack/react-store': 0.2.1(react-dom@18.3.1)(react@18.3.1)
+      '@tanstack/history': 1.61.1
+      '@tanstack/react-store': 0.5.5(react-dom@18.3.1)(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
       tiny-invariant: 1.3.3
       tiny-warning: 1.0.3
 
-  /@tanstack/react-store@0.2.1(react-dom@18.3.1)(react@18.3.1):
-    resolution: {integrity: sha512-tEbMCQjbeVw9KOP/202LfqZMSNAVi6zYkkp1kBom8nFuMx/965Hzes3+6G6b/comCwVxoJU8Gg9IrcF8yRPthw==}
+  /@tanstack/react-store@0.5.5(react-dom@18.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-1orYXGatBqXCYKuroFwV8Ll/6aDa5E3pU6RR4h7RvRk7TmxF1+zLCsWALZaeijXkySNMGmvawSbUXRypivg2XA==}
     peerDependencies:
-      react: '>=16'
-      react-dom: '>=16'
+      react: ^17.0.0 || ^18.0.0
+      react-dom: ^17.0.0 || ^18.0.0
     dependencies:
-      '@tanstack/store': 0.1.3
+      '@tanstack/store': 0.5.5
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
       use-sync-external-store: 1.2.2(react@18.3.1)
@@ -3634,7 +3453,7 @@ packages:
       react-dom: 18.3.1(react@18.3.1)
     dev: false
 
-  /@tanstack/router-devtools@1.35.3(@tanstack/react-router@1.35.3)(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1):
+  /@tanstack/router-devtools@1.35.3(@tanstack/react-router@1.62.0)(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1):
     resolution: {integrity: sha512-mLG3JJJTc16gu2dKFJagAh9weyraoewVJG4myVb3dde/DdJ6XPuQbJLXv3s5YbPykNtLFmHJXWwNTjndyiaCWQ==}
     engines: {node: '>=12'}
     peerDependencies:
@@ -3642,7 +3461,7 @@ packages:
       react: '>=16.8'
       react-dom: '>=16.8'
     dependencies:
-      '@tanstack/react-router': 1.35.3(react-dom@18.3.1)(react@18.3.1)
+      '@tanstack/react-router': 1.62.0(react-dom@18.3.1)(react@18.3.1)
       clsx: 2.1.1
       date-fns: 2.30.0
       goober: 2.1.14(csstype@3.1.3)
@@ -3685,8 +3504,19 @@ packages:
       - vite
     dev: true
 
-  /@tanstack/store@0.1.3:
-    resolution: {integrity: sha512-GnolmC8Fr4mvsHE1fGQmR3Nm0eBO3KnZjDU0a+P3TeQNM/dDscFGxtA7p31NplQNW3KwBw4t1RVFmz0VeKLxcw==}
+  /@tanstack/router-zod-adapter@1.62.0(@tanstack/react-router@1.62.0)(zod@3.23.8):
+    resolution: {integrity: sha512-eJHaUYTV3jrcUGGvtywSCRhHsrQertWnMfp8jslAFluvINfX2zWbJ+F9NRQDrMC5GkYYgv4Qze1g/d88u096rg==}
+    engines: {node: '>=12'}
+    peerDependencies:
+      '@tanstack/react-router': '>=1.43.2'
+      zod: '>=3'
+    dependencies:
+      '@tanstack/react-router': 1.62.0(react-dom@18.3.1)(react@18.3.1)
+      zod: 3.23.8
+    dev: false
+
+  /@tanstack/store@0.5.5:
+    resolution: {integrity: sha512-EOSrgdDAJExbvRZEQ/Xhh9iZchXpMN+ga1Bnk8Nmygzs8TfiE6hbzThF+Pr2G19uHL6+DTDTHhJ8VQiOd7l4tA==}
 
   /@tanstack/table-core@8.20.5:
     resolution: {integrity: sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==}
@@ -6421,8 +6251,8 @@ packages:
       tslib: 2.6.3
     dev: false
 
-  /react-remove-scroll@2.5.5(@types/react@18.3.3)(react@18.3.1):
-    resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==}
+  /react-remove-scroll@2.5.7(@types/react@18.3.3)(react@18.3.1):
+    resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==}
     engines: {node: '>=10'}
     peerDependencies:
       '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -6440,8 +6270,8 @@ packages:
       use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1)
     dev: false
 
-  /react-remove-scroll@2.5.7(@types/react@18.3.3)(react@18.3.1):
-    resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==}
+  /react-remove-scroll@2.6.0(@types/react@18.3.3)(react@18.3.1):
+    resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==}
     engines: {node: '>=10'}
     peerDependencies:
       '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -7323,7 +7153,7 @@ packages:
       react: ^16.8 || ^17.0 || ^18.0
       react-dom: ^16.8 || ^17.0 || ^18.0
     dependencies:
-      '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
+      '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
     transitivePeerDependencies:
diff --git a/frontend/src/api/endpoints/candidates.endpoint.ts b/frontend/src/api/endpoints/candidates.endpoint.ts
new file mode 100644
index 0000000000000000000000000000000000000000..27d1b1af5a3e94bb8d748c4deffa00b521cb5c45
--- /dev/null
+++ b/frontend/src/api/endpoints/candidates.endpoint.ts
@@ -0,0 +1,26 @@
+import { z } from "zod";
+import { CandidatesDto } from "../models/candidates.model";
+import api from "../utils/api";
+
+export namespace CandidatesEndpoint {
+  export const getActiveCandidates = (vacancyId: number) =>
+    api.get(`/vacancies/candidates/active/${vacancyId}`, {
+      schema: z.object({
+        candidates: z.array(CandidatesDto.ActiveCandidate),
+      }),
+    });
+
+  export const getDeclinedCandidates = (vacancyId: number) =>
+    api.get(`/vacancies/candidates/declined/${vacancyId}`, {
+      schema: z.object({
+        candidates: z.array(CandidatesDto.DeclinedCandidate),
+      }),
+    });
+
+  export const getPotentialCandidates = (vacancyId: number) =>
+    api.get(`/vacancies/candidates/potential/${vacancyId}`, {
+      schema: z.object({
+        candidates: z.array(CandidatesDto.PotentialCandidate),
+      }),
+    });
+}
diff --git a/frontend/src/api/endpoints/skill.endpoint.ts b/frontend/src/api/endpoints/skill.endpoint.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3ffbb28ebded5230eed56a13103f3b60e0bcd6db
--- /dev/null
+++ b/frontend/src/api/endpoints/skill.endpoint.ts
@@ -0,0 +1,21 @@
+import { z } from "zod";
+import { SkillDto } from "../models/skill.model";
+import api from "../utils/api";
+
+export namespace SkillEndpoint {
+  export const list = () => {
+    return api.get("/vacancies/skills/all", {
+      schema: z.array(SkillDto.Item),
+    });
+  };
+
+  export const create = (names: string[]) => {
+    return api.post(
+      "/vacancies/skills/new",
+      names.map((name) => ({ name })),
+      {
+        schema: z.array(SkillDto.Item),
+      },
+    );
+  };
+}
diff --git a/frontend/src/api/endpoints/tasks.endpoint.ts b/frontend/src/api/endpoints/tasks.endpoint.ts
new file mode 100644
index 0000000000000000000000000000000000000000..652d68d361fee809ad384879c726f9bc3b210393
--- /dev/null
+++ b/frontend/src/api/endpoints/tasks.endpoint.ts
@@ -0,0 +1,12 @@
+import { z } from "zod";
+import { SkillDto } from "../models/skill.model";
+import api from "../utils/api";
+import { TaskDto } from "../models/task.model";
+
+export namespace TasksEndpoint {
+  export const list = () => {
+    return api.get("/tasks/all", {
+      schema: z.array(TaskDto.Item),
+    });
+  };
+}
diff --git a/frontend/src/api/endpoints/vacanvy.endpoint.ts b/frontend/src/api/endpoints/vacanvy.endpoint.ts
index 69c00dd98e1ed38a9972d505d0c3859d02279935..e18948a9de279d145208c75411885e52d5e6213a 100644
--- a/frontend/src/api/endpoints/vacanvy.endpoint.ts
+++ b/frontend/src/api/endpoints/vacanvy.endpoint.ts
@@ -2,6 +2,8 @@ import api from "api/utils/api";
 import { Query } from "../utils/buildQueryString";
 import { VacancyDto } from "../models/vacancy.model";
 import { paged } from "../models/paged.model";
+import { z } from "zod";
+import { Priority } from "@/types/priority.type";
 
 export namespace VacancyEndpoint {
   export interface ListTemplate extends Query {
@@ -19,7 +21,37 @@ export namespace VacancyEndpoint {
     });
 
   export const getById = (id: string) =>
-    api.get(`/vacancies/${id}`, {
+    api.get(`/vacancies/roadmap/${id}`, {
       schema: VacancyDto.DetailedItem,
     });
+
+  interface VacancyTemplate {
+    name: string;
+    priority: Priority.Priority;
+    deadline: string;
+    profession: string;
+    area: string;
+    supervisor: string;
+    city: string;
+    experienceFrom: string;
+    experienceTo: string;
+    education: string;
+    keySkills: number[];
+    additionalSkills: number[];
+    description: string;
+    typeOfEmployment: string;
+    quantity: number;
+    direction: string;
+    salary_low: number;
+    salary_high: number;
+    stages: {
+      order: number;
+      name: string;
+      duration: number;
+    }[];
+  }
+  export const create = (vacancy: VacancyTemplate) =>
+    api.post("/vacancies/new", vacancy, {
+      schema: z.number(),
+    });
 }
diff --git a/frontend/src/api/models/candidates.model.ts b/frontend/src/api/models/candidates.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..04a523aab1630f44970ad842f4b08308691a9203
--- /dev/null
+++ b/frontend/src/api/models/candidates.model.ts
@@ -0,0 +1,29 @@
+import { z } from "zod";
+import { VacancyDto } from "./vacancy.model";
+
+export namespace CandidatesDto {
+  export const Candidate = z.object({
+    candidate_id: z.number(),
+    source: z.string(),
+    similarity: z.number(),
+  });
+  export type Candidate = z.infer<typeof Candidate>;
+
+  export const ActiveCandidate = Candidate.extend({
+    stage_name: z.string(),
+    date_of_accept: z.string(),
+  });
+  export type ActiveCandidate = z.infer<typeof ActiveCandidate>;
+
+  export const DeclinedCandidate = Candidate.extend({
+    date_of_decline: z.string(),
+    reason: z.string(),
+  });
+  export type DeclinedCandidate = z.infer<typeof DeclinedCandidate>;
+
+  export const PotentialCandidate = z.object({
+    source: z.string(),
+    similarity: z.number(),
+  });
+  export type PotentialCandidate = z.infer<typeof PotentialCandidate>;
+}
diff --git a/frontend/src/api/models/skill.model.ts b/frontend/src/api/models/skill.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a13974ad8fdf83a8137d6701559660e8ace2b4f2
--- /dev/null
+++ b/frontend/src/api/models/skill.model.ts
@@ -0,0 +1,9 @@
+import { z } from "zod";
+
+export namespace SkillDto {
+  export const Item = z.object({
+    name: z.string(),
+    id: z.number(),
+  });
+  export type Item = z.infer<typeof Item>;
+}
diff --git a/frontend/src/api/models/task.model.ts b/frontend/src/api/models/task.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ac4d791fd8e1d0af611157419cc814f020b0fc95
--- /dev/null
+++ b/frontend/src/api/models/task.model.ts
@@ -0,0 +1,5 @@
+import { z } from "node_modules/zod/lib";
+
+export namespace TaskDto {
+  export const Item = z.object({});
+}
diff --git a/frontend/src/api/models/vacancy.model.ts b/frontend/src/api/models/vacancy.model.ts
index 20109e201cb4c54bb27b355d48a70d9e8fcb0f91..a378aaa1ce8133f9fa67e9441e8f4613ccba5672 100644
--- a/frontend/src/api/models/vacancy.model.ts
+++ b/frontend/src/api/models/vacancy.model.ts
@@ -1,28 +1,8 @@
 import { z } from "zod";
 import { Priority } from "@/types/priority.type";
+import { SkillDto } from "./skill.model";
 
 export namespace VacancyDto {
-  const DetailedVacancySchema = z.object({
-    id: z.number(),
-    name: z.string(),
-    priority: Priority.Schema,
-    deadline: z.string().datetime(),
-    profession: z.string(),
-    area: z.string(),
-    supervisor: z.string(),
-    city: z.string(),
-    experience_from: z.number(),
-    experience_to: z.number(),
-    education: z.string(),
-    quantity: z.number(),
-    description: z.string(),
-    type_of_employment: z.string(),
-    vacancy_skills: z.array(z.any()),
-    recruiter: z.null(),
-    hr: z.null(),
-    created_at: z.string().datetime(),
-  });
-
   const SourceSchema = z.object({
     name: z.string(),
     count: z.number(),
@@ -60,17 +40,16 @@ export namespace VacancyDto {
     quantity: z.number(),
     description: z.string(),
     type_of_employment: z.string(),
-    vacancy_skills: z.array(
-      z.object({
-        name: z.string(),
-        id: z.number(),
-      }),
-    ),
+    vacancy_skills: z.array(SkillDto.Item),
+    // additional_skills: z.array(z.any()),
+    recruiter: z.null(),
+    hr: z.null(),
+    created_at: z.string(),
   });
   export type Item = z.infer<typeof Item>;
 
   export const DetailedItem = z.object({
-    vacancy: DetailedVacancySchema,
+    vacancy: Item,
     stages: z.array(StageSchema),
   });
   export type DetailedItem = z.infer<typeof DetailedItem>;
@@ -92,7 +71,16 @@ export const mockVacancy: VacancyDto.DetailedItem = {
     quantity: 1,
     description: "Разработчик",
     type_of_employment: "Полная занятость",
-    vacancy_skills: [],
+    vacancy_skills: [
+      {
+        id: 1,
+        name: "JavaScript",
+      },
+      {
+        id: 2,
+        name: "TypeScript",
+      },
+    ],
     recruiter: null,
     hr: null,
     created_at: "2021-01-01T00:00:00",
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
index 51f0d2d097c3c542d3c487f306d5c7d17b0f37fe..0097dd35ed0c11df97c96a291c551e22b64369f6 100644
--- a/frontend/src/app/globals.css
+++ b/frontend/src/app/globals.css
@@ -66,6 +66,15 @@
   #app {
     @apply bg-gray-50 text-gray-900 transition-all duration-150 overflow-hidden flex antialiased;
   }
+  *::-webkit-scrollbar {
+    @apply w-1 h-1;
+  }
+  *::-webkit-scrollbar-track {
+    @apply bg-gray-100;
+  }
+  *::-webkit-scrollbar-thumb {
+    @apply bg-gray-300 rounded-full;
+  }
 }
 
 html,
diff --git a/frontend/src/components/DropdownMultiple.tsx b/frontend/src/components/DropdownMultiple.tsx
index 4a3594a613b354165d9ddeb0c46028f16c074618..c7d653761c0f9b57d51bce3f5d32472761a20065 100644
--- a/frontend/src/components/DropdownMultiple.tsx
+++ b/frontend/src/components/DropdownMultiple.tsx
@@ -67,12 +67,12 @@ const DropdownMultiple = observer(<T,>(p: ComboboxMultipleProps<T>) => {
 
   return (
     <Combobox value={p.value} multiple onChange={p.onChange}>
-      <div className="relative text-sm">
-        {p.label && <Label className="text-sm">{p.label}</Label>}
+      <div className="relative text-sm pt-0.5">
+        {p.label && <Label className="font-medium">{p.label}</Label>}
         <div
           className={cn(
             "relative h-fit flex items-center w-full",
-            p.label && "mt-2",
+            p.label && "mt-1",
           )}
         >
           <ComboboxInput
@@ -92,8 +92,9 @@ const DropdownMultiple = observer(<T,>(p: ComboboxMultipleProps<T>) => {
             displayValue={() => (inputFocused ? "" : placeholder)}
             onChange={(event) => setQuery(event.target.value)}
           />
-          <ComboboxButton className="h-5 w-5 absolute right-2 text-accent-foreground">
+          <ComboboxButton className="h-5 w-5 items-center justify-center flex absolute right-2 text-accent-foreground">
             <ChevronDownIcon
+              strokeWidth={1.5}
               className={cn("transition-all", inputFocused && "rotate-180")}
             />
           </ComboboxButton>
@@ -107,12 +108,12 @@ const DropdownMultiple = observer(<T,>(p: ComboboxMultipleProps<T>) => {
           afterLeave={() => setQuery("")}
         >
           <ComboboxOptions
-            className="absolute z-10 mt-1 max-h-60 w-full border overflow-auto rounded-xl py-2 bg-card text-card-foreground"
+            className="absolute z-10 mt-1 max-h-60 w-full border overflow-auto rounded-lg py-2 bg-card text-card-foreground"
             style={{
               scrollbarWidth: "thin",
             }}
           >
-            {filteredOptions.length === 0 && query !== "" ? (
+            {filteredOptions.length === 0 ? (
               <div className="px-4 py-2 text-muted-foreground">
                 Ничего не найдено
               </div>
@@ -131,7 +132,7 @@ const DropdownMultiple = observer(<T,>(p: ComboboxMultipleProps<T>) => {
                   {({ selected }) => (
                     <>
                       <span>{p.render(option)}</span>
-                      {selected && <CheckIcon className="w-5 h-5" />}
+                      {selected && <CheckIcon className="size-3" />}
                     </>
                   )}
                 </ComboboxOption>
diff --git a/frontend/src/components/dropdowns/SkillsDropdown.tsx b/frontend/src/components/dropdowns/SkillsDropdown.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..407159113f88a9615bb31c2ffd9ec77a7a49c607
--- /dev/null
+++ b/frontend/src/components/dropdowns/SkillsDropdown.tsx
@@ -0,0 +1,45 @@
+import { SkillEndpoint } from "@/api/endpoints/skill.endpoint";
+import { observable } from "mobx";
+import { observer } from "mobx-react-lite";
+import { FC, useEffect } from "react";
+import { SkillDto } from "@/api/models/skill.model";
+import DropdownMultiple from "../DropdownMultiple";
+
+interface Props {
+  value: SkillDto.Item[];
+  onChange: (value: SkillDto.Item[]) => void;
+  label?: string;
+  filter?: (value: SkillDto.Item) => boolean;
+}
+
+export const skillsStore = observable<{ skills: SkillDto.Item[] }>({
+  skills: [],
+});
+
+export const SkillsDropdown: FC<Props> = observer((x) => {
+  useEffect(() => {
+    const fetchSkills = async () => {
+      skillsStore.skills = await SkillEndpoint.list();
+    };
+
+    if (skillsStore.skills.length === 0) {
+      fetchSkills();
+    }
+  }, []);
+
+  const skills = x.filter
+    ? skillsStore.skills.filter(x.filter)
+    : skillsStore.skills;
+
+  return (
+    <DropdownMultiple
+      label={x.label}
+      value={x.value}
+      onChange={x.onChange}
+      options={skills}
+      compare={(a) => a.name}
+      render={(item) => item.name}
+      placeholder="Выберите навыки"
+    />
+  );
+});
diff --git a/frontend/src/components/pages/tasks/reject-candidate.tsx b/frontend/src/components/pages/tasks/reject-candidate.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e12070c27f7c726161feb030ce136a425be305f5
--- /dev/null
+++ b/frontend/src/components/pages/tasks/reject-candidate.tsx
@@ -0,0 +1,55 @@
+import { Button } from "@/components/ui/button";
+import {
+  Dialog,
+  DialogClose,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+  DialogTrigger,
+} from "@/components/ui/dialog";
+import { Textarea } from "@/components/ui/textarea";
+import { observer } from "mobx-react-lite";
+import { FC, useState } from "react";
+
+interface Props {
+  onReject: (reason: string) => void;
+}
+
+export const RejectCandidate: FC<Props> = observer((x) => {
+  const [reason, setReason] = useState("");
+
+  return (
+    <Dialog>
+      <DialogTrigger className="text-red-500 underline hover:no-underline">
+        Отказ
+      </DialogTrigger>
+      <DialogContent>
+        <DialogHeader>
+          <DialogTitle>Отказ</DialogTitle>
+          <DialogDescription>Укажите причину отказа</DialogDescription>
+        </DialogHeader>
+        <Textarea
+          placeholder="Отказался от предложения"
+          value={reason}
+          onChange={(e) => setReason(e.target.value)}
+        />
+        <DialogFooter>
+          <DialogClose asChild>
+            <Button
+              variant="destructive"
+              disabled={!reason}
+              onClick={() => {
+                x.onReject(reason);
+                setReason("");
+              }}
+            >
+              Отказать
+            </Button>
+          </DialogClose>
+        </DialogFooter>
+      </DialogContent>
+    </Dialog>
+  );
+});
diff --git a/frontend/src/components/pages/vacancy/StagesForm.tsx b/frontend/src/components/pages/vacancy/StagesForm.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..692e3a0588b2121cef8f03e07df262f244a1f5bb
--- /dev/null
+++ b/frontend/src/components/pages/vacancy/StagesForm.tsx
@@ -0,0 +1,64 @@
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { NewVacancyStore } from "@/stores/new-vacancy";
+import { PlusIcon, Trash2Icon } from "lucide-react";
+import { observer } from "mobx-react-lite";
+import { FC } from "react";
+
+interface Props {
+  vm: NewVacancyStore;
+}
+
+export const StagesForm: FC<Props> = observer((x) => {
+  return (
+    <div className="col-span-2 w-full space-y-3">
+      <h3 className="text-xl font-medium">Этапы отбора</h3>
+      <div className="flex w-full *:flex-1 gap-4">
+        <div className="flex gap-2 flex-col">
+          <Label>Название этапа</Label>
+          {x.vm.stages.map((v) => (
+            <div key={v.id}>
+              <Input
+                autoFocus
+                className="bg-primary text-primary-foreground border-none font-medium"
+                value={v.name}
+                onChange={(e) => {
+                  v.name = e.target.value;
+                }}
+              />
+            </div>
+          ))}
+          <Button variant="secondary" size="sm" onClick={() => x.vm.addStage()}>
+            <PlusIcon />
+          </Button>
+        </div>
+        <div className="flex gap-2 flex-col">
+          <Label>SLA этапа в днях</Label>
+          {x.vm.stages.map((v) => (
+            <div key={v.id} className="flex items-center gap-2">
+              <Input
+                className="w-[100px]"
+                value={v.sla}
+                type="number"
+                onChange={(e) => {
+                  const number = Number(e.target.value);
+                  if (!isNaN(number) && number >= 0) {
+                    v.sla = number;
+                  }
+                }}
+              />
+              <Button
+                size="icon"
+                variant="ghost"
+                onClick={() => x.vm.removeStage(v)}
+              >
+                <Trash2Icon className="size-5" />
+              </Button>
+            </div>
+          ))}
+        </div>
+      </div>
+    </div>
+  );
+});
diff --git a/frontend/src/components/pages/vacancy/add-candidate.modal.tsx b/frontend/src/components/pages/vacancy/add-candidate.modal.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..da8a97444ec00b23e23f8f17f10be821d514b837
--- /dev/null
+++ b/frontend/src/components/pages/vacancy/add-candidate.modal.tsx
@@ -0,0 +1,55 @@
+import {
+  Dialog,
+  DialogClose,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+  DialogTrigger,
+} from "@/components/ui/dialog";
+import { Button, buttonVariants } from "@/components/ui/button";
+import { PlusIcon } from "lucide-react";
+import { observer } from "mobx-react-lite";
+import { FC } from "react";
+import { IconInput } from "@/components/ui/input";
+
+interface Props {
+  onSubmit: (candidate: { source: string; resumeLink: string }) => void;
+}
+
+export const AddCandidateModal: FC<Props> = observer((x) => {
+  return (
+    <Dialog>
+      <DialogTrigger
+        className={buttonVariants({ variant: "outline", size: "sm" })}
+      >
+        <>
+          <PlusIcon className="size-4" />
+          Добавить кандидата
+        </>
+      </DialogTrigger>
+      <DialogContent>
+        <DialogHeader>
+          <DialogTitle>Добавить кандидата</DialogTitle>
+          <DialogDescription>Укажите данные кандидата</DialogDescription>
+        </DialogHeader>
+        <IconInput
+          id="source"
+          label="Источник"
+          placeholder="Например, LinkedIn"
+        />
+        <IconInput
+          id="resumeLink"
+          label="Ссылка на резюме"
+          placeholder="Например, https://linkedin.com/in/username"
+        />
+        <DialogFooter>
+          <DialogClose asChild>
+            <Button>Добавить</Button>
+          </DialogClose>
+        </DialogFooter>
+      </DialogContent>
+    </Dialog>
+  );
+});
diff --git a/frontend/src/components/pages/vacancy/analytics.view.tsx b/frontend/src/components/pages/vacancy/analytics.view.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b108301867134567885e7910241e685192adedb6
--- /dev/null
+++ b/frontend/src/components/pages/vacancy/analytics.view.tsx
@@ -0,0 +1,31 @@
+import { VacancyStore } from "@/stores/vanacy.store";
+import { observer } from "mobx-react-lite";
+import { FC } from "react";
+
+interface Props {
+  vm: VacancyStore;
+}
+
+const Card: FC<{
+  title: string;
+  value: string;
+}> = observer((x) => {
+  return (
+    <div className="font-medium rounded-xl p-4 bg-white flex flex-col gap-2">
+      <h3 className="text-xl">{x.value}</h3>
+      <p className="text-nowrap">{x.title}</p>
+    </div>
+  );
+});
+
+export const AnalyticsView: FC<Props> = observer((x) => {
+  return (
+    <div className="flex flex-wrap gap-4">
+      <Card title="Наполненность рынка" value="чел. на вакансию" />
+      <Card title="Диапазон ожиданий по ЗП" value="МЛН – МЛН" />
+      <Card title="Медиальное значение по ожидаемой ЗП" value="МЛН" />
+      <Card title="Диапазон по ЗП у конкурентов" value="МЛН – МЛН" />
+      <Card title="Медиальное значение по ЗП у конкурентов" value="МЛН – МЛН" />
+    </div>
+  );
+});
diff --git a/frontend/src/components/pages/vacancy/candidates.view.tsx b/frontend/src/components/pages/vacancy/candidates.view.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..bb5e1f21450204077cc41e672ffe016d52d4d813
--- /dev/null
+++ b/frontend/src/components/pages/vacancy/candidates.view.tsx
@@ -0,0 +1,123 @@
+import { VacancyStore } from "@/stores/vanacy.store";
+import { FCVM } from "@/utils/vm";
+import { observer } from "mobx-react-lite";
+import { useEffect } from "react";
+import { ChartSection } from "./chart-section";
+import { Column, DataTable } from "@/components/ui/data-table";
+import { CandidatesDto } from "@/api/models/candidates.model";
+import { AddCandidateModal } from "./add-candidate.modal";
+
+const activeColumns: Column<CandidatesDto.ActiveCandidate>[] = [
+  {
+    header: "â„–",
+    accessor: (x) => x.candidate_id,
+  },
+  {
+    header: "Дата отклика",
+    accessor: (x) => new Date(x.date_of_accept).toLocaleDateString("ru-RU"),
+  },
+  {
+    header: "Статус",
+    accessor: (x) => x.stage_name,
+  },
+  {
+    header: "Источник",
+    accessor: (x) => x.source,
+  },
+  {
+    header: "Схожесть\nс вакансией",
+    accessor: (x) => `${x.similarity}%`,
+  },
+  {
+    header: "Резюме",
+    accessor: (x) => (
+      <a
+        href={"https://google.com"}
+        target="_blank"
+        rel="noreferrer"
+        className="text-blue-500 underline"
+      >
+        Файл
+      </a>
+    ),
+  },
+];
+
+const declinedColumns: Column<CandidatesDto.DeclinedCandidate>[] = [
+  {
+    header: "â„–",
+    accessor: (x) => x.candidate_id,
+  },
+  {
+    header: "Дата отказа",
+    accessor: (x) => new Date(x.date_of_decline).toLocaleDateString("ru-RU"),
+  },
+  {
+    header: "Причина",
+    accessor: (x) => x.reason,
+  },
+  {
+    header: "Источник",
+    accessor: (x) => x.source,
+  },
+  {
+    header: "Схожесть\nс вакансией",
+    accessor: (x) => `${x.similarity}%`,
+  },
+  {
+    header: "Резюме",
+    accessor: (x) => (
+      <a
+        href={"https://google.com"}
+        target="_blank"
+        rel="noreferrer"
+        className="text-blue-500 underline"
+      >
+        Файл
+      </a>
+    ),
+  },
+];
+
+const potentialColumns: Column<CandidatesDto.PotentialCandidate>[] = [
+  {
+    header: "Источник",
+    accessor: (x) => x.source,
+  },
+  {
+    header: "% схожести с вакансией",
+    accessor: (x) => `${x.similarity}%`,
+  },
+];
+
+export const CandidatesView: FCVM<VacancyStore> = observer((x) => {
+  useEffect(() => {
+    x.vm.loadCandidates();
+  }, [x.vm]);
+
+  return (
+    <div className="flex flex-col gap-4">
+      <ChartSection
+        title="Кандидаты по вакансии"
+        actions={<AddCandidateModal onSubmit={(v) => void 0} />}
+      >
+        {x.vm.activeCandidates && (
+          <DataTable data={x.vm.activeCandidates} columns={activeColumns} />
+        )}
+      </ChartSection>
+      <ChartSection title="Кандидаты с отказами">
+        {x.vm.declinedCandidates && (
+          <DataTable data={x.vm.declinedCandidates} columns={declinedColumns} />
+        )}
+      </ChartSection>
+      <ChartSection title="Потенциальные кандидаты">
+        {x.vm.potentialCandidates && (
+          <DataTable
+            data={x.vm.potentialCandidates}
+            columns={potentialColumns}
+          />
+        )}
+      </ChartSection>
+    </div>
+  );
+});
diff --git a/frontend/src/components/pages/vacancy/chart-section.tsx b/frontend/src/components/pages/vacancy/chart-section.tsx
index fb9de854272ff03aefd5d27e76822927053920c6..fc4e5fd3d04623c9e2fbc4d5625a2c129838ef18 100644
--- a/frontend/src/components/pages/vacancy/chart-section.tsx
+++ b/frontend/src/components/pages/vacancy/chart-section.tsx
@@ -1,19 +1,63 @@
+import { Button } from "@/components/ui/button";
+import { cn } from "@/utils/cn";
+import { ReactNode } from "@tanstack/react-router";
+import { ChevronDownIcon } from "lucide-react";
 import { observer } from "mobx-react-lite";
-import { FC, PropsWithChildren } from "react";
+import { FC, PropsWithChildren, useState } from "react";
 
 interface Props extends PropsWithChildren {
   title: string;
-  description: string;
+  description?: string;
+  collapsible?: boolean;
+  actions?: ReactNode;
+  allowOverflow?: boolean;
 }
 
-export const ChartSection: FC<Props> = observer((x) => {
-  return (
-    <section className="bg-white p-5 rounded-2xl border overflow-hidden w-full">
-      <h2 className="text-2xl font-medium text-slate-500">{x.title}</h2>
-      <p className="text-sm mt-2">{x.description}</p>
-      <div className="flex gap-8 xl:gap-32 mt-3 overflow-auto">
-        {x.children}
-      </div>
-    </section>
-  );
-});
+export const ChartSection: FC<Props> = observer(
+  ({ collapsible = true, ...x }) => {
+    const [collapsed, setCollapsed] = useState(false);
+
+    return (
+      <section className="bg-white rounded-2xl border w-full py-5">
+        <div className="flex justify-between px-5 pb-0 items-center gap-1">
+          <h2
+            className={cn(
+              "text-2xl font-medium",
+              collapsible && "text-slate-500",
+            )}
+          >
+            {x.title}
+          </h2>
+          <div className="flex items-center gap-1">
+            {!collapsed && x.actions}
+            {collapsible && (
+              <Button
+                variant="ghost"
+                size="icon"
+                className={cn(
+                  "size-8 flex items-center justify-center",
+                  !collapsed && "rotate-180",
+                )}
+                onClick={() => setCollapsed(!collapsed)}
+              >
+                <ChevronDownIcon className="siz-6" />
+              </Button>
+            )}
+          </div>
+        </div>
+        {!collapsed && x.description && (
+          <p className="text-sm mt-2 px-5">{x.description}</p>
+        )}
+        <div
+          className={cn(
+            "flex flex-col md:flex-row gap-8 xl:gap-32 mt-3 overflow-auto px-5",
+            collapsed && "hidden",
+            x.allowOverflow && "overflow-visible",
+          )}
+        >
+          {x.children}
+        </div>
+      </section>
+    );
+  },
+);
diff --git a/frontend/src/components/pages/vacancy/overview.view.tsx b/frontend/src/components/pages/vacancy/overview.view.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9d38425f7db6aab50ee47e454b6b623842b297b2
--- /dev/null
+++ b/frontend/src/components/pages/vacancy/overview.view.tsx
@@ -0,0 +1,90 @@
+import { VacancyStore } from "@/stores/vanacy.store";
+import { cn } from "@/utils/cn";
+import { observer } from "mobx-react-lite";
+import { FC } from "react";
+
+interface Props {
+  vm: VacancyStore;
+}
+
+const LabelValue: FC<{ label: string; value: string; row?: boolean }> = ({
+  label,
+  value,
+  row = false,
+}) => {
+  return (
+    <div
+      className={cn(
+        "space-y-2 text-sm",
+        row ? "flex items-center gap-2 space-y-0" : "",
+      )}
+    >
+      <div className="font-semibold">{label}:</div>
+      <div>{value}</div>
+    </div>
+  );
+};
+
+const LabelList: FC<{ label: string; values: string[] }> = ({
+  label,
+  values,
+}) => {
+  return (
+    <div className="space-y-3 pt-2">
+      <h2 className="text-xl font-medium">{label}</h2>
+      <ul className="flex flex-wrap gap-2">
+        {values.map((x) => (
+          <li
+            key={x}
+            className="bg-slate-200 rounded-md px-3 py-1 text-slate-500"
+          >
+            {x}
+          </li>
+        ))}
+      </ul>
+    </div>
+  );
+};
+
+export const OverviewView: FC<Props> = observer((x) => {
+  return (
+    <div className="space-y-4 pt-8">
+      <div className="space-y-4 bg-white p-4 rounded-xl">
+        <h2 className="text-xl font-medium">Основная информация</h2>
+        <div className="flex gap-8 flex-wrap">
+          <LabelValue
+            label="Руководитель"
+            value={x.vm.vacancy.vacancy.supervisor}
+          />
+          <LabelValue label="Город" value={x.vm.vacancy.vacancy.city} />
+          <LabelValue
+            label="Режим работы"
+            value={x.vm.vacancy.vacancy.type_of_employment}
+          />
+        </div>
+        <LabelValue label="Описание" value={x.vm.vacancy.vacancy.description} />
+        <LabelList
+          label="Источники размещения вакансии"
+          values={x.vm.vacancy.stages[0].sources.map((x) => x.name)}
+        />
+      </div>
+      <div className="space-y-4 bg-white p-4 rounded-xl">
+        <h2 className="text-xl font-medium">Требования к кандидату</h2>
+        <LabelValue
+          row
+          label="Опыт работы"
+          value={`${x.vm.vacancy.vacancy.experience_from} – ${x.vm.vacancy.vacancy.experience_to} лет`}
+        />
+        <LabelValue
+          row
+          label="Образование"
+          value={x.vm.vacancy.vacancy.education}
+        />
+        <LabelList
+          label="Ключевые навыки"
+          values={x.vm.vacancy.vacancy.vacancy_skills.map((x) => x.name)}
+        />
+      </div>
+    </div>
+  );
+});
diff --git a/frontend/src/components/pages/vacancy/stats.view.tsx b/frontend/src/components/pages/vacancy/stats.view.tsx
index 078a127cb7891143f579b65c6c879cb57c5ef7f6..2e90886de1aec2fd5f6c422c03c6c7d0bb0937f2 100644
--- a/frontend/src/components/pages/vacancy/stats.view.tsx
+++ b/frontend/src/components/pages/vacancy/stats.view.tsx
@@ -18,12 +18,12 @@ interface Props {
 
 const COLORS = [
   "bg-teal-300",
-  "bg-indigo-300",
   "bg-blue-300",
   "bg-yellow-300",
   "bg-orange-300",
   "bg-green-300",
   "bg-red-300",
+  "bg-indigo-300",
   "bg-purple-300",
   "bg-pink-300",
   "bg-gray-300",
diff --git a/frontend/src/components/sidebar/Sidebar.tsx b/frontend/src/components/sidebar/Sidebar.tsx
index 51f09883b09b8cb367a3f8fe13e07088866e41a9..c7db00699e22877274f5bbd1ca773df7475f3bc0 100644
--- a/frontend/src/components/sidebar/Sidebar.tsx
+++ b/frontend/src/components/sidebar/Sidebar.tsx
@@ -1,7 +1,12 @@
 import { AnimatePresence, motion } from "framer-motion";
 import { observer } from "mobx-react-lite";
 import { Logo } from "../ui/logo";
-import { Link, useMatches } from "@tanstack/react-router";
+import {
+  Link,
+  useLocation,
+  useMatches,
+  useNavigate,
+} from "@tanstack/react-router";
 import { RouteType } from "@/types/router.type";
 import {
   BriefcaseIcon,
@@ -12,7 +17,7 @@ import {
 } from "lucide-react";
 import { cn } from "@/utils/cn";
 import { AuthState } from "./AuthState";
-import { Button } from "../ui/button";
+import { Button, buttonVariants } from "../ui/button";
 import { FC } from "react";
 import { ScrollArea } from "../ui/scroll-area";
 
@@ -26,14 +31,18 @@ const items: {
   to: RouteType;
   label: string;
   icon: React.ElementType;
+  active?: (v: string) => boolean;
+  disabled?: boolean;
 }[] = [
   {
     to: "/",
     label: "Вакансии",
     icon: BriefcaseIcon,
+    active: (pathname) =>
+      pathname.includes("/vacancy") && !pathname.includes("/vacancy/new"),
   },
   {
-    to: "/login",
+    to: "/tasks",
     label: "Мои задачи",
     icon: CheckSquareIcon,
   },
@@ -41,16 +50,21 @@ const items: {
     to: "/login",
     label: "Мои кандидаты",
     icon: UsersIcon,
+    disabled: true,
   },
   {
     to: "/login",
     label: "Качество подбора",
     icon: StarIcon,
+    disabled: true,
   },
 ];
 
 export const Sidebar: FC<{ hideSidebar?: boolean }> = observer(
   ({ hideSidebar }) => {
+    const { pathname } = useLocation();
+    const navigate = useNavigate();
+
     return (
       <AnimatePresence mode="popLayout" initial={false}>
         {!hideSidebar && (
@@ -68,8 +82,11 @@ export const Sidebar: FC<{ hideSidebar?: boolean }> = observer(
                   <li key={i}>
                     <Link
                       to={item.to}
+                      disabled={item.disabled}
                       className={cn(
-                        "font-medium flex items-center gap-2 px-4 py-2 text-sm text-slate-700 hover:bg-slate-100 rounded-md",
+                        item.active?.(pathname) && "active",
+                        "font-medium flex items-center gap-2 px-4 py-2 text-sm text-slate-700 rounded-md",
+                        !item.disabled && "hover:bg-slate-100",
                         "[&.active]:text-primary",
                       )}
                     >
@@ -80,7 +97,13 @@ export const Sidebar: FC<{ hideSidebar?: boolean }> = observer(
                 ))}
               </ul>
               <div className="mx-6 my-5">
-                <Button className="w-full gap-1">
+                <Button
+                  onClick={() => {
+                    navigate({ to: "/vacancy/new" });
+                  }}
+                  className={"w-full gap-1"}
+                  disabled={pathname.includes("/vacancy/new")}
+                >
                   <PlusIcon className="size-5" />
                   Создать вакансию
                 </Button>
diff --git a/frontend/src/components/ui/auto-form/types.ts b/frontend/src/components/ui/auto-form/types.ts
index b9774670240ce0f234be7dd4aa59c4e712652f65..09c57367a50c93a154a6bd84a9d7ed403091aec2 100644
--- a/frontend/src/components/ui/auto-form/types.ts
+++ b/frontend/src/components/ui/auto-form/types.ts
@@ -2,7 +2,7 @@ import { ControllerRenderProps, FieldValues } from "react-hook-form";
 import * as z from "zod";
 import { INPUT_COMPONENTS } from "./config";
 
-export type FieldConfigItem = {
+export interface FieldConfigItem {
   description?: React.ReactNode;
   inputProps?: React.InputHTMLAttributes<HTMLInputElement> & {
     showLabel?: boolean;
@@ -15,7 +15,7 @@ export type FieldConfigItem = {
   renderParent?: (props: {
     children: React.ReactNode;
   }) => React.ReactElement | null;
-};
+}
 
 export type FieldConfig<SchemaType extends z.infer<z.ZodObject<any, any>>> = {
   // If SchemaType.key is an object, create a nested FieldConfig, otherwise FieldConfigItem
@@ -31,12 +31,12 @@ export enum DependencyType {
   SETS_OPTIONS,
 }
 
-type BaseDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> = {
+interface BaseDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> {
   sourceField: keyof SchemaType;
   type: DependencyType;
   targetField: keyof SchemaType;
   when: (sourceFieldValue: any, targetFieldValue: any) => boolean;
-};
+}
 
 export type ValueDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> =
   BaseDependency<SchemaType> & {
@@ -64,7 +64,7 @@ export type Dependency<SchemaType extends z.infer<z.ZodObject<any, any>>> =
 /**
  * A FormInput component can handle a specific Zod type (e.g. "ZodBoolean")
  */
-export type AutoFormInputComponentProps = {
+export interface AutoFormInputComponentProps {
   zodInputProps: React.InputHTMLAttributes<HTMLInputElement>;
   field: ControllerRenderProps<FieldValues, any>;
   fieldConfigItem: FieldConfigItem;
@@ -73,4 +73,4 @@ export type AutoFormInputComponentProps = {
   fieldProps: any;
   zodItem: z.ZodAny;
   className?: string;
-};
+}
diff --git a/frontend/src/components/ui/data-table.tsx b/frontend/src/components/ui/data-table.tsx
index f92d0586e3ec6c24d648e530f5b09b4eedef858f..ecd1269340e717c7fe2566ae37a1880cbaec1919 100644
--- a/frontend/src/components/ui/data-table.tsx
+++ b/frontend/src/components/ui/data-table.tsx
@@ -1,3 +1,4 @@
+import { cn } from "@/utils/cn";
 import {
   Table,
   TableCaption,
@@ -8,19 +9,21 @@ import {
   TableCell,
   TableFooter,
 } from "./table";
+import { ReactNode } from "@tanstack/react-router";
 
 export interface Column<T> {
-  header: string;
-  accessor: (item: T) => React.ReactNode;
+  header: ReactNode;
+  accessor?: (item: T) => ReactNode;
   className?: string;
 }
 
 interface DataTableProps<T> {
   caption?: string;
-  data: T[];
+  data: NoInfer<T>[];
   columns: Column<T>[];
   footer?: React.ReactNode;
   onRowClick?: (item: T) => void;
+  className?: string;
 }
 
 export const DataTable = <T,>({
@@ -29,14 +32,18 @@ export const DataTable = <T,>({
   columns,
   footer,
   onRowClick,
+  className,
 }: DataTableProps<T>) => {
   return (
-    <Table>
+    <Table className={className}>
       {caption && <TableCaption>{caption}</TableCaption>}
       <TableHeader>
         <TableRow>
           {columns.map((column, index) => (
-            <TableHead key={index} className={column.className}>
+            <TableHead
+              key={index}
+              className={cn("whitespace-pre text-nowrap", column.className)}
+            >
               {column.header}
             </TableHead>
           ))}
@@ -47,15 +54,25 @@ export const DataTable = <T,>({
           <TableRow
             key={rowIndex}
             onClick={onRowClick ? () => onRowClick(item) : undefined}
+            tabIndex={onRowClick ? 0 : undefined}
+            onKeyDown={(e) => {
+              if (e.key === "Enter") {
+                onRowClick?.(item);
+              }
+            }}
             className={
               onRowClick
                 ? "cursor-pointer hover:bg-slate-100 transition-colors"
-                : undefined
+                : "hover:bg-transparent"
             }
           >
             {columns.map((column, colIndex) => (
               <TableCell key={colIndex} className={column.className}>
-                {column.accessor(item)}
+                {column.accessor
+                  ? column.accessor(item)
+                  : typeof item === "string" || typeof item === "number"
+                    ? item
+                    : null}
               </TableCell>
             ))}
           </TableRow>
diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..20cd688d77a1b7be817ddf78608d9ab85da4068b
--- /dev/null
+++ b/frontend/src/components/ui/dialog.tsx
@@ -0,0 +1,120 @@
+import * as React from "react";
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { X } from "lucide-react";
+
+import { cn } from "@/utils/cn";
+
+const Dialog = DialogPrimitive.Root;
+
+const DialogTrigger = DialogPrimitive.Trigger;
+
+const DialogPortal = DialogPrimitive.Portal;
+
+const DialogClose = DialogPrimitive.Close;
+
+const DialogOverlay = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Overlay>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+  <DialogPrimitive.Overlay
+    ref={ref}
+    className={cn(
+      "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+      className,
+    )}
+    {...props}
+  />
+));
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
+
+const DialogContent = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+  <DialogPortal>
+    <DialogOverlay />
+    <DialogPrimitive.Content
+      ref={ref}
+      className={cn(
+        "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
+        className,
+      )}
+      {...props}
+    >
+      {children}
+      <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
+        <X className="h-4 w-4" />
+        <span className="sr-only">Close</span>
+      </DialogPrimitive.Close>
+    </DialogPrimitive.Content>
+  </DialogPortal>
+));
+DialogContent.displayName = DialogPrimitive.Content.displayName;
+
+const DialogHeader = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col space-y-1.5 text-center sm:text-left",
+      className,
+    )}
+    {...props}
+  />
+);
+DialogHeader.displayName = "DialogHeader";
+
+const DialogFooter = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+      className,
+    )}
+    {...props}
+  />
+);
+DialogFooter.displayName = "DialogFooter";
+
+const DialogTitle = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Title>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
+>(({ className, ...props }, ref) => (
+  <DialogPrimitive.Title
+    ref={ref}
+    className={cn(
+      "text-lg font-semibold leading-none tracking-tight",
+      className,
+    )}
+    {...props}
+  />
+));
+DialogTitle.displayName = DialogPrimitive.Title.displayName;
+
+const DialogDescription = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Description>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
+>(({ className, ...props }, ref) => (
+  <DialogPrimitive.Description
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+));
+DialogDescription.displayName = DialogPrimitive.Description.displayName;
+
+export {
+  Dialog,
+  DialogPortal,
+  DialogOverlay,
+  DialogClose,
+  DialogTrigger,
+  DialogContent,
+  DialogHeader,
+  DialogFooter,
+  DialogTitle,
+  DialogDescription,
+};
diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx
index 0858f3f67f64fc34f60220ef70af516a96d85f27..b03234c2d7029dadc16cf059fdd2864e3b7d9105 100644
--- a/frontend/src/components/ui/input.tsx
+++ b/frontend/src/components/ui/input.tsx
@@ -1,6 +1,7 @@
 import * as React from "react";
 
 import { cn } from "@/utils/cn";
+import { Label } from "./label";
 
 export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
 
@@ -27,27 +28,34 @@ const IconInput = React.forwardRef<
     leftIcon?: React.ReactElement;
     rightIcon?: React.ReactElement;
     containerClassName?: string;
+    label?: string;
   }
->(({ leftIcon, rightIcon, className, containerClassName, ...props }, ref) => {
-  return (
-    <div className={cn("relative", containerClassName)}>
-      <Input
-        ref={ref}
-        className={cn(leftIcon && "pl-10", rightIcon && "pr-10", className)}
-        {...props}
-      />
-      {leftIcon && (
-        <div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-foreground *:size-4">
-          {leftIcon}
-        </div>
-      )}
-      {rightIcon && (
-        <div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none text-foreground *:size-4">
-          {rightIcon}
-        </div>
-      )}
-    </div>
-  );
-});
+>(
+  (
+    { leftIcon, rightIcon, className, containerClassName, label, ...props },
+    ref,
+  ) => {
+    return (
+      <div className={cn("relative", containerClassName)}>
+        {label && <Label htmlFor={props.id}>{label}</Label>}
+        <Input
+          ref={ref}
+          className={cn(leftIcon && "pl-10", rightIcon && "pr-10", className)}
+          {...props}
+        />
+        {leftIcon && (
+          <div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-foreground *:size-4">
+            {leftIcon}
+          </div>
+        )}
+        {rightIcon && (
+          <div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none text-foreground *:size-4">
+            {rightIcon}
+          </div>
+        )}
+      </div>
+    );
+  },
+);
 
 export { Input, IconInput };
diff --git a/frontend/src/components/ui/table.tsx b/frontend/src/components/ui/table.tsx
index 192ba951cb0cf1140e03585c9b605d50ccf9eddd..a6d7e1c494f5b00bbfb7070946d9dcea8cab2cad 100644
--- a/frontend/src/components/ui/table.tsx
+++ b/frontend/src/components/ui/table.tsx
@@ -6,7 +6,7 @@ const Table = React.forwardRef<
   HTMLTableElement,
   React.HTMLAttributes<HTMLTableElement>
 >(({ className, ...props }, ref) => (
-  <div className="relative w-full overflow-auto">
+  <div className="relative w-full overflow-auto bg-white border rounded-md">
     <table
       ref={ref}
       className={cn("w-full caption-bottom text-sm", className)}
diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx
index 6fdd237ebe345fe2309346d823af37e46d25a76c..7308f6d4113b575f56f6965b0c299ac0c3b941db 100644
--- a/frontend/src/components/ui/tabs.tsx
+++ b/frontend/src/components/ui/tabs.tsx
@@ -12,7 +12,7 @@ const TabsList = React.forwardRef<
   <TabsPrimitive.List
     ref={ref}
     className={cn(
-      "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
+      "inline-flex h-fit items-center rounded-md bg-muted p-1 text-muted-foreground max-w-full overflow-x-auto",
       className,
     )}
     {...props}
diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts
index e62f66c96aedac7ade45777decf57bd7aeaf5161..9029a3dd7ec867f69046beced212069eb27a7d20 100644
--- a/frontend/src/routeTree.gen.ts
+++ b/frontend/src/routeTree.gen.ts
@@ -14,6 +14,8 @@ import { createFileRoute } from '@tanstack/react-router'
 
 import { Route as rootRoute } from './routes/__root'
 import { Route as BaseIndexImport } from './routes/_base/index'
+import { Route as BaseTasksImport } from './routes/_base/tasks'
+import { Route as BaseVacancyNewImport } from './routes/_base/vacancy/new'
 import { Route as BaseVacancyIdImport } from './routes/_base/vacancy/$id'
 
 // Create Virtual Routes
@@ -46,6 +48,16 @@ const BaseLoginLazyRoute = BaseLoginLazyImport.update({
   getParentRoute: () => BaseLazyRoute,
 } as any).lazy(() => import('./routes/_base/login.lazy').then((d) => d.Route))
 
+const BaseTasksRoute = BaseTasksImport.update({
+  path: '/tasks',
+  getParentRoute: () => BaseLazyRoute,
+} as any)
+
+const BaseVacancyNewRoute = BaseVacancyNewImport.update({
+  path: '/vacancy/new',
+  getParentRoute: () => BaseLazyRoute,
+} as any)
+
 const BaseVacancyIdRoute = BaseVacancyIdImport.update({
   path: '/vacancy/$id',
   getParentRoute: () => BaseLazyRoute,
@@ -62,6 +74,13 @@ declare module '@tanstack/react-router' {
       preLoaderRoute: typeof BaseLazyImport
       parentRoute: typeof rootRoute
     }
+    '/_base/tasks': {
+      id: '/_base/tasks'
+      path: '/tasks'
+      fullPath: '/tasks'
+      preLoaderRoute: typeof BaseTasksImport
+      parentRoute: typeof BaseLazyImport
+    }
     '/_base/login': {
       id: '/_base/login'
       path: '/login'
@@ -90,6 +109,13 @@ declare module '@tanstack/react-router' {
       preLoaderRoute: typeof BaseVacancyIdImport
       parentRoute: typeof BaseLazyImport
     }
+    '/_base/vacancy/new': {
+      id: '/_base/vacancy/new'
+      path: '/vacancy/new'
+      fullPath: '/vacancy/new'
+      preLoaderRoute: typeof BaseVacancyNewImport
+      parentRoute: typeof BaseLazyImport
+    }
   }
 }
 
@@ -97,10 +123,12 @@ declare module '@tanstack/react-router' {
 
 export const routeTree = rootRoute.addChildren({
   BaseLazyRoute: BaseLazyRoute.addChildren({
+    BaseTasksRoute,
     BaseLoginLazyRoute,
     BaseRegisterLazyRoute,
     BaseIndexRoute,
     BaseVacancyIdRoute,
+    BaseVacancyNewRoute,
   }),
 })
 
@@ -118,12 +146,18 @@ export const routeTree = rootRoute.addChildren({
     "/_base": {
       "filePath": "_base.lazy.tsx",
       "children": [
+        "/_base/tasks",
         "/_base/login",
         "/_base/register",
         "/_base/",
-        "/_base/vacancy/$id"
+        "/_base/vacancy/$id",
+        "/_base/vacancy/new"
       ]
     },
+    "/_base/tasks": {
+      "filePath": "_base/tasks.tsx",
+      "parent": "/_base"
+    },
     "/_base/login": {
       "filePath": "_base/login.lazy.tsx",
       "parent": "/_base"
@@ -139,6 +173,10 @@ export const routeTree = rootRoute.addChildren({
     "/_base/vacancy/$id": {
       "filePath": "_base/vacancy/$id.tsx",
       "parent": "/_base"
+    },
+    "/_base/vacancy/new": {
+      "filePath": "_base/vacancy/new.tsx",
+      "parent": "/_base"
     }
   }
 }
diff --git a/frontend/src/routes/_base/index.tsx b/frontend/src/routes/_base/index.tsx
index 12d05bc8e66c77f70b64953f7cceb6c999bde16b..011da71ff4ba0ea048ac33f380d6ab08ca443350 100644
--- a/frontend/src/routes/_base/index.tsx
+++ b/frontend/src/routes/_base/index.tsx
@@ -19,11 +19,11 @@ const columns: Column<VacancyDto.Item>[] = [
   },
   {
     header: "Дата создания",
-    accessor: (item) => new Date(item.deadline).toLocaleDateString(),
+    accessor: (item) => new Date(item.created_at).toLocaleDateString("ru-RU"),
   },
   {
     header: "До дедлайна",
-    accessor: (item) => new Date(item.deadline).toLocaleDateString(),
+    accessor: (item) => new Date(item.deadline).toLocaleDateString("ru-RU"),
   },
 ];
 
diff --git a/frontend/src/routes/_base/tasks.tsx b/frontend/src/routes/_base/tasks.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d3d9bb79391085e2fc30344e3b501f17c7b50a46
--- /dev/null
+++ b/frontend/src/routes/_base/tasks.tsx
@@ -0,0 +1,110 @@
+import { TasksEndpoint } from "@/api/endpoints/tasks.endpoint";
+import { MainLayout } from "@/components/hoc/layouts/main.layout";
+import { RejectCandidate } from "@/components/pages/tasks/reject-candidate";
+import { Button } from "@/components/ui/button";
+import { Column, DataTable } from "@/components/ui/data-table";
+import { checkAuth } from "@/utils/check-grant";
+import { createFileRoute } from "@tanstack/react-router";
+import { CheckIcon, UserXIcon } from "lucide-react";
+import { observer } from "mobx-react-lite";
+import { useCallback, useMemo } from "react";
+
+interface Task {
+  id: number;
+  stage: string;
+  candidate_number: number;
+  vacancy_name: string;
+  deadline: string;
+  resume_link: string;
+}
+
+const Page = observer(() => {
+  const { tasks } = Route.useLoaderData();
+
+  const onReject = useCallback((task: Task) => {
+    console.log(task);
+  }, []);
+
+  const columns = useMemo<Column<Task>[]>(
+    () => [
+      {
+        header: "Этап",
+        accessor: (v) => (
+          <div className="flex items-center text-nowrap gap-1">
+            <button className="border rounded-md size-6 flex items-center justify-center group">
+              <CheckIcon className="size-4 group-hover:opacity-100 opacity-0" />
+            </button>
+            {v.stage}
+          </div>
+        ),
+      },
+      {
+        header: "â„–",
+        accessor: (v) => v.candidate_number,
+      },
+      {
+        header: "Вакансия",
+        accessor: (v) => v.vacancy_name,
+      },
+      {
+        header: "До дедлайна",
+        accessor: (v) => {
+          const deadline = new Date(v.deadline);
+          const now = new Date();
+          const diff = deadline.getTime() - now.getTime();
+          const days = Math.ceil(diff / (1000 * 3600 * 24));
+          return `${days} дн.`;
+        },
+      },
+      {
+        header: "Резюме",
+        accessor: (v) => (
+          <a
+            href={v.resume_link}
+            target="_blank"
+            rel="noreferrer"
+            className="text-blue-500 underline hover:no-underline"
+          >
+            Файл
+          </a>
+        ),
+      },
+      {
+        header: <UserXIcon className="size-5" />,
+        accessor: (v) => <RejectCandidate onReject={() => onReject(v)} />,
+      },
+    ],
+    [onReject],
+  );
+
+  return (
+    <MainLayout
+      header={
+        <div className="space-y-6">
+          <h1 className="text-3xl font-semibold">Мои задачи</h1>
+        </div>
+      }
+    >
+      <DataTable data={tasks} columns={columns} />
+    </MainLayout>
+  );
+});
+
+export const Route = createFileRoute("/_base/tasks")({
+  component: Page,
+  beforeLoad: checkAuth,
+  loader: async () => {
+    // const tasks = await TasksEndpoint.list();
+    const tasks: Task[] = [
+      {
+        id: 1,
+        stage: "HR Скрининг",
+        candidate_number: 1,
+        vacancy_name: "vacancy",
+        deadline: "2024-10-06T11:27:41.106023",
+        resume_link: "https://google.com",
+      },
+    ];
+    return { tasks };
+  },
+});
diff --git a/frontend/src/routes/_base/vacancy/$id.tsx b/frontend/src/routes/_base/vacancy/$id.tsx
index c24715d2b84e82cfdb25883631ee0a73aaf32634..4dbd63d777294ba5d1470bf87082f0055803883d 100644
--- a/frontend/src/routes/_base/vacancy/$id.tsx
+++ b/frontend/src/routes/_base/vacancy/$id.tsx
@@ -1,6 +1,9 @@
 import { VacancyEndpoint } from "@/api/endpoints/vacanvy.endpoint";
 import { mockVacancy } from "@/api/models/vacancy.model";
 import { MainLayout } from "@/components/hoc/layouts/main.layout";
+import { AnalyticsView } from "@/components/pages/vacancy/analytics.view";
+import { CandidatesView } from "@/components/pages/vacancy/candidates.view";
+import { OverviewView } from "@/components/pages/vacancy/overview.view";
 import { Stats } from "@/components/pages/vacancy/stats.view";
 import { Label } from "@/components/ui/label";
 import { Progress } from "@/components/ui/progress";
@@ -17,12 +20,21 @@ import { Priority } from "@/types/priority.type";
 import { checkAuth } from "@/utils/check-grant";
 import { pluralize } from "@/utils/pluralize";
 import { useViewModel } from "@/utils/vm";
-import { createFileRoute, useLoaderData } from "@tanstack/react-router";
-import { observable } from "mobx";
+import {
+  createFileRoute,
+  useLoaderData,
+  useNavigate,
+  useSearch,
+} from "@tanstack/react-router";
 import { observer } from "mobx-react-lite";
+import { useState } from "react";
+import { z } from "zod";
 
 const Page = observer(() => {
   const { vacancy } = Route.useLoaderData();
+  const [tab, setTab] = useState(
+    new URLSearchParams(window.location.search).get("tab") ?? "overview",
+  );
   const vm = useViewModel(VacancyStore, vacancy);
 
   const deadline = new Date(vm.vacancy.vacancy.deadline);
@@ -47,14 +59,14 @@ const Page = observer(() => {
             {vm.vacancy.vacancy.name}
           </h1>
           <p className="text-slate-500">{vm.vacancy.vacancy.area}</p>
-          <div className="flex justify-between items-end">
-            <div className="space-y-2">
+          <div className="flex justify-between items-end gap-2">
+            <div className="space-y-2 basis-[300px]">
               <span className="flex items-center text-slate-800">
                 {deadline.toLocaleDateString("ru-RU")}
-                <div className="rounded-full bg-slate-500 size-2 inline-block mx-2"></div>
+                <div className="rounded-full bg-slate-500 min-w-2 size-2 inline-block mx-2"></div>
                 ещё {daysLeft} {pluralize(daysLeft, ["день", "дня", "дней"])}
               </span>
-              <Progress value={percentage} className="w-[300px] h-2" />
+              <Progress value={percentage} className="basis-[300px] h-2" />
             </div>
             <div>
               <Label htmlFor="priority">Приоритет</Label>
@@ -79,19 +91,25 @@ const Page = observer(() => {
         </div>
       }
     >
-      <Tabs value={vm.tab} onValueChange={(value) => (vm.tab = value)}>
-        <TabsList>
+      <Tabs value={tab} onValueChange={(value) => setTab(value)}>
+        <TabsList className="overflow-x-auto">
           <TabsTrigger value="overview">О вакансии</TabsTrigger>
           <TabsTrigger value="stats">Статистика по вакансии</TabsTrigger>
           <TabsTrigger value="candidates">Кандидаты</TabsTrigger>
           <TabsTrigger value="analytics">Анализ рынка по вакансии</TabsTrigger>
         </TabsList>
-        <TabsContent value="overview">overview</TabsContent>
+        <TabsContent value="overview">
+          <OverviewView vm={vm} />
+        </TabsContent>
         <TabsContent value="stats">
           <Stats vacancy={vm.vacancy} />
         </TabsContent>
-        <TabsContent value="candidates">candidates</TabsContent>
-        <TabsContent value="analytics">sources</TabsContent>
+        <TabsContent value="candidates">
+          <CandidatesView vm={vm} />
+        </TabsContent>
+        <TabsContent value="analytics">
+          <AnalyticsView vm={vm} />
+        </TabsContent>
       </Tabs>
     </MainLayout>
   );
@@ -101,8 +119,7 @@ export const Route = createFileRoute("/_base/vacancy/$id")({
   component: Page,
   beforeLoad: checkAuth,
   loader: async (x) => {
-    // const vacancy = await VacancyEndpoint.getById(x.params.id);
-    const vacancy = mockVacancy;
+    const vacancy = await VacancyEndpoint.getById(x.params.id);
     return { vacancy };
   },
 });
diff --git a/frontend/src/routes/_base/vacancy/new.tsx b/frontend/src/routes/_base/vacancy/new.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..287f406974d16d0dd95a14124dde03e0b1a7f69a
--- /dev/null
+++ b/frontend/src/routes/_base/vacancy/new.tsx
@@ -0,0 +1,238 @@
+import { MainLayout } from "@/components/hoc/layouts/main.layout";
+import { ChartSection } from "@/components/pages/vacancy/chart-section";
+import { IconInput, Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Priority } from "@/types/priority.type";
+import { checkAuth } from "@/utils/check-grant";
+import { createFileRoute, useNavigate } from "@tanstack/react-router";
+import { observer } from "mobx-react-lite";
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select";
+import { NewVacancyStore } from "@/stores/new-vacancy";
+import { useViewModel } from "@/utils/vm";
+import { SkillsDropdown } from "@/components/dropdowns/SkillsDropdown";
+import { Textarea } from "@/components/ui/textarea";
+import DropdownMultiple from "@/components/DropdownMultiple";
+import { StagesForm } from "@/components/pages/vacancy/StagesForm";
+import { Button } from "@/components/ui/button";
+import { toast } from "sonner";
+import AutoFormInput from "@/components/ui/auto-form/fields/input";
+import AutoForm from "@/components/ui/auto-form";
+import { z } from "zod";
+
+const Page = observer(() => {
+  const vm = useViewModel(NewVacancyStore);
+  const navigate = useNavigate();
+
+  return (
+    <MainLayout title="Новая вакансия">
+      <div className="space-y-4">
+        <ChartSection
+          title="Основная информация"
+          collapsible={false}
+          allowOverflow
+        >
+          <div className="flex flex-wrap gap-4">
+            <IconInput
+              id="title"
+              className="w-[300px]"
+              value={vm.name}
+              onChange={(e) => {
+                vm.name = e.target.value;
+              }}
+              label="Название вакансии"
+              placeholder="Фронтенд разработчик"
+            />
+            <div>
+              <Label htmlFor="priority">Приоритет</Label>
+              <Select
+                key={vm.priority}
+                value={vm.priority.toString()}
+                onValueChange={(value) => {
+                  vm.priority = Number(value);
+                }}
+              >
+                <SelectTrigger id="priority" className="w-[180px]">
+                  <SelectValue placeholder="Выберите приоритет" />
+                </SelectTrigger>
+                <SelectContent>
+                  <SelectItem value="1">{Priority.locale[1]}</SelectItem>
+                  <SelectItem value="2">{Priority.locale[2]}</SelectItem>
+                  <SelectItem value="3">{Priority.locale[3]}</SelectItem>
+                </SelectContent>
+              </Select>
+            </div>
+          </div>
+        </ChartSection>
+        <ChartSection
+          title="Основная информация"
+          collapsible={false}
+          allowOverflow
+        >
+          <div className="flex flex-wrap gap-4">
+            <div className="grid grid-cols-[1fr_auto_1fr] w-fit items-center gap-x-1 h-fit">
+              <Label
+                htmlFor="experience_from"
+                className="col-span-3 mt-1.5 mb-1"
+              >
+                Опыт работы, лет
+              </Label>
+              <IconInput
+                id="experience_from"
+                type="number"
+                placeholder="от"
+                value={vm.experienceFrom}
+                onChange={(e) => {
+                  const value = Number(e.target.value);
+                  if (
+                    value > Number(vm.experienceTo || 99) ||
+                    value < 0 ||
+                    value > 99
+                  ) {
+                    return;
+                  }
+                  vm.experienceFrom = e.target.value;
+                }}
+                className="w-24"
+              />
+              <span>–</span>
+              <IconInput
+                id="experience_to"
+                type="number"
+                placeholder="до"
+                value={vm.experienceTo}
+                onChange={(e) => {
+                  const value = Number(e.target.value);
+                  if (value > 99 || value < 0) {
+                    return;
+                  }
+                  vm.experienceTo = e.target.value;
+                }}
+                className="w-24"
+              />
+            </div>
+            <IconInput
+              type="number"
+              label="Кол-во мест"
+              placeholder="1"
+              value={vm.quantity}
+              onChange={(e) => {
+                const value = Number(e.target.value);
+                if (value < 1 || value > 99) {
+                  return;
+                }
+                vm.quantity = value;
+              }}
+              className="w-24"
+            />
+            <IconInput
+              label="Образование"
+              placeholder="Высшее"
+              value={vm.education}
+              onChange={(e) => {
+                vm.education = e.target.value;
+              }}
+              className="w-[400px]"
+            />
+            <div className="w-full flex gap-4 *:w-[250px]">
+              <SkillsDropdown
+                label="Ключевые навыки"
+                value={vm.keySkills}
+                onChange={(value) => {
+                  vm.keySkills = value;
+                }}
+                filter={(value) => !vm.additionalSkills.includes(value)}
+              />
+              <SkillsDropdown
+                label="Дополнительные навыки"
+                value={vm.additionalSkills}
+                filter={(value) => !vm.keySkills.includes(value)}
+                onChange={(value) => {
+                  vm.additionalSkills = value;
+                }}
+              />
+            </div>
+          </div>
+        </ChartSection>
+        <ChartSection
+          title="Подробности о вакансии"
+          collapsible={false}
+          allowOverflow
+        >
+          <div className="grid grid-cols-2 w-full gap-6 gap-x-4">
+            <div>
+              <Label htmlFor="description">Описание</Label>
+              <Textarea
+                id="description"
+                placeholder="О чем вакансия?"
+                value={vm.description}
+                onChange={(e) => {
+                  vm.description = e.target.value;
+                }}
+              />
+            </div>
+            <div className="grid grid-cols-2">
+              <div className="grid grid-cols-[1fr_auto_1fr] w-fit items-center gap-x-1 h-fit">
+                <Label
+                  htmlFor="experience_from"
+                  className="col-span-3 mt-1.5 mb-1"
+                >
+                  Вилка зарплаты, руб
+                </Label>
+                <IconInput
+                  id="salary_low"
+                  type="number"
+                  placeholder="от"
+                  onChange={(e) => {
+                    vm.salaryLow = e.target.value;
+                  }}
+                  className="w-24"
+                />
+                <span>–</span>
+                <IconInput
+                  id="salary_high"
+                  type="number"
+                  placeholder="до"
+                  value={vm.salaryHigh}
+                  onChange={(e) => {
+                    vm.salaryHigh = e.target.value;
+                  }}
+                  className="w-24"
+                />
+              </div>
+            </div>
+            <StagesForm vm={vm} />
+          </div>
+        </ChartSection>
+        <div className="w-full flex justify-end">
+          <Button
+            onClick={() => {
+              if (vm.validate()) {
+                toast.promise(vm.create(navigate), {
+                  loading: "Создание вакансии...",
+                  success: "Вакансия создана",
+                  error: "Ошибка создания вакансии",
+                });
+              }
+            }}
+          >
+            Создать вакансию
+          </Button>
+        </div>
+      </div>
+    </MainLayout>
+  );
+});
+
+export const Route = createFileRoute("/_base/vacancy/new")({
+  component: Page,
+  beforeLoad: checkAuth,
+  loader: async () => {
+    return {};
+  },
+});
diff --git a/frontend/src/stores/new-vacancy.ts b/frontend/src/stores/new-vacancy.ts
new file mode 100644
index 0000000000000000000000000000000000000000..38201b1e6d7801015c0c95d60ff90d89bf98ba7a
--- /dev/null
+++ b/frontend/src/stores/new-vacancy.ts
@@ -0,0 +1,119 @@
+import { VacancyEndpoint } from "@/api/endpoints/vacanvy.endpoint";
+import { SkillDto } from "@/api/models/skill.model";
+import { Priority } from "@/types/priority.type";
+import { DisposableVm } from "@/utils/vm";
+import { NavigateFn } from "@tanstack/react-router";
+import { makeAutoObservable } from "mobx";
+import { toast } from "sonner";
+
+export class StageStore {
+  name = "";
+  sla = 0;
+  readonly id = Math.random();
+
+  constructor() {
+    makeAutoObservable(this);
+  }
+}
+
+export class NewVacancyStore implements DisposableVm {
+  name = "";
+  priority: Priority.Priority = Priority.Priority.LOW;
+  deadline: Date = new Date();
+  profession = "";
+  area = "";
+  supervisor = "";
+  city = "";
+  experienceFrom = "";
+  experienceTo = "";
+  education = "";
+  keySkills: SkillDto.Item[] = [];
+  additionalSkills: SkillDto.Item[] = [];
+  description = "";
+  typeOfEmployment = "";
+  quantity = 1;
+  direction = "";
+  salaryLow = "";
+  salaryHigh = "";
+
+  loading = false;
+
+  stages: StageStore[] = [];
+
+  addStage() {
+    this.stages.push(new StageStore());
+  }
+
+  removeStage(stage: StageStore) {
+    this.stages = this.stages.filter((x) => x !== stage);
+  }
+
+  constructor() {
+    makeAutoObservable(this);
+  }
+
+  validate(): boolean {
+    if (this.name.length < 3) {
+      toast.error("Название вакансии должно быть не менее 3 символов");
+      return false;
+    }
+
+    if (Number(this.salaryLow) > Number(this.salaryHigh)) {
+      toast.error("Минимальная зарплата не может быть больше максимальной");
+      return false;
+    }
+
+    if (this.stages.length < 1) {
+      toast.error("Необходимо добавить хотя бы один этап");
+      return false;
+    }
+
+    if (this.keySkills.length < 1) {
+      toast.error("Необходимо добавить хотя бы один ключевой навык");
+      return false;
+    }
+
+    if (Number(this.experienceFrom) > Number(this.experienceTo)) {
+      toast.error("Неверно указаны годы опыта");
+      return false;
+    }
+
+    return true;
+  }
+
+  async create(navigate: NavigateFn) {
+    const id = await VacancyEndpoint.create({
+      name: this.name,
+      priority: this.priority,
+      deadline: this.deadline.toISOString().split("T")[0] + "T00:00:00",
+      profession: this.profession,
+      area: this.area,
+      supervisor: this.supervisor,
+      city: this.city,
+      experienceFrom: this.experienceFrom,
+      experienceTo: this.experienceTo,
+      education: this.education,
+      keySkills: this.keySkills.map((x) => x.id),
+      additionalSkills: this.additionalSkills.map((x) => x.id),
+      description: this.description,
+      typeOfEmployment: this.typeOfEmployment,
+      quantity: this.quantity,
+      direction: this.direction,
+      salary_low: Number(this.salaryLow),
+      salary_high: Number(this.salaryHigh),
+      stages: this.stages.map((x, i) => ({
+        order: i + 1,
+        name: x.name,
+        duration: x.sla,
+      })),
+    });
+
+    navigate({ to: "/vacancy/$id", params: { id: id.toString() } });
+
+    return id;
+  }
+
+  dispose(): void {
+    return;
+  }
+}
diff --git a/frontend/src/stores/vanacy.store.ts b/frontend/src/stores/vanacy.store.ts
index 487bea1e90d1db08b85b402931c01dfe942dcb5f..4b2de9962d3e41095c36f210f3f04709a65758fa 100644
--- a/frontend/src/stores/vanacy.store.ts
+++ b/frontend/src/stores/vanacy.store.ts
@@ -1,13 +1,33 @@
+import { CandidatesEndpoint } from "@/api/endpoints/candidates.endpoint";
+import { CandidatesDto } from "@/api/models/candidates.model";
 import { mockVacancy, VacancyDto } from "@/api/models/vacancy.model";
 import { DisposableVm } from "@/utils/vm";
 import { makeAutoObservable, observable } from "mobx";
 
 export class VacancyStore implements DisposableVm {
-  tab = "overview";
+  activeCandidates: CandidatesDto.ActiveCandidate[] = [];
+  potentialCandidates: CandidatesDto.PotentialCandidate[] = [];
+  declinedCandidates: CandidatesDto.DeclinedCandidate[] = [];
 
   constructor(public readonly vacancy: VacancyDto.DetailedItem) {
     makeAutoObservable(this);
   }
 
-  dispose(): void {}
+  async loadCandidates() {
+    if (this.activeCandidates.length) return;
+
+    const [active, potential, declined] = await Promise.all([
+      CandidatesEndpoint.getActiveCandidates(this.vacancy.vacancy.id),
+      CandidatesEndpoint.getPotentialCandidates(this.vacancy.vacancy.id),
+      CandidatesEndpoint.getDeclinedCandidates(this.vacancy.vacancy.id),
+    ]);
+
+    this.activeCandidates = active.candidates;
+    this.potentialCandidates = potential.candidates;
+    this.declinedCandidates = declined.candidates;
+  }
+
+  dispose(): void {
+    return;
+  }
 }
diff --git a/frontend/src/utils/check-grant.ts b/frontend/src/utils/check-grant.ts
index cfa6022c0fe447f6dcad260ec18327424e8fd322..4eac81a266639f18be34180723410452adfea583 100644
--- a/frontend/src/utils/check-grant.ts
+++ b/frontend/src/utils/check-grant.ts
@@ -2,7 +2,6 @@ import { AuthService } from "@/stores/auth.service";
 import { redirect } from "@tanstack/react-router";
 
 export const checkAuth = () => {
-  console.log("check");
   if (AuthService.auth.state === "authenticated") {
     return;
   }
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 24080de3dfa5871acef7f63964b122172ce7f25a..3dd1ad8218b41ec11e10a41bcef07b2b88e6b24d 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -21,25 +21,11 @@ export default defineConfig({
     }),
     VitePWA({
       registerType: "autoUpdate",
-      workbox: {
-        runtimeCaching: [
-          {
-            urlPattern: /^https:\/\/api\.lct\.larek\.tech\/consumers\/q$/,
-            handler: "CacheFirst",
-            options: {
-              cacheName: "api-cache",
-              expiration: {
-                maxEntries: 1, // Keep only one entry
-              },
-              cacheableResponse: {
-                statuses: [0, 200], // Cache successful responses and opaque responses
-              },
-            },
-          },
-        ],
+      manifest: {
+        theme_color: "#015AAE",
+        name: "Дашборд рекуртера",
       },
     }),
-    // basicSsl()
   ],
   build: {
     target: "esnext",