From 26301d1a13c505d21f1cdff50fcc1772a7c82b76 Mon Sep 17 00:00:00 2001 From: Andre Henriques Date: Thu, 21 Nov 2024 12:19:43 +0000 Subject: [PATCH] lot of changes --- api/application.properties | 1 + .../applications/ApplicationsController.kt | 27 +- .../applications/UserStatusNode.kt | 301 ++++++++++ api/src/main/resources/schema.sql | 44 +- package.json | 5 + pnpm-lock.yaml | 22 + site/package.json | 6 +- site/pnpm-lock.yaml | 69 ++- site/src/app.html | 5 - site/src/lib/ApplicationsStore.svelte.ts | 13 +- site/src/routes/+layout.svelte | 1 + site/src/routes/+page.svelte | 9 + site/src/routes/ApplicationsList.svelte | 33 +- site/src/routes/AppliyedList.svelte | 18 +- site/src/routes/NavBar.svelte | 5 +- site/src/routes/PApplicationList.svelte | 6 - site/src/routes/cv/+page.svelte | 22 +- site/src/routes/flow/+page.svelte | 513 ++++++++++++++++++ site/src/routes/flow/IconPicker.svelte | 33 ++ site/src/routes/flow/Link.svelte | 78 +++ site/src/routes/flow/NodeRect.svelte | 162 ++++++ site/src/routes/flow/Rect.svelte | 41 ++ site/src/routes/flow/generate-list-json.ts | 5 + site/src/routes/flow/icons-list.json | 1 + site/src/routes/flow/types.ts | 60 ++ site/src/routes/graphs/+page.svelte | 55 +- site/src/routes/work-area/CompanyField.svelte | 28 + site/src/routes/work-area/DropingZone.svelte | 198 +++++++ .../routes/work-area/LinkApplication.svelte | 11 +- .../routes/work-area/SearchApplication.svelte | 37 +- site/src/routes/work-area/Timeline.svelte | 11 +- site/src/routes/work-area/WorkArea.svelte | 231 ++------ 32 files changed, 1740 insertions(+), 311 deletions(-) create mode 100644 api/src/main/kotlin/com/andr3h3nriqu3s/applications/UserStatusNode.kt create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 site/src/routes/flow/+page.svelte create mode 100644 site/src/routes/flow/IconPicker.svelte create mode 100644 site/src/routes/flow/Link.svelte create mode 100644 site/src/routes/flow/NodeRect.svelte create mode 100644 site/src/routes/flow/Rect.svelte create mode 100644 site/src/routes/flow/generate-list-json.ts create mode 100644 site/src/routes/flow/icons-list.json create mode 100644 site/src/routes/flow/types.ts create mode 100644 site/src/routes/work-area/CompanyField.svelte create mode 100644 site/src/routes/work-area/DropingZone.svelte diff --git a/api/application.properties b/api/application.properties index 67b7fe8..99d3969 100644 --- a/api/application.properties +++ b/api/application.properties @@ -2,6 +2,7 @@ spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://kronos.home:5432/applications spring.datasource.username=applications spring.datasource.password=applications +spring-boot.run.jvmArguments=-Duser.timezone=UTC spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect # Disable the trace on the error responses diff --git a/api/src/main/kotlin/com/andr3h3nriqu3s/applications/ApplicationsController.kt b/api/src/main/kotlin/com/andr3h3nriqu3s/applications/ApplicationsController.kt index d7d8a8e..a71fdf7 100644 --- a/api/src/main/kotlin/com/andr3h3nriqu3s/applications/ApplicationsController.kt +++ b/api/src/main/kotlin/com/andr3h3nriqu3s/applications/ApplicationsController.kt @@ -33,8 +33,10 @@ data class Application( var extra_data: String, var payrange: String, var status: Int, + var status_id: String?, var company: String, var recruiter: String, + var agency: Boolean, var message: String, var linked_application: String, var status_history: String, @@ -56,8 +58,10 @@ data class Application( rs.getString("extra_data"), rs.getString("payrange"), rs.getInt("status"), + rs.getString("status_id"), rs.getString("company"), rs.getString("recruiter"), + rs.getBoolean("agency"), rs.getString("message"), rs.getString("linked_application"), rs.getString("status_history"), @@ -85,6 +89,7 @@ data class CVData( val company: String, val recruiter: String, val message: String, + val agency: Boolean, val flairs: List ) @@ -113,7 +118,7 @@ class ApplicationsController( val flairs = application.flairs.map { it.toFlairSimple() } - return CVData(application.company, application.recruiter, application.message, flairs) + return CVData(application.company, application.recruiter, application.message, application.agency, flairs) } /** Create a new application from the link */ @@ -135,8 +140,10 @@ class ApplicationsController( "", "", 0, + null, "", "", + false, "", "", "", @@ -263,8 +270,10 @@ class ApplicationsController( "", "", 0, + null, "", "", + false, "", "", "", @@ -535,8 +544,9 @@ class ApplicationService( } // Create time is auto created by the database + // The default status is null db.update( - "insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status, company, recruiter, message, linked_application, status_history, application_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", + "insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status, company, recruiter, message, linked_application, status_history, application_time, agency) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", application.id, application.url, application.original_url, @@ -552,6 +562,7 @@ class ApplicationService( application.linked_application, application.status_history, application.application_time, + application.agency, ) eventService.create(application.id, EventType.Creation) @@ -595,7 +606,16 @@ class ApplicationService( return iter.toList() } + public fun findAllByUserStatusId(statusId: String, user: UserDb): List { + return db.query( + "select * from applications where status_id=? and user_id=? order by title asc;", + arrayOf(statusId, user.id), + Application + ) + } + // Update the stauts on the application object before giving it to this function + // TODO how status history works public fun updateStatus(application: Application): Application { val status_string = "${application.status}" @@ -627,7 +647,7 @@ class ApplicationService( public fun update(application: Application): Application { // I don't want ot update create_time db.update( - "update applications set url=?, original_url=?, unique_url=?, title=?, user_id=?, extra_data=?, payrange=?, company=?, recruiter=?, message=?, linked_application=? where id=?", + "update applications set url=?, original_url=?, unique_url=?, title=?, user_id=?, extra_data=?, payrange=?, company=?, recruiter=?, message=?, linked_application=?, agency=? where id=?", application.url, application.original_url, application.unique_url, @@ -639,6 +659,7 @@ class ApplicationService( application.recruiter, application.message, application.linked_application, + application.agency, application.id, ) return application diff --git a/api/src/main/kotlin/com/andr3h3nriqu3s/applications/UserStatusNode.kt b/api/src/main/kotlin/com/andr3h3nriqu3s/applications/UserStatusNode.kt new file mode 100644 index 0000000..6590303 --- /dev/null +++ b/api/src/main/kotlin/com/andr3h3nriqu3s/applications/UserStatusNode.kt @@ -0,0 +1,301 @@ +package com.andr3h3nriqu3s.applications + +import java.sql.ResultSet +import java.util.UUID +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.jdbc.core.JdbcTemplate +import org.springframework.jdbc.core.RowMapper +import org.springframework.stereotype.Service +import org.springframework.web.bind.annotation.ControllerAdvice +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestHeader +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.server.ResponseStatusException + +data class UserStatusNode( + var id: String, + var user_id: String, + var name: String, + var icon: String, + var x: Int, + var y: Int, + var width: Int, + var height: Int, + var permission: Int +) { + companion object : RowMapper { + override public fun mapRow(rs: ResultSet, rowNum: Int): UserStatusNode { + return UserStatusNode( + rs.getString("id"), + rs.getString("user_id"), + rs.getString("name"), + rs.getString("icon"), + rs.getInt("x"), + rs.getInt("y"), + rs.getInt("width"), + rs.getInt("height"), + rs.getInt("permission"), + ) + } + } +} + +data class UserStatusLink( + var id: String, + var user_id: String, + var source_node: String?, + var target_node: String?, + var bi: Boolean, + var source_x: Int, + var source_y: Int, + var target_x: Int, + var target_y: Int, +) { + companion object : RowMapper { + override public fun mapRow(rs: ResultSet, rowNum: Int): UserStatusLink { + return UserStatusLink( + rs.getString("id"), + rs.getString("user_id"), + rs.getString("source_node"), + rs.getString("target_node"), + rs.getBoolean("bi"), + rs.getInt("source_x"), + rs.getInt("source_y"), + rs.getInt("target_x"), + rs.getInt("target_y"), + ) + } + } +} + +@RestController +@ControllerAdvice +@RequestMapping("/api/user/status") +class UserApplicationStatusController( + val sessionService: SessionService, + val userStatusNodeService: UserStatusNodeService, + val userStatusLinkService: UserStatusLinkService, + val applicationService: ApplicationService, +) { + + // + // Nodes + // + @GetMapping(path = ["/node"], produces = [MediaType.APPLICATION_JSON_VALUE]) + public fun listAll(@RequestHeader("token") token: String): List { + val user = sessionService.verifyTokenThrow(token) + + return userStatusNodeService.findAllByUserId(user.id) + } + + @PostMapping(path = ["/node"], produces = [MediaType.APPLICATION_JSON_VALUE]) + public fun create( + @RequestBody node: UserStatusNode, + @RequestHeader("token") token: String + ): UserStatusNode { + val user = sessionService.verifyTokenThrow(token) + node.user_id = user.id + + return userStatusNodeService.create(node) + } + + @PutMapping(path = ["/node"], produces = [MediaType.APPLICATION_JSON_VALUE]) + public fun put( + @RequestBody node: UserStatusNode, + @RequestHeader("token") token: String + ): UserStatusNode? { + val user = sessionService.verifyTokenThrow(token) + + if (userStatusNodeService.findById(node.id, user) == null) { + throw NotFound() + } + + return userStatusNodeService.update(node) + } + + @DeleteMapping(path = ["/node/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE]) + public fun delete(@PathVariable id: String, @RequestHeader("token") token: String): Boolean { + val user = sessionService.verifyTokenThrow(token) + + val node = userStatusNodeService.findById(id, user) + + if (node == null) { + throw ResponseStatusException(HttpStatus.NOT_FOUND, "Could not find a node") + } + + val applications = applicationService.findAllByUserStatusId(id, user) + + if (applications.size > 0) { + throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Applications exist with this user status!") + } + + userStatusLinkService.deleteByNode(user, node) + userStatusNodeService.delete(node, user) + + return true + } + + // + // Link + // + @GetMapping(path = ["/link"], produces = [MediaType.APPLICATION_JSON_VALUE]) + public fun listAllLinks(@RequestHeader("token") token: String): List { + val user = sessionService.verifyTokenThrow(token) + return userStatusLinkService.findAllByUserId(user) + } + + @PutMapping(path = ["/link"], produces = [MediaType.APPLICATION_JSON_VALUE]) + public fun createLink( + @RequestBody link: UserStatusLink, + @RequestHeader("token") token: String + ): UserStatusLink { + val user = sessionService.verifyTokenThrow(token) + + if (link.source_node == link.target_node) { + throw ResponseStatusException( + HttpStatus.BAD_REQUEST, + "Request Node and Target Not can not be the same" + ) + } + + if (link.source_node != null && + userStatusNodeService.findById(link.source_node!!, user) == null + ) { + throw ResponseStatusException(HttpStatus.NOT_FOUND, "Request source not found") + } + + if (link.target_node != null && + userStatusNodeService.findById(link.target_node!!, user) == null + ) { + throw ResponseStatusException(HttpStatus.NOT_FOUND, "Request target not found") + } + + link.user_id = user.id + + return userStatusLinkService.create(link) + } +} + +@Service +public class UserStatusNodeService(val db: JdbcTemplate) { + + public fun findById(id: String, user: UserDb): UserStatusNode? { + var nodes = + db.query( + "select * from user_status_node where id=? and user_id=?", + arrayOf(id, user.id), + UserStatusNode + ) + if (nodes.size == 0) { + return null + } + + return nodes[0] + } + + public fun findAllByUserId(user_id: String): List { + return db.query( + "select * from user_status_node where user_id=?", + arrayOf(user_id), + UserStatusNode + ) + .toList() + } + + public fun update(node: UserStatusNode): UserStatusNode { + db.update( + "update user_status_node set name=?, icon=?, x=?, y=?, width=?, height=?, permission=? where id=?;", + node.name, + node.icon, + node.x, + node.y, + node.width, + node.height, + node.permission, + node.id, + ) + + return node + } + + public fun create(node: UserStatusNode): UserStatusNode { + val id = UUID.randomUUID().toString() + node.id = id + + db.update( + "insert into user_status_node (id, user_id, name, icon, x, y, width, height, permission) values (?, ?, ?, ?, ?, ?, ?, ?, ?);", + node.id, + node.user_id, + node.name, + node.icon, + node.x, + node.y, + node.width, + node.height, + node.permission + ) + + return node + } + + public fun delete(node: UserStatusNode, user: UserDb) { + db.update("delete from user_status_node where id=? and user_id=?;", node.id, user.id) + } +} + +@Service +public class UserStatusLinkService(val db: JdbcTemplate) { + + public fun findById(id: String, user: UserDb): UserStatusLink? { + val links = + db.query( + "select * from user_status_link where user_id=? and id=?;", + arrayOf(id, user.id), + UserStatusLink + ) + if (links.size == 0) { + return null + } + return links[0] + } + + public fun findAllByUserId(user: UserDb): List { + return db.query( + "select * from user_status_link where user_id=?", + arrayOf(user.id), + UserStatusLink + ) + .toList() + } + + public fun deleteByNode(user: UserDb, node: UserStatusNode) { + db.update("delete from user_status_link where (target_node=? or source_node=?) and user_id=?;", node.id, node.id, user.id) + } + + public fun create(link: UserStatusLink): UserStatusLink { + val id = UUID.randomUUID().toString() + link.id = id + + db.update( + "insert into user_status_link (id, user_id, source_node, target_node, bi, source_x, source_y, target_x, target_y) values (?, ?, ?, ?, ?, ?, ?, ?, ?);", + link.id, + link.user_id, + link.source_node, + link.target_node, + link.bi, + link.source_x, + link.source_y, + link.target_x, + link.target_y, + ) + + return link + } +} diff --git a/api/src/main/resources/schema.sql b/api/src/main/resources/schema.sql index e9b794e..b712a14 100644 --- a/api/src/main/resources/schema.sql +++ b/api/src/main/resources/schema.sql @@ -23,16 +23,20 @@ create table if not exists applications ( status_history text default '', user_id text, extra_data text, + -- this status will be deprecated in favor of the node style status status integer, + status_id text default null, linked_application text default '', application_time text default '', + agency boolean default false, create_time timestamp default now() ); +-- Views are deprecated will be removed in the future create table if not exists views ( id text primary key, application_id text not null, - time timestamp default current_timestamp + time timestamp default now() ); create table if not exists flair ( @@ -65,5 +69,41 @@ create table if not exists events ( -- This only matters when event_type == 1 new_status integer, - time timestamp default current_timestamp + time timestamp default now() +); + +-- +-- User Controlled Status +-- + +create table if not exists user_status_node ( + id text primary key, + user_id text not null, + name text not null, + icon text not null, + x integer default 0, + y integer default 0, + width integer default 0, + height integer default 0, + permission integer default 0 +); + +create table if not exists user_status_link ( + id text primary key, + -- You technically can get this by loking a the source and target nodes but that seams more complicated + user_id text not null, + + -- This can be null because null means creation + source_node text, + -- This can be null because null means creation + target_node text, + + source_x integer default 0, + source_y integer default 0, + + target_x integer default 0, + target_y integer default 0, + + -- If this link is bidiretoral + bi boolean ); diff --git a/package.json b/package.json new file mode 100644 index 0000000..f122d91 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "bootstrap-icons": "^1.11.3" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..df8142d --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,22 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + bootstrap-icons: + specifier: ^1.11.3 + version: 1.11.3 + +packages: + + bootstrap-icons@1.11.3: + resolution: {integrity: sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==} + +snapshots: + + bootstrap-icons@1.11.3: {} diff --git a/site/package.json b/site/package.json index 4107053..b774bae 100644 --- a/site/package.json +++ b/site/package.json @@ -18,6 +18,7 @@ "@sveltejs/vite-plugin-svelte": "^3.0.0", "@types/d3": "^7.4.3", "@types/eslint": "^8.56.7", + "@types/node": "^22.8.7", "autoprefixer": "^10.4.19", "d3": "^7.9.0", "eslint": "^9.0.0", @@ -35,5 +36,8 @@ "typescript-eslint": "^8.0.0-alpha.20", "vite": "^5.0.3" }, - "type": "module" + "type": "module", + "dependencies": { + "bootstrap-icons": "1.11.3" + } } diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index f5ff75d..4cfb06c 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -7,25 +7,32 @@ settings: importers: .: + dependencies: + bootstrap-icons: + specifier: 1.11.3 + version: 1.11.3 devDependencies: '@sveltejs/adapter-auto': specifier: ^3.0.0 - version: 3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3))(svelte@5.0.0-next.174)(vite@5.3.3)) + version: 3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7))) '@sveltejs/adapter-static': specifier: ^3.0.5 - version: 3.0.5(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3))(svelte@5.0.0-next.174)(vite@5.3.3)) + version: 3.0.5(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7))) '@sveltejs/kit': specifier: ^2.0.0 - version: 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3))(svelte@5.0.0-next.174)(vite@5.3.3) + version: 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)) '@sveltejs/vite-plugin-svelte': specifier: ^3.0.0 - version: 3.1.1(svelte@5.0.0-next.174)(vite@5.3.3) + version: 3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)) '@types/d3': specifier: ^7.4.3 version: 7.4.3 '@types/eslint': specifier: ^8.56.7 version: 8.56.10 + '@types/node': + specifier: ^22.8.7 + version: 22.8.7 autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.39) @@ -73,7 +80,7 @@ importers: version: 8.0.0-alpha.39(eslint@9.6.0)(typescript@5.5.3) vite: specifier: ^5.0.3 - version: 5.3.3 + version: 5.3.3(@types/node@22.8.7) packages: @@ -520,6 +527,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@22.8.7': + resolution: {integrity: sha512-LidcG+2UeYIWcMuMUpBKOnryBWG/rnmOHQR5apjn8myTQcx3rinFRn7DcIFhMnS0PPFSC6OafdIKEad0lj6U0Q==} + '@types/pug@2.0.10': resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} @@ -651,6 +661,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bootstrap-icons@1.11.3: + resolution: {integrity: sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1674,6 +1687,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + update-browserslist-db@1.1.0: resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} hasBin: true @@ -1964,18 +1980,18 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.18.0': optional: true - '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3))(svelte@5.0.0-next.174)(vite@5.3.3))': + '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))': dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3))(svelte@5.0.0-next.174)(vite@5.3.3) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-static@3.0.5(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3))(svelte@5.0.0-next.174)(vite@5.3.3))': + '@sveltejs/adapter-static@3.0.5(@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))': dependencies: - '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3))(svelte@5.0.0-next.174)(vite@5.3.3) + '@sveltejs/kit': 2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)) - '@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3))(svelte@5.0.0-next.174)(vite@5.3.3)': + '@sveltejs/kit@2.5.18(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.174)(vite@5.3.3) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -1989,28 +2005,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.174 tiny-glob: 0.2.9 - vite: 5.3.3 + vite: 5.3.3(@types/node@22.8.7) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3))(svelte@5.0.0-next.174)(vite@5.3.3)': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.174)(vite@5.3.3) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)) debug: 4.3.5 svelte: 5.0.0-next.174 - vite: 5.3.3 + vite: 5.3.3(@types/node@22.8.7) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3)': + '@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3))(svelte@5.0.0-next.174)(vite@5.3.3) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)))(svelte@5.0.0-next.174)(vite@5.3.3(@types/node@22.8.7)) debug: 4.3.5 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.10 svelte: 5.0.0-next.174 svelte-hmr: 0.16.0(svelte@5.0.0-next.174) - vite: 5.3.3 - vitefu: 0.2.5(vite@5.3.3) + vite: 5.3.3(@types/node@22.8.7) + vitefu: 0.2.5(vite@5.3.3(@types/node@22.8.7)) transitivePeerDependencies: - supports-color @@ -2144,6 +2160,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/node@22.8.7': + dependencies: + undici-types: 6.19.8 + '@types/pug@2.0.10': {} '@typescript-eslint/eslint-plugin@8.0.0-alpha.39(@typescript-eslint/parser@8.0.0-alpha.39(eslint@9.6.0)(typescript@5.5.3))(eslint@9.6.0)(typescript@5.5.3)': @@ -2289,6 +2309,8 @@ snapshots: binary-extensions@2.3.0: {} + bootstrap-icons@1.11.3: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -3330,6 +3352,8 @@ snapshots: typescript@5.5.3: {} + undici-types@6.19.8: {} + update-browserslist-db@1.1.0(browserslist@4.23.1): dependencies: browserslist: 4.23.1 @@ -3342,17 +3366,18 @@ snapshots: util-deprecate@1.0.2: {} - vite@5.3.3: + vite@5.3.3(@types/node@22.8.7): dependencies: esbuild: 0.21.5 postcss: 8.4.39 rollup: 4.18.0 optionalDependencies: + '@types/node': 22.8.7 fsevents: 2.3.3 - vitefu@0.2.5(vite@5.3.3): + vitefu@0.2.5(vite@5.3.3(@types/node@22.8.7)): optionalDependencies: - vite: 5.3.3 + vite: 5.3.3(@types/node@22.8.7) which@2.0.2: dependencies: diff --git a/site/src/app.html b/site/src/app.html index aa5340e..684fe65 100644 --- a/site/src/app.html +++ b/site/src/app.html @@ -5,11 +5,6 @@ - - %sveltekit.head% diff --git a/site/src/lib/ApplicationsStore.svelte.ts b/site/src/lib/ApplicationsStore.svelte.ts index e5124d8..7c33ed0 100644 --- a/site/src/lib/ApplicationsStore.svelte.ts +++ b/site/src/lib/ApplicationsStore.svelte.ts @@ -11,9 +11,11 @@ export const ApplicationStatus = Object.freeze({ Applyed: 4, Expired: 5, TasksToDo: 6, + TasksToDo2: 10, LinkedApplication: 7, InterviewStep1: 8, - InterviewStep2: 9 + InterviewStep2: 9, + FinalInterview: 11 }); export const ApplicationStatusIconMaping: Record, string> = Object.freeze({ @@ -24,9 +26,11 @@ export const ApplicationStatusIconMaping: Record, string> = Object.freeze({ @@ -37,9 +41,11 @@ export const ApplicationStatusMaping: Record, s 4: 'Applyed', 5: 'Expired', 6: 'Tasks To Do', + 10: 'Tasks To Do 2', 7: 'Linked Application', 8: 'Interview 1', - 9: 'Interview 2' + 9: 'Interview 2', + 11: 'Final Interview' }); export type View = { @@ -73,6 +79,7 @@ export type Application = { payrange: string; status: AsEnum; recruiter: string; + agency: boolean; company: string; message: string; linked_application: string; diff --git a/site/src/routes/+layout.svelte b/site/src/routes/+layout.svelte index 2e511e0..873e030 100644 --- a/site/src/routes/+layout.svelte +++ b/site/src/routes/+layout.svelte @@ -1,5 +1,6 @@ diff --git a/site/src/routes/+page.svelte b/site/src/routes/+page.svelte index 3858c71..230b803 100644 --- a/site/src/routes/+page.svelte +++ b/site/src/routes/+page.svelte @@ -16,9 +16,18 @@ + + Interview Final + + + Interview II + Interview I + + Tasks To do 2 + Tasks To do diff --git a/site/src/routes/ApplicationsList.svelte b/site/src/routes/ApplicationsList.svelte index bafb2e5..c6435ff 100644 --- a/site/src/routes/ApplicationsList.svelte +++ b/site/src/routes/ApplicationsList.svelte @@ -7,18 +7,9 @@ onMount(() => { applicationStore.loadApplications(); }); - -
-

To Apply

-
- -
- {applicationStore.applications.length} -
-
-
- {#each applicationStore.applications.filter((i) => { + let internal = $derived( + applicationStore.applications.filter((i) => { if (!filter) { return true; } @@ -31,7 +22,20 @@ } return x.match(f); - }) as item} + }) + ); + + +
+

To Apply

+
+ +
+ {internal.length} +
+
+
+ {#each internal as item}
-
+

diff --git a/site/src/routes/AppliyedList.svelte b/site/src/routes/AppliyedList.svelte index bceebe9..7a1bfc1 100644 --- a/site/src/routes/AppliyedList.svelte +++ b/site/src/routes/AppliyedList.svelte @@ -10,11 +10,11 @@ let filter = $state(''); -
+

Applied

-
+
{#each applicationStore.applyed.filter((i) => { if (!filter) { return true; @@ -30,10 +30,10 @@ return x.match(f); }) as item}
{/if}

- - {item.url} -
{/each}
+
+
diff --git a/site/src/routes/NavBar.svelte b/site/src/routes/NavBar.svelte index 5712cc6..3100614 100644 --- a/site/src/routes/NavBar.svelte +++ b/site/src/routes/NavBar.svelte @@ -3,7 +3,7 @@ import { userStore } from '$lib/UserStore.svelte'; -
+
+
{userStore.user.username} diff --git a/site/src/routes/PApplicationList.svelte b/site/src/routes/PApplicationList.svelte index 9aa0a48..2438ef4 100644 --- a/site/src/routes/PApplicationList.svelte +++ b/site/src/routes/PApplicationList.svelte @@ -44,12 +44,6 @@
{/if} - - {item.url} -
{/each} diff --git a/site/src/routes/cv/+page.svelte b/site/src/routes/cv/+page.svelte index 6254215..825d408 100644 --- a/site/src/routes/cv/+page.svelte +++ b/site/src/routes/cv/+page.svelte @@ -2,6 +2,7 @@ import { userStore } from '$lib/UserStore.svelte'; import { get } from '$lib/utils'; import { onMount } from 'svelte'; + import ApplicationsList from '../ApplicationsList.svelte'; let id: string | undefined | null = $state(undefined); @@ -23,6 +24,7 @@ recruiter: string; message: string; company: string; + agency: boolean, flairs: SimpleFlair[]; }; @@ -127,15 +129,17 @@
{#if application} -

- 👋 Hello - {#if application.recruiter} - {application.recruiter} @ - {application.company} - {:else if application.company} - recruiter @ {application.company} - {/if} -

+ {#if !application.agency} +

+ 👋 Hello + {#if application.recruiter} + {application.recruiter} @ + {application.company} + {:else if application.company} + recruiter @ {application.company} + {/if} +

+ {/if} {#if application.message}
diff --git a/site/src/routes/flow/+page.svelte b/site/src/routes/flow/+page.svelte new file mode 100644 index 0000000..6da4ad7 --- /dev/null +++ b/site/src/routes/flow/+page.svelte @@ -0,0 +1,513 @@ + + +
+ +
+
+ + +
+ +
+ + +
+ + {#if x !== 0 || y !== 0} + + {/if} + + + +
+ + + + + {#if linkSource} + + {/if} + + + {#each links as _, i} + {@const sourceN = links[i].sourceNode} + {@const targetN = links[i].targetNode} + + {/each} + + + + + {#if mouseAction === 'create'} + + {#if Math.abs(startMX - curPosX) / grid_size <= 8 || Math.abs(startMY - curPosY) / grid_size <= 4} +
+ {:else} +
+ {/if} +
+ {/if} + + + {#each nodes as _, i} + { + try { + await deleteR(`user/status/node/${nodes[i].id}`); + + links = links.filter(link => link.sourceNode.node.id !== nodes[i].id && link.targetNode.node.id !== nodes[i].id) + + nodes.splice(i, 1); + nodes = nodes; + } catch (e) { + console.log("TODO inform the user", e) + } + }} + onNodePointClick={async (inNodeX, inNodeY) => { + if (mouseAction === undefined) { + linkSource = { + node: nodes[i], + x: inNodeX, + y: inNodeY + }; + mouseAction = 'link'; + } else if (mouseAction === 'link' && linkSource) { + if (nodes[i] === linkSource.node) { + linkSource = undefined; + return; + } + + try { + await put('user/status/link', { + id: '', + user_id: '', + source_node: linkSource.node.id, + target_node: nodes[i].id, + bi: false, + source_x: linkSource.x, + source_y: linkSource.y, + + target_x: inNodeX, + target_y: inNodeY + } as FullLinkApi); + + // if mouse action is link then we can assume that linkSource is set + links.push({ + sourceNode: { ...linkSource }, + targetNode: { + node: nodes[i], + x: inNodeX, + y: inNodeY + }, + bi: false + }); + // Tell svelte that the links changed + links = links; + linkSource = undefined; + mouseAction = undefined; + } catch (e) { + console.log('inform the user', e); + } + } + }} + /> + {/each} +
+
+
+ + diff --git a/site/src/routes/flow/IconPicker.svelte b/site/src/routes/flow/IconPicker.svelte new file mode 100644 index 0000000..58fdc03 --- /dev/null +++ b/site/src/routes/flow/IconPicker.svelte @@ -0,0 +1,33 @@ + + + +
+
+ +
+
+
+ {#each filter ? icon_list.filter((a) => { + return a.match(new RegExp(filter, 'ig')); + }) : icon_list as icon} + + {/each} +
+
+
+
+
diff --git a/site/src/routes/flow/Link.svelte b/site/src/routes/flow/Link.svelte new file mode 100644 index 0000000..8271a61 --- /dev/null +++ b/site/src/routes/flow/Link.svelte @@ -0,0 +1,78 @@ + + + + + + + + + diff --git a/site/src/routes/flow/NodeRect.svelte b/site/src/routes/flow/NodeRect.svelte new file mode 100644 index 0000000..aa514ea --- /dev/null +++ b/site/src/routes/flow/NodeRect.svelte @@ -0,0 +1,162 @@ + + + +
{ + if (mouseAction === undefined || mouseAction === 'link') { + hover = true; + } + }} + onmouseleave={() => { + if (hover) { + hover = false; + } + }} + > + +
+ {#if node.permission === 0} + + {:else} + {node.name} + {/if} +
+ {#if node.permission === 0 && !linkMode} + + {/if} +
+ + + {#if hover && linkMode} + + {#each { length: node.width } as _, i} + {@const adjust = grid_size / 2 + grid_size * i - connectionPointSize / 2} + + {/each} + + + {#each { length: node.width } as _, i} + {@const adjust = grid_size / 2 + grid_size * i - connectionPointSize / 2} + + {/each} + + + {#each { length: node.height } as _, i} + {@const adjust = grid_size / 2 + grid_size * i - connectionPointSize / 2} + + {/each} + + + {#each { length: node.height } as _, i} + {@const adjust = grid_size / 2 + grid_size * i - connectionPointSize / 2} + + {/each} + {/if} +
+ + { + node.icon = text; + window.requestAnimationFrame(() => { + save(); + }); + }} +/> diff --git a/site/src/routes/flow/Rect.svelte b/site/src/routes/flow/Rect.svelte new file mode 100644 index 0000000..18f8d37 --- /dev/null +++ b/site/src/routes/flow/Rect.svelte @@ -0,0 +1,41 @@ + + +
+ {@render children()} +
diff --git a/site/src/routes/flow/generate-list-json.ts b/site/src/routes/flow/generate-list-json.ts new file mode 100644 index 0000000..17b18ec --- /dev/null +++ b/site/src/routes/flow/generate-list-json.ts @@ -0,0 +1,5 @@ +import fs from 'fs'; + +const icons_list = fs.readdirSync('../../../node_modules/bootstrap-icons/icons').map(a => a.replace('.svg', '')); + +fs.writeFileSync("icons-list.json", JSON.stringify(icons_list)) diff --git a/site/src/routes/flow/icons-list.json b/site/src/routes/flow/icons-list.json new file mode 100644 index 0000000..5a21ba5 --- /dev/null +++ b/site/src/routes/flow/icons-list.json @@ -0,0 +1 @@ +["0-circle-fill","0-circle","0-square-fill","0-square","1-circle-fill","1-circle","1-square-fill","1-square","123","2-circle-fill","2-circle","2-square-fill","2-square","3-circle-fill","3-circle","3-square-fill","3-square","4-circle-fill","4-circle","4-square-fill","4-square","5-circle-fill","5-circle","5-square-fill","5-square","6-circle-fill","6-circle","6-square-fill","6-square","7-circle-fill","7-circle","7-square-fill","7-square","8-circle-fill","8-circle","8-square-fill","8-square","9-circle-fill","9-circle","9-square-fill","9-square","activity","airplane-engines-fill","airplane-engines","airplane-fill","airplane","alarm-fill","alarm","alexa","align-bottom","align-center","align-end","align-middle","align-start","align-top","alipay","alphabet-uppercase","alphabet","alt","amazon","amd","android","android2","app-indicator","app","apple","archive-fill","archive","arrow-90deg-down","arrow-90deg-left","arrow-90deg-right","arrow-90deg-up","arrow-bar-down","arrow-bar-left","arrow-bar-right","arrow-bar-up","arrow-clockwise","arrow-counterclockwise","arrow-down-circle-fill","arrow-down-circle","arrow-down-left-circle-fill","arrow-down-left-circle","arrow-down-left-square-fill","arrow-down-left-square","arrow-down-left","arrow-down-right-circle-fill","arrow-down-right-circle","arrow-down-right-square-fill","arrow-down-right-square","arrow-down-right","arrow-down-short","arrow-down-square-fill","arrow-down-square","arrow-down-up","arrow-down","arrow-left-circle-fill","arrow-left-circle","arrow-left-right","arrow-left-short","arrow-left-square-fill","arrow-left-square","arrow-left","arrow-repeat","arrow-return-left","arrow-return-right","arrow-right-circle-fill","arrow-right-circle","arrow-right-short","arrow-right-square-fill","arrow-right-square","arrow-right","arrow-through-heart-fill","arrow-through-heart","arrow-up-circle-fill","arrow-up-circle","arrow-up-left-circle-fill","arrow-up-left-circle","arrow-up-left-square-fill","arrow-up-left-square","arrow-up-left","arrow-up-right-circle-fill","arrow-up-right-circle","arrow-up-right-square-fill","arrow-up-right-square","arrow-up-right","arrow-up-short","arrow-up-square-fill","arrow-up-square","arrow-up","arrows-angle-contract","arrows-angle-expand","arrows-collapse-vertical","arrows-collapse","arrows-expand-vertical","arrows-expand","arrows-fullscreen","arrows-move","arrows-vertical","arrows","aspect-ratio-fill","aspect-ratio","asterisk","at","award-fill","award","back","backpack-fill","backpack","backpack2-fill","backpack2","backpack3-fill","backpack3","backpack4-fill","backpack4","backspace-fill","backspace-reverse-fill","backspace-reverse","backspace","badge-3d-fill","badge-3d","badge-4k-fill","badge-4k","badge-8k-fill","badge-8k","badge-ad-fill","badge-ad","badge-ar-fill","badge-ar","badge-cc-fill","badge-cc","badge-hd-fill","badge-hd","badge-sd-fill","badge-sd","badge-tm-fill","badge-tm","badge-vo-fill","badge-vo","badge-vr-fill","badge-vr","badge-wc-fill","badge-wc","bag-check-fill","bag-check","bag-dash-fill","bag-dash","bag-fill","bag-heart-fill","bag-heart","bag-plus-fill","bag-plus","bag-x-fill","bag-x","bag","balloon-fill","balloon-heart-fill","balloon-heart","balloon","ban-fill","ban","bandaid-fill","bandaid","bank","bank2","bar-chart-fill","bar-chart-line-fill","bar-chart-line","bar-chart-steps","bar-chart","basket-fill","basket","basket2-fill","basket2","basket3-fill","basket3","battery-charging","battery-full","battery-half","battery","behance","bell-fill","bell-slash-fill","bell-slash","bell","bezier","bezier2","bicycle","bing","binoculars-fill","binoculars","blockquote-left","blockquote-right","bluetooth","body-text","book-fill","book-half","book","bookmark-check-fill","bookmark-check","bookmark-dash-fill","bookmark-dash","bookmark-fill","bookmark-heart-fill","bookmark-heart","bookmark-plus-fill","bookmark-plus","bookmark-star-fill","bookmark-star","bookmark-x-fill","bookmark-x","bookmark","bookmarks-fill","bookmarks","bookshelf","boombox-fill","boombox","bootstrap-fill","bootstrap-reboot","bootstrap","border-all","border-bottom","border-center","border-inner","border-left","border-middle","border-outer","border-right","border-style","border-top","border-width","border","bounding-box-circles","bounding-box","box-arrow-down-left","box-arrow-down-right","box-arrow-down","box-arrow-in-down-left","box-arrow-in-down-right","box-arrow-in-down","box-arrow-in-left","box-arrow-in-right","box-arrow-in-up-left","box-arrow-in-up-right","box-arrow-in-up","box-arrow-left","box-arrow-right","box-arrow-up-left","box-arrow-up-right","box-arrow-up","box-fill","box-seam-fill","box-seam","box","box2-fill","box2-heart-fill","box2-heart","box2","boxes","braces-asterisk","braces","bricks","briefcase-fill","briefcase","brightness-alt-high-fill","brightness-alt-high","brightness-alt-low-fill","brightness-alt-low","brightness-high-fill","brightness-high","brightness-low-fill","brightness-low","brilliance","broadcast-pin","broadcast","browser-chrome","browser-edge","browser-firefox","browser-safari","brush-fill","brush","bucket-fill","bucket","bug-fill","bug","building-add","building-check","building-dash","building-down","building-exclamation","building-fill-add","building-fill-check","building-fill-dash","building-fill-down","building-fill-exclamation","building-fill-gear","building-fill-lock","building-fill-slash","building-fill-up","building-fill-x","building-fill","building-gear","building-lock","building-slash","building-up","building-x","building","buildings-fill","buildings","bullseye","bus-front-fill","bus-front","c-circle-fill","c-circle","c-square-fill","c-square","cake-fill","cake","cake2-fill","cake2","calculator-fill","calculator","calendar-check-fill","calendar-check","calendar-date-fill","calendar-date","calendar-day-fill","calendar-day","calendar-event-fill","calendar-event","calendar-fill","calendar-heart-fill","calendar-heart","calendar-minus-fill","calendar-minus","calendar-month-fill","calendar-month","calendar-plus-fill","calendar-plus","calendar-range-fill","calendar-range","calendar-week-fill","calendar-week","calendar-x-fill","calendar-x","calendar","calendar2-check-fill","calendar2-check","calendar2-date-fill","calendar2-date","calendar2-day-fill","calendar2-day","calendar2-event-fill","calendar2-event","calendar2-fill","calendar2-heart-fill","calendar2-heart","calendar2-minus-fill","calendar2-minus","calendar2-month-fill","calendar2-month","calendar2-plus-fill","calendar2-plus","calendar2-range-fill","calendar2-range","calendar2-week-fill","calendar2-week","calendar2-x-fill","calendar2-x","calendar2","calendar3-event-fill","calendar3-event","calendar3-fill","calendar3-range-fill","calendar3-range","calendar3-week-fill","calendar3-week","calendar3","calendar4-event","calendar4-range","calendar4-week","calendar4","camera-fill","camera-reels-fill","camera-reels","camera-video-fill","camera-video-off-fill","camera-video-off","camera-video","camera","camera2","capslock-fill","capslock","capsule-pill","capsule","car-front-fill","car-front","card-checklist","card-heading","card-image","card-list","card-text","caret-down-fill","caret-down-square-fill","caret-down-square","caret-down","caret-left-fill","caret-left-square-fill","caret-left-square","caret-left","caret-right-fill","caret-right-square-fill","caret-right-square","caret-right","caret-up-fill","caret-up-square-fill","caret-up-square","caret-up","cart-check-fill","cart-check","cart-dash-fill","cart-dash","cart-fill","cart-plus-fill","cart-plus","cart-x-fill","cart-x","cart","cart2","cart3","cart4","cash-coin","cash-stack","cash","cassette-fill","cassette","cast","cc-circle-fill","cc-circle","cc-square-fill","cc-square","chat-dots-fill","chat-dots","chat-fill","chat-heart-fill","chat-heart","chat-left-dots-fill","chat-left-dots","chat-left-fill","chat-left-heart-fill","chat-left-heart","chat-left-quote-fill","chat-left-quote","chat-left-text-fill","chat-left-text","chat-left","chat-quote-fill","chat-quote","chat-right-dots-fill","chat-right-dots","chat-right-fill","chat-right-heart-fill","chat-right-heart","chat-right-quote-fill","chat-right-quote","chat-right-text-fill","chat-right-text","chat-right","chat-square-dots-fill","chat-square-dots","chat-square-fill","chat-square-heart-fill","chat-square-heart","chat-square-quote-fill","chat-square-quote","chat-square-text-fill","chat-square-text","chat-square","chat-text-fill","chat-text","chat","check-all","check-circle-fill","check-circle","check-lg","check-square-fill","check-square","check","check2-all","check2-circle","check2-square","check2","chevron-bar-contract","chevron-bar-down","chevron-bar-expand","chevron-bar-left","chevron-bar-right","chevron-bar-up","chevron-compact-down","chevron-compact-left","chevron-compact-right","chevron-compact-up","chevron-contract","chevron-double-down","chevron-double-left","chevron-double-right","chevron-double-up","chevron-down","chevron-expand","chevron-left","chevron-right","chevron-up","circle-fill","circle-half","circle-square","circle","clipboard-check-fill","clipboard-check","clipboard-data-fill","clipboard-data","clipboard-fill","clipboard-heart-fill","clipboard-heart","clipboard-minus-fill","clipboard-minus","clipboard-plus-fill","clipboard-plus","clipboard-pulse","clipboard-x-fill","clipboard-x","clipboard","clipboard2-check-fill","clipboard2-check","clipboard2-data-fill","clipboard2-data","clipboard2-fill","clipboard2-heart-fill","clipboard2-heart","clipboard2-minus-fill","clipboard2-minus","clipboard2-plus-fill","clipboard2-plus","clipboard2-pulse-fill","clipboard2-pulse","clipboard2-x-fill","clipboard2-x","clipboard2","clock-fill","clock-history","clock","cloud-arrow-down-fill","cloud-arrow-down","cloud-arrow-up-fill","cloud-arrow-up","cloud-check-fill","cloud-check","cloud-download-fill","cloud-download","cloud-drizzle-fill","cloud-drizzle","cloud-fill","cloud-fog-fill","cloud-fog","cloud-fog2-fill","cloud-fog2","cloud-hail-fill","cloud-hail","cloud-haze-fill","cloud-haze","cloud-haze2-fill","cloud-haze2","cloud-lightning-fill","cloud-lightning-rain-fill","cloud-lightning-rain","cloud-lightning","cloud-minus-fill","cloud-minus","cloud-moon-fill","cloud-moon","cloud-plus-fill","cloud-plus","cloud-rain-fill","cloud-rain-heavy-fill","cloud-rain-heavy","cloud-rain","cloud-slash-fill","cloud-slash","cloud-sleet-fill","cloud-sleet","cloud-snow-fill","cloud-snow","cloud-sun-fill","cloud-sun","cloud-upload-fill","cloud-upload","cloud","clouds-fill","clouds","cloudy-fill","cloudy","code-slash","code-square","code","coin","collection-fill","collection-play-fill","collection-play","collection","columns-gap","columns","command","compass-fill","compass","cone-striped","cone","controller","cookie","copy","cpu-fill","cpu","credit-card-2-back-fill","credit-card-2-back","credit-card-2-front-fill","credit-card-2-front","credit-card-fill","credit-card","crop","crosshair","crosshair2","cup-fill","cup-hot-fill","cup-hot","cup-straw","cup","currency-bitcoin","currency-dollar","currency-euro","currency-exchange","currency-pound","currency-rupee","currency-yen","cursor-fill","cursor-text","cursor","dash-circle-dotted","dash-circle-fill","dash-circle","dash-lg","dash-square-dotted","dash-square-fill","dash-square","dash","database-add","database-check","database-dash","database-down","database-exclamation","database-fill-add","database-fill-check","database-fill-dash","database-fill-down","database-fill-exclamation","database-fill-gear","database-fill-lock","database-fill-slash","database-fill-up","database-fill-x","database-fill","database-gear","database-lock","database-slash","database-up","database-x","database","device-hdd-fill","device-hdd","device-ssd-fill","device-ssd","diagram-2-fill","diagram-2","diagram-3-fill","diagram-3","diamond-fill","diamond-half","diamond","dice-1-fill","dice-1","dice-2-fill","dice-2","dice-3-fill","dice-3","dice-4-fill","dice-4","dice-5-fill","dice-5","dice-6-fill","dice-6","disc-fill","disc","discord","display-fill","display","displayport-fill","displayport","distribute-horizontal","distribute-vertical","door-closed-fill","door-closed","door-open-fill","door-open","dot","download","dpad-fill","dpad","dribbble","dropbox","droplet-fill","droplet-half","droplet","duffle-fill","duffle","ear-fill","ear","earbuds","easel-fill","easel","easel2-fill","easel2","easel3-fill","easel3","egg-fill","egg-fried","egg","eject-fill","eject","emoji-angry-fill","emoji-angry","emoji-astonished-fill","emoji-astonished","emoji-dizzy-fill","emoji-dizzy","emoji-expressionless-fill","emoji-expressionless","emoji-frown-fill","emoji-frown","emoji-grimace-fill","emoji-grimace","emoji-grin-fill","emoji-grin","emoji-heart-eyes-fill","emoji-heart-eyes","emoji-kiss-fill","emoji-kiss","emoji-laughing-fill","emoji-laughing","emoji-neutral-fill","emoji-neutral","emoji-smile-fill","emoji-smile-upside-down-fill","emoji-smile-upside-down","emoji-smile","emoji-sunglasses-fill","emoji-sunglasses","emoji-surprise-fill","emoji-surprise","emoji-tear-fill","emoji-tear","emoji-wink-fill","emoji-wink","envelope-arrow-down-fill","envelope-arrow-down","envelope-arrow-up-fill","envelope-arrow-up","envelope-at-fill","envelope-at","envelope-check-fill","envelope-check","envelope-dash-fill","envelope-dash","envelope-exclamation-fill","envelope-exclamation","envelope-fill","envelope-heart-fill","envelope-heart","envelope-open-fill","envelope-open-heart-fill","envelope-open-heart","envelope-open","envelope-paper-fill","envelope-paper-heart-fill","envelope-paper-heart","envelope-paper","envelope-plus-fill","envelope-plus","envelope-slash-fill","envelope-slash","envelope-x-fill","envelope-x","envelope","eraser-fill","eraser","escape","ethernet","ev-front-fill","ev-front","ev-station-fill","ev-station","exclamation-circle-fill","exclamation-circle","exclamation-diamond-fill","exclamation-diamond","exclamation-lg","exclamation-octagon-fill","exclamation-octagon","exclamation-square-fill","exclamation-square","exclamation-triangle-fill","exclamation-triangle","exclamation","exclude","explicit-fill","explicit","exposure","eye-fill","eye-slash-fill","eye-slash","eye","eyedropper","eyeglasses","facebook","fan","fast-forward-btn-fill","fast-forward-btn","fast-forward-circle-fill","fast-forward-circle","fast-forward-fill","fast-forward","feather","feather2","file-arrow-down-fill","file-arrow-down","file-arrow-up-fill","file-arrow-up","file-bar-graph-fill","file-bar-graph","file-binary-fill","file-binary","file-break-fill","file-break","file-check-fill","file-check","file-code-fill","file-code","file-diff-fill","file-diff","file-earmark-arrow-down-fill","file-earmark-arrow-down","file-earmark-arrow-up-fill","file-earmark-arrow-up","file-earmark-bar-graph-fill","file-earmark-bar-graph","file-earmark-binary-fill","file-earmark-binary","file-earmark-break-fill","file-earmark-break","file-earmark-check-fill","file-earmark-check","file-earmark-code-fill","file-earmark-code","file-earmark-diff-fill","file-earmark-diff","file-earmark-easel-fill","file-earmark-easel","file-earmark-excel-fill","file-earmark-excel","file-earmark-fill","file-earmark-font-fill","file-earmark-font","file-earmark-image-fill","file-earmark-image","file-earmark-lock-fill","file-earmark-lock","file-earmark-lock2-fill","file-earmark-lock2","file-earmark-medical-fill","file-earmark-medical","file-earmark-minus-fill","file-earmark-minus","file-earmark-music-fill","file-earmark-music","file-earmark-pdf-fill","file-earmark-pdf","file-earmark-person-fill","file-earmark-person","file-earmark-play-fill","file-earmark-play","file-earmark-plus-fill","file-earmark-plus","file-earmark-post-fill","file-earmark-post","file-earmark-ppt-fill","file-earmark-ppt","file-earmark-richtext-fill","file-earmark-richtext","file-earmark-ruled-fill","file-earmark-ruled","file-earmark-slides-fill","file-earmark-slides","file-earmark-spreadsheet-fill","file-earmark-spreadsheet","file-earmark-text-fill","file-earmark-text","file-earmark-word-fill","file-earmark-word","file-earmark-x-fill","file-earmark-x","file-earmark-zip-fill","file-earmark-zip","file-earmark","file-easel-fill","file-easel","file-excel-fill","file-excel","file-fill","file-font-fill","file-font","file-image-fill","file-image","file-lock-fill","file-lock","file-lock2-fill","file-lock2","file-medical-fill","file-medical","file-minus-fill","file-minus","file-music-fill","file-music","file-pdf-fill","file-pdf","file-person-fill","file-person","file-play-fill","file-play","file-plus-fill","file-plus","file-post-fill","file-post","file-ppt-fill","file-ppt","file-richtext-fill","file-richtext","file-ruled-fill","file-ruled","file-slides-fill","file-slides","file-spreadsheet-fill","file-spreadsheet","file-text-fill","file-text","file-word-fill","file-word","file-x-fill","file-x","file-zip-fill","file-zip","file","files-alt","files","filetype-aac","filetype-ai","filetype-bmp","filetype-cs","filetype-css","filetype-csv","filetype-doc","filetype-docx","filetype-exe","filetype-gif","filetype-heic","filetype-html","filetype-java","filetype-jpg","filetype-js","filetype-json","filetype-jsx","filetype-key","filetype-m4p","filetype-md","filetype-mdx","filetype-mov","filetype-mp3","filetype-mp4","filetype-otf","filetype-pdf","filetype-php","filetype-png","filetype-ppt","filetype-pptx","filetype-psd","filetype-py","filetype-raw","filetype-rb","filetype-sass","filetype-scss","filetype-sh","filetype-sql","filetype-svg","filetype-tiff","filetype-tsx","filetype-ttf","filetype-txt","filetype-wav","filetype-woff","filetype-xls","filetype-xlsx","filetype-xml","filetype-yml","film","filter-circle-fill","filter-circle","filter-left","filter-right","filter-square-fill","filter-square","filter","fingerprint","fire","flag-fill","flag","floppy-fill","floppy","floppy2-fill","floppy2","flower1","flower2","flower3","folder-check","folder-fill","folder-minus","folder-plus","folder-symlink-fill","folder-symlink","folder-x","folder","folder2-open","folder2","fonts","forward-fill","forward","front","fuel-pump-diesel-fill","fuel-pump-diesel","fuel-pump-fill","fuel-pump","fullscreen-exit","fullscreen","funnel-fill","funnel","gear-fill","gear-wide-connected","gear-wide","gear","gem","gender-ambiguous","gender-female","gender-male","gender-neuter","gender-trans","geo-alt-fill","geo-alt","geo-fill","geo","gift-fill","gift","git","github","gitlab","globe-americas","globe-asia-australia","globe-central-south-asia","globe-europe-africa","globe","globe2","google-play","google","gpu-card","graph-down-arrow","graph-down","graph-up-arrow","graph-up","grid-1x2-fill","grid-1x2","grid-3x2-gap-fill","grid-3x2-gap","grid-3x2","grid-3x3-gap-fill","grid-3x3-gap","grid-3x3","grid-fill","grid","grip-horizontal","grip-vertical","h-circle-fill","h-circle","h-square-fill","h-square","hammer","hand-index-fill","hand-index-thumb-fill","hand-index-thumb","hand-index","hand-thumbs-down-fill","hand-thumbs-down","hand-thumbs-up-fill","hand-thumbs-up","handbag-fill","handbag","hash","hdd-fill","hdd-network-fill","hdd-network","hdd-rack-fill","hdd-rack","hdd-stack-fill","hdd-stack","hdd","hdmi-fill","hdmi","headphones","headset-vr","headset","heart-arrow","heart-fill","heart-half","heart-pulse-fill","heart-pulse","heart","heartbreak-fill","heartbreak","hearts","heptagon-fill","heptagon-half","heptagon","hexagon-fill","hexagon-half","hexagon","highlighter","highlights","hospital-fill","hospital","hourglass-bottom","hourglass-split","hourglass-top","hourglass","house-add-fill","house-add","house-check-fill","house-check","house-dash-fill","house-dash","house-door-fill","house-door","house-down-fill","house-down","house-exclamation-fill","house-exclamation","house-fill","house-gear-fill","house-gear","house-heart-fill","house-heart","house-lock-fill","house-lock","house-slash-fill","house-slash","house-up-fill","house-up","house-x-fill","house-x","house","houses-fill","houses","hr","hurricane","hypnotize","image-alt","image-fill","image","images","inbox-fill","inbox","inboxes-fill","inboxes","incognito","indent","infinity","info-circle-fill","info-circle","info-lg","info-square-fill","info-square","info","input-cursor-text","input-cursor","instagram","intersect","journal-album","journal-arrow-down","journal-arrow-up","journal-bookmark-fill","journal-bookmark","journal-check","journal-code","journal-medical","journal-minus","journal-plus","journal-richtext","journal-text","journal-x","journal","journals","joystick","justify-left","justify-right","justify","kanban-fill","kanban","key-fill","key","keyboard-fill","keyboard","ladder","lamp-fill","lamp","laptop-fill","laptop","layer-backward","layer-forward","layers-fill","layers-half","layers","layout-sidebar-inset-reverse","layout-sidebar-inset","layout-sidebar-reverse","layout-sidebar","layout-split","layout-text-sidebar-reverse","layout-text-sidebar","layout-text-window-reverse","layout-text-window","layout-three-columns","layout-wtf","life-preserver","lightbulb-fill","lightbulb-off-fill","lightbulb-off","lightbulb","lightning-charge-fill","lightning-charge","lightning-fill","lightning","line","link-45deg","link","linkedin","list-check","list-columns-reverse","list-columns","list-nested","list-ol","list-stars","list-task","list-ul","list","lock-fill","lock","luggage-fill","luggage","lungs-fill","lungs","magic","magnet-fill","magnet","mailbox-flag","mailbox","mailbox2-flag","mailbox2","map-fill","map","markdown-fill","markdown","marker-tip","mask","mastodon","medium","megaphone-fill","megaphone","memory","menu-app-fill","menu-app","menu-button-fill","menu-button-wide-fill","menu-button-wide","menu-button","menu-down","menu-up","messenger","meta","mic-fill","mic-mute-fill","mic-mute","mic","microsoft-teams","microsoft","minecart-loaded","minecart","modem-fill","modem","moisture","moon-fill","moon-stars-fill","moon-stars","moon","mortarboard-fill","mortarboard","motherboard-fill","motherboard","mouse-fill","mouse","mouse2-fill","mouse2","mouse3-fill","mouse3","music-note-beamed","music-note-list","music-note","music-player-fill","music-player","newspaper","nintendo-switch","node-minus-fill","node-minus","node-plus-fill","node-plus","noise-reduction","nut-fill","nut","nvidia","nvme-fill","nvme","octagon-fill","octagon-half","octagon","opencollective","optical-audio-fill","optical-audio","option","outlet","p-circle-fill","p-circle","p-square-fill","p-square","paint-bucket","palette-fill","palette","palette2","paperclip","paragraph","pass-fill","pass","passport-fill","passport","patch-check-fill","patch-check","patch-exclamation-fill","patch-exclamation","patch-minus-fill","patch-minus","patch-plus-fill","patch-plus","patch-question-fill","patch-question","pause-btn-fill","pause-btn","pause-circle-fill","pause-circle","pause-fill","pause","paypal","pc-display-horizontal","pc-display","pc-horizontal","pc","pci-card-network","pci-card-sound","pci-card","peace-fill","peace","pen-fill","pen","pencil-fill","pencil-square","pencil","pentagon-fill","pentagon-half","pentagon","people-fill","people","percent","person-add","person-arms-up","person-badge-fill","person-badge","person-bounding-box","person-check-fill","person-check","person-circle","person-dash-fill","person-dash","person-down","person-exclamation","person-fill-add","person-fill-check","person-fill-dash","person-fill-down","person-fill-exclamation","person-fill-gear","person-fill-lock","person-fill-slash","person-fill-up","person-fill-x","person-fill","person-gear","person-heart","person-hearts","person-lines-fill","person-lock","person-plus-fill","person-plus","person-raised-hand","person-rolodex","person-slash","person-square","person-standing-dress","person-standing","person-up","person-vcard-fill","person-vcard","person-video","person-video2","person-video3","person-walking","person-wheelchair","person-workspace","person-x-fill","person-x","person","phone-fill","phone-flip","phone-landscape-fill","phone-landscape","phone-vibrate-fill","phone-vibrate","phone","pie-chart-fill","pie-chart","piggy-bank-fill","piggy-bank","pin-angle-fill","pin-angle","pin-fill","pin-map-fill","pin-map","pin","pinterest","pip-fill","pip","play-btn-fill","play-btn","play-circle-fill","play-circle","play-fill","play","playstation","plug-fill","plug","plugin","plus-circle-dotted","plus-circle-fill","plus-circle","plus-lg","plus-slash-minus","plus-square-dotted","plus-square-fill","plus-square","plus","postage-fill","postage-heart-fill","postage-heart","postage","postcard-fill","postcard-heart-fill","postcard-heart","postcard","power","prescription","prescription2","printer-fill","printer","projector-fill","projector","puzzle-fill","puzzle","qr-code-scan","qr-code","question-circle-fill","question-circle","question-diamond-fill","question-diamond","question-lg","question-octagon-fill","question-octagon","question-square-fill","question-square","question","quora","quote","r-circle-fill","r-circle","r-square-fill","r-square","radar","radioactive","rainbow","receipt-cutoff","receipt","reception-0","reception-1","reception-2","reception-3","reception-4","record-btn-fill","record-btn","record-circle-fill","record-circle","record-fill","record","record2-fill","record2","recycle","reddit","regex","repeat-1","repeat","reply-all-fill","reply-all","reply-fill","reply","rewind-btn-fill","rewind-btn","rewind-circle-fill","rewind-circle","rewind-fill","rewind","robot","rocket-fill","rocket-takeoff-fill","rocket-takeoff","rocket","router-fill","router","rss-fill","rss","rulers","safe-fill","safe","safe2-fill","safe2","save-fill","save","save2-fill","save2","scissors","scooter","screwdriver","sd-card-fill","sd-card","search-heart-fill","search-heart","search","segmented-nav","send-arrow-down-fill","send-arrow-down","send-arrow-up-fill","send-arrow-up","send-check-fill","send-check","send-dash-fill","send-dash","send-exclamation-fill","send-exclamation","send-fill","send-plus-fill","send-plus","send-slash-fill","send-slash","send-x-fill","send-x","send","server","shadows","share-fill","share","shield-check","shield-exclamation","shield-fill-check","shield-fill-exclamation","shield-fill-minus","shield-fill-plus","shield-fill-x","shield-fill","shield-lock-fill","shield-lock","shield-minus","shield-plus","shield-shaded","shield-slash-fill","shield-slash","shield-x","shield","shift-fill","shift","shop-window","shop","shuffle","sign-dead-end-fill","sign-dead-end","sign-do-not-enter-fill","sign-do-not-enter","sign-intersection-fill","sign-intersection-side-fill","sign-intersection-side","sign-intersection-t-fill","sign-intersection-t","sign-intersection-y-fill","sign-intersection-y","sign-intersection","sign-merge-left-fill","sign-merge-left","sign-merge-right-fill","sign-merge-right","sign-no-left-turn-fill","sign-no-left-turn","sign-no-parking-fill","sign-no-parking","sign-no-right-turn-fill","sign-no-right-turn","sign-railroad-fill","sign-railroad","sign-stop-fill","sign-stop-lights-fill","sign-stop-lights","sign-stop","sign-turn-left-fill","sign-turn-left","sign-turn-right-fill","sign-turn-right","sign-turn-slight-left-fill","sign-turn-slight-left","sign-turn-slight-right-fill","sign-turn-slight-right","sign-yield-fill","sign-yield","signal","signpost-2-fill","signpost-2","signpost-fill","signpost-split-fill","signpost-split","signpost","sim-fill","sim-slash-fill","sim-slash","sim","sina-weibo","skip-backward-btn-fill","skip-backward-btn","skip-backward-circle-fill","skip-backward-circle","skip-backward-fill","skip-backward","skip-end-btn-fill","skip-end-btn","skip-end-circle-fill","skip-end-circle","skip-end-fill","skip-end","skip-forward-btn-fill","skip-forward-btn","skip-forward-circle-fill","skip-forward-circle","skip-forward-fill","skip-forward","skip-start-btn-fill","skip-start-btn","skip-start-circle-fill","skip-start-circle","skip-start-fill","skip-start","skype","slack","slash-circle-fill","slash-circle","slash-lg","slash-square-fill","slash-square","slash","sliders","sliders2-vertical","sliders2","smartwatch","snapchat","snow","snow2","snow3","sort-alpha-down-alt","sort-alpha-down","sort-alpha-up-alt","sort-alpha-up","sort-down-alt","sort-down","sort-numeric-down-alt","sort-numeric-down","sort-numeric-up-alt","sort-numeric-up","sort-up-alt","sort-up","soundwave","sourceforge","speaker-fill","speaker","speedometer","speedometer2","spellcheck","spotify","square-fill","square-half","square","stack-overflow","stack","star-fill","star-half","star","stars","steam","stickies-fill","stickies","sticky-fill","sticky","stop-btn-fill","stop-btn","stop-circle-fill","stop-circle","stop-fill","stop","stoplights-fill","stoplights","stopwatch-fill","stopwatch","strava","stripe","subscript","substack","subtract","suit-club-fill","suit-club","suit-diamond-fill","suit-diamond","suit-heart-fill","suit-heart","suit-spade-fill","suit-spade","suitcase-fill","suitcase-lg-fill","suitcase-lg","suitcase","suitcase2-fill","suitcase2","sun-fill","sun","sunglasses","sunrise-fill","sunrise","sunset-fill","sunset","superscript","symmetry-horizontal","symmetry-vertical","table","tablet-fill","tablet-landscape-fill","tablet-landscape","tablet","tag-fill","tag","tags-fill","tags","taxi-front-fill","taxi-front","telegram","telephone-fill","telephone-forward-fill","telephone-forward","telephone-inbound-fill","telephone-inbound","telephone-minus-fill","telephone-minus","telephone-outbound-fill","telephone-outbound","telephone-plus-fill","telephone-plus","telephone-x-fill","telephone-x","telephone","tencent-qq","terminal-dash","terminal-fill","terminal-plus","terminal-split","terminal-x","terminal","text-center","text-indent-left","text-indent-right","text-left","text-paragraph","text-right","text-wrap","textarea-resize","textarea-t","textarea","thermometer-half","thermometer-high","thermometer-low","thermometer-snow","thermometer-sun","thermometer","threads-fill","threads","three-dots-vertical","three-dots","thunderbolt-fill","thunderbolt","ticket-detailed-fill","ticket-detailed","ticket-fill","ticket-perforated-fill","ticket-perforated","ticket","tiktok","toggle-off","toggle-on","toggle2-off","toggle2-on","toggles","toggles2","tools","tornado","train-freight-front-fill","train-freight-front","train-front-fill","train-front","train-lightrail-front-fill","train-lightrail-front","translate","transparency","trash-fill","trash","trash2-fill","trash2","trash3-fill","trash3","tree-fill","tree","trello","triangle-fill","triangle-half","triangle","trophy-fill","trophy","tropical-storm","truck-flatbed","truck-front-fill","truck-front","truck","tsunami","tv-fill","tv","twitch","twitter-x","twitter","type-bold","type-h1","type-h2","type-h3","type-h4","type-h5","type-h6","type-italic","type-strikethrough","type-underline","type","ubuntu","ui-checks-grid","ui-checks","ui-radios-grid","ui-radios","umbrella-fill","umbrella","unindent","union","unity","universal-access-circle","universal-access","unlock-fill","unlock","upc-scan","upc","upload","usb-c-fill","usb-c","usb-drive-fill","usb-drive","usb-fill","usb-micro-fill","usb-micro","usb-mini-fill","usb-mini","usb-plug-fill","usb-plug","usb-symbol","usb","valentine","valentine2","vector-pen","view-list","view-stacked","vignette","vimeo","vinyl-fill","vinyl","virus","virus2","voicemail","volume-down-fill","volume-down","volume-mute-fill","volume-mute","volume-off-fill","volume-off","volume-up-fill","volume-up","vr","wallet-fill","wallet","wallet2","watch","water","webcam-fill","webcam","wechat","whatsapp","wifi-1","wifi-2","wifi-off","wifi","wikipedia","wind","window-dash","window-desktop","window-dock","window-fullscreen","window-plus","window-sidebar","window-split","window-stack","window-x","window","windows","wordpress","wrench-adjustable-circle-fill","wrench-adjustable-circle","wrench-adjustable","wrench","x-circle-fill","x-circle","x-diamond-fill","x-diamond","x-lg","x-octagon-fill","x-octagon","x-square-fill","x-square","x","xbox","yelp","yin-yang","youtube","zoom-in","zoom-out"] \ No newline at end of file diff --git a/site/src/routes/flow/types.ts b/site/src/routes/flow/types.ts new file mode 100644 index 0000000..8301815 --- /dev/null +++ b/site/src/routes/flow/types.ts @@ -0,0 +1,60 @@ +export type Node = { + id: string, + user_id: string, + x: number; + y: number; + width: number; + height: number; + name: string; + icon: string; + // 1 disables editing + permission: number; +}; + +export type LinkNode = { + node: Node; + x: number; + y: number; +} + +export type FullLink = { + sourceNode: LinkNode; + targetNode: LinkNode; + bi?: boolean; +} + +export type FullLinkApi = { + id: string, + user_id: string, + target_node: string | null, + source_node: string | null, + bi: boolean, + source_x: number, + source_y: number, + + target_x: number, + target_y: number +} + + +export type ActionType = undefined | 'drag' | 'create' | 'move' | 'link'; + +export function extractLinkNodePosX(canvasX: (x: number) => number, node: LinkNode, grid_size: number) { + if (node.x === -1) { + return canvasX(node.node.x); + } + if (node.x === node.node.width) { + return canvasX(node.node.x) + node.node.width * grid_size; + } + return canvasX(node.node.x) + node.x * grid_size + grid_size / 2; +} + +export function extractLinkNodePosY(canvasY: (x: number) => number, node: LinkNode, grid_size: number) { + if (node.y === -1) { + return canvasY(node.node.y); + } + if (node.y === node.node.height) { + return canvasY(node.node.y) + node.node.height * grid_size; + } + return canvasY(node.node.y) + node.y * grid_size + grid_size / 2; +} diff --git a/site/src/routes/graphs/+page.svelte b/site/src/routes/graphs/+page.svelte index 10e17d3..ef705fb 100644 --- a/site/src/routes/graphs/+page.svelte +++ b/site/src/routes/graphs/+page.svelte @@ -16,6 +16,8 @@ let showLinked = $state(false); let showToApply = $state(false); + let totalPercetange = $state(true); + // Handle the graph creation $effect(() => { if (!chartDiv || applications.length == 0) return; @@ -46,6 +48,8 @@ Linkedin: 0 }; + let count = 0; + applications.forEach((a) => { let source: NodeType; @@ -65,6 +69,8 @@ return; } + count++; + if (a.url.includes('linkedin')) { source = 'Linkedin'; sourceData['Linkedin'] += 1; @@ -106,6 +112,12 @@ return; } } + if (history.includes(`${ApplicationStatus.TasksToDo2}`)) { + source = addGraph(source, ApplicationStatus.TasksToDo2); + if (a.status == ApplicationStatus.TasksToDo2) { + return; + } + } if (history.includes(`${ApplicationStatus.InterviewStep1}`)) { source = addGraph(source, ApplicationStatus.InterviewStep1); if (a.status == ApplicationStatus.InterviewStep1) { @@ -118,6 +130,12 @@ return; } } + if (history.includes(`${ApplicationStatus.FinalInterview}`)) { + source = addGraph(source, ApplicationStatus.FinalInterview); + if (a.status == ApplicationStatus.FinalInterview) { + return; + } + } addGraph(source, ApplicationStatus.ApplyedButSaidNo); }); @@ -154,7 +172,9 @@ originalValue: node, id: name, index: i, - percentage: Math.trunc((value / applications.length) * 100) + percentage: Math.trunc( + (value / (totalPercetange ? applications.length : count)) * 100 + ) }; return base; }); @@ -201,9 +221,9 @@ // let color = d3.schemeSpectral[nodes.length]; // let color = d3.interpolateTurbo(nodes.length); - function getColor(index: number) { - return d3.interpolateRainbow(index/nodes.length); - } + function getColor(index: number) { + return d3.interpolateRainbow(index / nodes.length); + } // add in the links var link = svg @@ -221,10 +241,12 @@ .attr('class', 'link') .attr('d', path) .style('stroke', function (d) { - return d3.rgb( - getColor(d.source.index) - // color[d.source.index] - ).toString(); + return d3 + .rgb( + getColor(d.source.index) + // color[d.source.index] + ) + .toString(); }) .style('stroke-width', function (d) { return Math.max(1, d.dy); @@ -264,13 +286,16 @@ .attr('width', sankey.getNodeWidth()) .style('fill', function (d) { return getColor(d.index); - //color[d.index]; + //color[d.index]; }) .style('stroke', (d) => { - return d3.rgb( - getColor(d.index) - //color[d.index] - ).darker(2).toString(); + return d3 + .rgb( + getColor(d.index) + //color[d.index] + ) + .darker(2) + .toString(); }) .append('title') .text(function (d) { @@ -329,6 +354,10 @@ +
+ + +
diff --git a/site/src/routes/work-area/CompanyField.svelte b/site/src/routes/work-area/CompanyField.svelte new file mode 100644 index 0000000..79f27ee --- /dev/null +++ b/site/src/routes/work-area/CompanyField.svelte @@ -0,0 +1,28 @@ + + +
+ + +
+ {#each fcomps as comp} + + {/each} +
+
diff --git a/site/src/routes/work-area/DropingZone.svelte b/site/src/routes/work-area/DropingZone.svelte new file mode 100644 index 0000000..be98eb3 --- /dev/null +++ b/site/src/routes/work-area/DropingZone.svelte @@ -0,0 +1,198 @@ + + +{#if applicationStore.dragging && activeItem} +
+ + {#if activeItem.status === ApplicationStatus.WorkingOnIt} + { + await moveStatus(ApplicationStatus.ToApply); + applicationStore.loadAplyed(true); + applicationStore.loadAll(true); + }} + > + To apply + + {/if} + + {#if activeItem.status === ApplicationStatus.WorkingOnIt} + + moveStatus(ApplicationStatus.Ignore)}> + Ignore it + + {/if} + + {#if ([ApplicationStatus.WorkingOnIt, ApplicationStatus.Expired] as number[]).includes(activeItem.status)} + + { + if (activeItem && activeItem.status === ApplicationStatus.Expired) { + moveStatus(ApplicationStatus.ToApply, false); + } else { + moveStatus(ApplicationStatus.Expired); + } + }} + > + Mark as expired + + {/if} + + {#if activeItem.status === ApplicationStatus.WorkingOnIt} + + remove()}>Delete it + {/if} + + {#if ([ApplicationStatus.WorkingOnIt, ApplicationStatus.TasksToDo] as number[]).includes(activeItem.status)} + + { + await moveStatus(ApplicationStatus.Applyed); + applicationStore.loadAll(true); + applicationStore.loadAplyed(true); + }} + > + Apply + + {/if} + + {#if activeItem.status === ApplicationStatus.Applyed} + + { + await moveStatus(ApplicationStatus.TasksToDo); + applicationStore.loadAll(true); + applicationStore.loadAplyed(true); + }} + > + Tasks To Do + + {/if} + + {#if activeItem.status === ApplicationStatus.TasksToDo} + + { + await moveStatus(ApplicationStatus.TasksToDo2); + applicationStore.loadAll(true); + applicationStore.loadAplyed(true); + }} + > + Tasks To Do 2 + + {/if} + + {#if ([ApplicationStatus.TasksToDo, ApplicationStatus.Applyed] as number[]).includes(activeItem.status)} + + { + await moveStatus(ApplicationStatus.InterviewStep1); + applicationStore.loadAll(true); + applicationStore.loadAplyed(true); + }} + > + Interview 1 + + {/if} + + {#if ([ApplicationStatus.InterviewStep1] as number[]).includes(activeItem.status)} + + { + await moveStatus(ApplicationStatus.InterviewStep2); + applicationStore.loadAll(true); + applicationStore.loadAplyed(true); + }} + > + Interview 2 + + {/if} + + {#if ([ApplicationStatus.InterviewStep1, ApplicationStatus.InterviewStep2] as number[]).includes(activeItem.status)} + + { + await moveStatus(ApplicationStatus.FinalInterview); + applicationStore.loadAll(true); + applicationStore.loadAplyed(true); + }} + > + Interview Final + + {/if} + + {#if !([ApplicationStatus.ApplyedButSaidNo] as number[]).includes(activeItem.status)} + + { + moveStatus(ApplicationStatus.ApplyedButSaidNo); + applicationStore.loadAll(true); + applicationStore.loadAplyed(true); + }} + > + I was rejeted :( + + {/if} +
+{/if} diff --git a/site/src/routes/work-area/LinkApplication.svelte b/site/src/routes/work-area/LinkApplication.svelte index b6433ab..1476589 100644 --- a/site/src/routes/work-area/LinkApplication.svelte +++ b/site/src/routes/work-area/LinkApplication.svelte @@ -27,8 +27,11 @@ async function submit(item: Application) { try { application.linked_application = item.id; - application.status = ApplicationStatus.LinkedApplication; await put('application/update', application); + await put('application/status', { + id: application.id, + status: ApplicationStatus.LinkedApplication + }); dialog.close(); onreload(item); } catch (e) { @@ -70,9 +73,9 @@
{/if} - - {item.url} - + + {ApplicationStatusMaping[item.status]} +
{/each} diff --git a/site/src/routes/work-area/SearchApplication.svelte b/site/src/routes/work-area/SearchApplication.svelte index fbf8a68..0426545 100644 --- a/site/src/routes/work-area/SearchApplication.svelte +++ b/site/src/routes/work-area/SearchApplication.svelte @@ -36,18 +36,9 @@ document.removeEventListener('keydown', docKey); }; }); - - -
- -
- {applications.length} -
-
-
- - {#each applications.filter((i) => { + let internal = $derived( + applications.filter((i) => { if (application && i.id == application.id) { return false; } @@ -71,7 +62,20 @@ } return x.match(f); - }) as item} + }) + ); + + + +
+ +
+ {internal.length} +
+
+
+ + {#each internal as item}
-
- {ApplicationStatusMaping[item.status]} -
- - {item.url} + + {ApplicationStatusMaping[item.status]}
diff --git a/site/src/routes/work-area/Timeline.svelte b/site/src/routes/work-area/Timeline.svelte index de87d6b..a8020ae 100644 --- a/site/src/routes/work-area/Timeline.svelte +++ b/site/src/routes/work-area/Timeline.svelte @@ -8,7 +8,7 @@ ApplicationStatusMaping } from '$lib/ApplicationsStore.svelte'; - let { application }: { application: Application } = $props(); + let { application, showAll }: { application: Application, showAll: boolean } = $props(); let events: (ApplicationEvent & { timeDiff: string })[] = $state([]); @@ -58,7 +58,7 @@ if (event.event_type === EventType.Creation) { status = ApplicationStatus.ToApply; } - if (event.event_type !== EventType.StatusUpdate) { + if (event.event_type !== EventType.StatusUpdate || showAll) { if (time) { _events[_events.length - 1].timeDiff = time; } @@ -118,10 +118,13 @@ class="shadow-sm shadow-violet-500 border rounded-full min-w-[50px] min-h-[50px] grid place-items-center bg-white z-10" > {#if event.event_type == EventType.Creation} - + {:else if event.event_type == EventType.View} {:else} diff --git a/site/src/routes/work-area/WorkArea.svelte b/site/src/routes/work-area/WorkArea.svelte index 439256d..b7b6297 100644 --- a/site/src/routes/work-area/WorkArea.svelte +++ b/site/src/routes/work-area/WorkArea.svelte @@ -3,21 +3,21 @@ ApplicationStatus, ApplicationStatusMaping, applicationStore, - type Application, - type AsEnum + type Application } from '$lib/ApplicationsStore.svelte'; - import { put, preventDefault, post, get, deleteR } from '$lib/utils'; + import { put, preventDefault, post, get } from '$lib/utils'; import { onMount } from 'svelte'; import ExtractTextDialog from './ExtractTextDialog.svelte'; import Flair from '../flair/Flair.svelte'; import NewUrlDialog from './NewUrlDialog.svelte'; - import DropZone from './DropZone.svelte'; import { userStore } from '$lib/UserStore.svelte'; import LinkApplication from './LinkApplication.svelte'; import SearchApplication from './SearchApplication.svelte'; import NewApplication from './NewApplication.svelte'; import Timeline from './Timeline.svelte'; + import DropingZone from './DropingZone.svelte'; + import CompanyField from './CompanyField.svelte'; let activeItem: Application | undefined = $state(); @@ -30,6 +30,8 @@ let showExtraData = $state(false); + let drag = $state(true); + async function activate(item?: Application) { if (!item) { return; @@ -94,6 +96,9 @@ ); } + // + // Make the CV open on a new page + // function docKey(e: KeyboardEvent) { if (activeItem && e.ctrlKey && e.code === 'KeyO') { openCV(activeItem.id); @@ -108,6 +113,9 @@ document.removeEventListener('keydown', docKey); }; }); + // + // + // async function loadActive() { try { @@ -175,46 +183,6 @@ applicationStore.loadItem = undefined; }); - async function moveStatus(status: AsEnum, moveOut = true) { - if (!activeItem) return; - // Deactivate active item - try { - await put('application/status', { - id: activeItem.id, - status: status - }); - } catch (e) { - // Show User - console.log('info data', e); - return; - } - applicationStore.loadApplications(true); - applicationStore.loadAplyed(true); - if (moveOut) { - activeItem = undefined; - } else { - activeItem.status = status; - } - //openedWindow?.close(); - //openedWindow = undefined; - } - - async function remove() { - if (!activeItem) return; - // Deactivate active item - try { - await deleteR(`application/${activeItem.id}`); - } catch (e) { - // Show User - console.log('info data', e); - return; - } - applicationStore.loadApplications(true); - activeItem = undefined; - //openedWindow?.close(); - //openedWindow = undefined; - } - async function save() { try { await put('application/update', activeItem); @@ -232,14 +200,6 @@ console.log('info data', e); } } - - let drag = $state(true); - - const statusMapping: string = $derived( - (ApplicationStatusMaping[ - activeItem?.status as (typeof ApplicationStatus)[keyof typeof ApplicationStatus] - ] as string) ?? '' - );
@@ -260,7 +220,9 @@
{#if activeItem.status != 1}
- {statusMapping} + {(ApplicationStatusMaping[ + activeItem?.status as (typeof ApplicationStatus)[keyof typeof ApplicationStatus] + ] as string) ?? ''}
{/if} {#if showExtraData} @@ -278,15 +240,7 @@ {/if}
-
- - -
+
+
+ + +
@@ -316,7 +280,7 @@ />
{#if !activeItem.unique_url || showExtraData} -
+
Url
{activeItem.url} @@ -324,7 +288,7 @@
{/if} {#if activeItem.unique_url} -
+
Unique Url
{activeItem.unique_url} @@ -332,7 +296,7 @@
{/if} {#if activeItem.linked_application} -
+
Linked Application
{activeItem.linked_application} @@ -340,7 +304,7 @@
{/if} {#if activeItem.application_time} -
+
Application Time
{activeItem.application_time} @@ -372,7 +336,6 @@