lot of changes
This commit is contained in:
parent
dd047c0bcf
commit
26301d1a13
@ -2,6 +2,7 @@ spring.datasource.driver-class-name=org.postgresql.Driver
|
|||||||
spring.datasource.url=jdbc:postgresql://kronos.home:5432/applications
|
spring.datasource.url=jdbc:postgresql://kronos.home:5432/applications
|
||||||
spring.datasource.username=applications
|
spring.datasource.username=applications
|
||||||
spring.datasource.password=applications
|
spring.datasource.password=applications
|
||||||
|
spring-boot.run.jvmArguments=-Duser.timezone=UTC
|
||||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||||
|
|
||||||
# Disable the trace on the error responses
|
# Disable the trace on the error responses
|
||||||
|
@ -33,8 +33,10 @@ data class Application(
|
|||||||
var extra_data: String,
|
var extra_data: String,
|
||||||
var payrange: String,
|
var payrange: String,
|
||||||
var status: Int,
|
var status: Int,
|
||||||
|
var status_id: String?,
|
||||||
var company: String,
|
var company: String,
|
||||||
var recruiter: String,
|
var recruiter: String,
|
||||||
|
var agency: Boolean,
|
||||||
var message: String,
|
var message: String,
|
||||||
var linked_application: String,
|
var linked_application: String,
|
||||||
var status_history: String,
|
var status_history: String,
|
||||||
@ -56,8 +58,10 @@ data class Application(
|
|||||||
rs.getString("extra_data"),
|
rs.getString("extra_data"),
|
||||||
rs.getString("payrange"),
|
rs.getString("payrange"),
|
||||||
rs.getInt("status"),
|
rs.getInt("status"),
|
||||||
|
rs.getString("status_id"),
|
||||||
rs.getString("company"),
|
rs.getString("company"),
|
||||||
rs.getString("recruiter"),
|
rs.getString("recruiter"),
|
||||||
|
rs.getBoolean("agency"),
|
||||||
rs.getString("message"),
|
rs.getString("message"),
|
||||||
rs.getString("linked_application"),
|
rs.getString("linked_application"),
|
||||||
rs.getString("status_history"),
|
rs.getString("status_history"),
|
||||||
@ -85,6 +89,7 @@ data class CVData(
|
|||||||
val company: String,
|
val company: String,
|
||||||
val recruiter: String,
|
val recruiter: String,
|
||||||
val message: String,
|
val message: String,
|
||||||
|
val agency: Boolean,
|
||||||
val flairs: List<SimpleFlair>
|
val flairs: List<SimpleFlair>
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -113,7 +118,7 @@ class ApplicationsController(
|
|||||||
|
|
||||||
val flairs = application.flairs.map { it.toFlairSimple() }
|
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 */
|
/** Create a new application from the link */
|
||||||
@ -135,8 +140,10 @@ class ApplicationsController(
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
|
null,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
false,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
@ -263,8 +270,10 @@ class ApplicationsController(
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
|
null,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
false,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
@ -535,8 +544,9 @@ class ApplicationService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create time is auto created by the database
|
// Create time is auto created by the database
|
||||||
|
// The default status is null
|
||||||
db.update(
|
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.id,
|
||||||
application.url,
|
application.url,
|
||||||
application.original_url,
|
application.original_url,
|
||||||
@ -552,6 +562,7 @@ class ApplicationService(
|
|||||||
application.linked_application,
|
application.linked_application,
|
||||||
application.status_history,
|
application.status_history,
|
||||||
application.application_time,
|
application.application_time,
|
||||||
|
application.agency,
|
||||||
)
|
)
|
||||||
|
|
||||||
eventService.create(application.id, EventType.Creation)
|
eventService.create(application.id, EventType.Creation)
|
||||||
@ -595,7 +606,16 @@ class ApplicationService(
|
|||||||
return iter.toList()
|
return iter.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun findAllByUserStatusId(statusId: String, user: UserDb): List<Application> {
|
||||||
|
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
|
// Update the stauts on the application object before giving it to this function
|
||||||
|
// TODO how status history works
|
||||||
public fun updateStatus(application: Application): Application {
|
public fun updateStatus(application: Application): Application {
|
||||||
|
|
||||||
val status_string = "${application.status}"
|
val status_string = "${application.status}"
|
||||||
@ -627,7 +647,7 @@ class ApplicationService(
|
|||||||
public fun update(application: Application): Application {
|
public fun update(application: Application): Application {
|
||||||
// I don't want ot update create_time
|
// I don't want ot update create_time
|
||||||
db.update(
|
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.url,
|
||||||
application.original_url,
|
application.original_url,
|
||||||
application.unique_url,
|
application.unique_url,
|
||||||
@ -639,6 +659,7 @@ class ApplicationService(
|
|||||||
application.recruiter,
|
application.recruiter,
|
||||||
application.message,
|
application.message,
|
||||||
application.linked_application,
|
application.linked_application,
|
||||||
|
application.agency,
|
||||||
application.id,
|
application.id,
|
||||||
)
|
)
|
||||||
return application
|
return application
|
||||||
|
@ -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<UserStatusNode> {
|
||||||
|
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<UserStatusLink> {
|
||||||
|
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<UserStatusNode> {
|
||||||
|
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<UserStatusLink> {
|
||||||
|
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<UserStatusNode> {
|
||||||
|
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<UserStatusLink> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -23,16 +23,20 @@ create table if not exists applications (
|
|||||||
status_history text default '',
|
status_history text default '',
|
||||||
user_id text,
|
user_id text,
|
||||||
extra_data text,
|
extra_data text,
|
||||||
|
-- this status will be deprecated in favor of the node style status
|
||||||
status integer,
|
status integer,
|
||||||
|
status_id text default null,
|
||||||
linked_application text default '',
|
linked_application text default '',
|
||||||
application_time text default '',
|
application_time text default '',
|
||||||
|
agency boolean default false,
|
||||||
create_time timestamp default now()
|
create_time timestamp default now()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Views are deprecated will be removed in the future
|
||||||
create table if not exists views (
|
create table if not exists views (
|
||||||
id text primary key,
|
id text primary key,
|
||||||
application_id text not null,
|
application_id text not null,
|
||||||
time timestamp default current_timestamp
|
time timestamp default now()
|
||||||
);
|
);
|
||||||
|
|
||||||
create table if not exists flair (
|
create table if not exists flair (
|
||||||
@ -65,5 +69,41 @@ create table if not exists events (
|
|||||||
-- This only matters when event_type == 1
|
-- This only matters when event_type == 1
|
||||||
new_status integer,
|
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
|
||||||
);
|
);
|
||||||
|
5
package.json
Normal file
5
package.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap-icons": "^1.11.3"
|
||||||
|
}
|
||||||
|
}
|
22
pnpm-lock.yaml
Normal file
22
pnpm-lock.yaml
Normal file
@ -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: {}
|
@ -18,6 +18,7 @@
|
|||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"@types/eslint": "^8.56.7",
|
"@types/eslint": "^8.56.7",
|
||||||
|
"@types/node": "^22.8.7",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
@ -35,5 +36,8 @@
|
|||||||
"typescript-eslint": "^8.0.0-alpha.20",
|
"typescript-eslint": "^8.0.0-alpha.20",
|
||||||
"vite": "^5.0.3"
|
"vite": "^5.0.3"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap-icons": "1.11.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,25 +7,32 @@ settings:
|
|||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
|
dependencies:
|
||||||
|
bootstrap-icons:
|
||||||
|
specifier: 1.11.3
|
||||||
|
version: 1.11.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@sveltejs/adapter-auto':
|
'@sveltejs/adapter-auto':
|
||||||
specifier: ^3.0.0
|
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':
|
'@sveltejs/adapter-static':
|
||||||
specifier: ^3.0.5
|
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':
|
'@sveltejs/kit':
|
||||||
specifier: ^2.0.0
|
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':
|
'@sveltejs/vite-plugin-svelte':
|
||||||
specifier: ^3.0.0
|
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':
|
'@types/d3':
|
||||||
specifier: ^7.4.3
|
specifier: ^7.4.3
|
||||||
version: 7.4.3
|
version: 7.4.3
|
||||||
'@types/eslint':
|
'@types/eslint':
|
||||||
specifier: ^8.56.7
|
specifier: ^8.56.7
|
||||||
version: 8.56.10
|
version: 8.56.10
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.8.7
|
||||||
|
version: 22.8.7
|
||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.4.19
|
specifier: ^10.4.19
|
||||||
version: 10.4.19(postcss@8.4.39)
|
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)
|
version: 8.0.0-alpha.39(eslint@9.6.0)(typescript@5.5.3)
|
||||||
vite:
|
vite:
|
||||||
specifier: ^5.0.3
|
specifier: ^5.0.3
|
||||||
version: 5.3.3
|
version: 5.3.3(@types/node@22.8.7)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@ -520,6 +527,9 @@ packages:
|
|||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
|
'@types/node@22.8.7':
|
||||||
|
resolution: {integrity: sha512-LidcG+2UeYIWcMuMUpBKOnryBWG/rnmOHQR5apjn8myTQcx3rinFRn7DcIFhMnS0PPFSC6OafdIKEad0lj6U0Q==}
|
||||||
|
|
||||||
'@types/pug@2.0.10':
|
'@types/pug@2.0.10':
|
||||||
resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
|
resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
|
||||||
|
|
||||||
@ -651,6 +661,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
bootstrap-icons@1.11.3:
|
||||||
|
resolution: {integrity: sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==}
|
||||||
|
|
||||||
brace-expansion@1.1.11:
|
brace-expansion@1.1.11:
|
||||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||||
|
|
||||||
@ -1674,6 +1687,9 @@ packages:
|
|||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
undici-types@6.19.8:
|
||||||
|
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
|
||||||
|
|
||||||
update-browserslist-db@1.1.0:
|
update-browserslist-db@1.1.0:
|
||||||
resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==}
|
resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -1964,18 +1980,18 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc@4.18.0':
|
'@rollup/rollup-win32-x64-msvc@4.18.0':
|
||||||
optional: true
|
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:
|
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
|
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:
|
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:
|
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
|
'@types/cookie': 0.6.0
|
||||||
cookie: 0.6.0
|
cookie: 0.6.0
|
||||||
devalue: 5.0.0
|
devalue: 5.0.0
|
||||||
@ -1989,28 +2005,28 @@ snapshots:
|
|||||||
sirv: 2.0.4
|
sirv: 2.0.4
|
||||||
svelte: 5.0.0-next.174
|
svelte: 5.0.0-next.174
|
||||||
tiny-glob: 0.2.9
|
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:
|
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
|
debug: 4.3.5
|
||||||
svelte: 5.0.0-next.174
|
svelte: 5.0.0-next.174
|
||||||
vite: 5.3.3
|
vite: 5.3.3(@types/node@22.8.7)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- 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:
|
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
|
debug: 4.3.5
|
||||||
deepmerge: 4.3.1
|
deepmerge: 4.3.1
|
||||||
kleur: 4.1.5
|
kleur: 4.1.5
|
||||||
magic-string: 0.30.10
|
magic-string: 0.30.10
|
||||||
svelte: 5.0.0-next.174
|
svelte: 5.0.0-next.174
|
||||||
svelte-hmr: 0.16.0(svelte@5.0.0-next.174)
|
svelte-hmr: 0.16.0(svelte@5.0.0-next.174)
|
||||||
vite: 5.3.3
|
vite: 5.3.3(@types/node@22.8.7)
|
||||||
vitefu: 0.2.5(vite@5.3.3)
|
vitefu: 0.2.5(vite@5.3.3(@types/node@22.8.7))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -2144,6 +2160,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
|
'@types/node@22.8.7':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 6.19.8
|
||||||
|
|
||||||
'@types/pug@2.0.10': {}
|
'@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)':
|
'@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: {}
|
binary-extensions@2.3.0: {}
|
||||||
|
|
||||||
|
bootstrap-icons@1.11.3: {}
|
||||||
|
|
||||||
brace-expansion@1.1.11:
|
brace-expansion@1.1.11:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 1.0.2
|
balanced-match: 1.0.2
|
||||||
@ -3330,6 +3352,8 @@ snapshots:
|
|||||||
|
|
||||||
typescript@5.5.3: {}
|
typescript@5.5.3: {}
|
||||||
|
|
||||||
|
undici-types@6.19.8: {}
|
||||||
|
|
||||||
update-browserslist-db@1.1.0(browserslist@4.23.1):
|
update-browserslist-db@1.1.0(browserslist@4.23.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.23.1
|
browserslist: 4.23.1
|
||||||
@ -3342,17 +3366,18 @@ snapshots:
|
|||||||
|
|
||||||
util-deprecate@1.0.2: {}
|
util-deprecate@1.0.2: {}
|
||||||
|
|
||||||
vite@5.3.3:
|
vite@5.3.3(@types/node@22.8.7):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.21.5
|
esbuild: 0.21.5
|
||||||
postcss: 8.4.39
|
postcss: 8.4.39
|
||||||
rollup: 4.18.0
|
rollup: 4.18.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
'@types/node': 22.8.7
|
||||||
fsevents: 2.3.3
|
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:
|
optionalDependencies:
|
||||||
vite: 5.3.3
|
vite: 5.3.3(@types/node@22.8.7)
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -5,11 +5,6 @@
|
|||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
|
||||||
/>
|
|
||||||
|
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body class="grad-back" data-sveltekit-preload-data="hover">
|
<body class="grad-back" data-sveltekit-preload-data="hover">
|
||||||
|
@ -11,9 +11,11 @@ export const ApplicationStatus = Object.freeze({
|
|||||||
Applyed: 4,
|
Applyed: 4,
|
||||||
Expired: 5,
|
Expired: 5,
|
||||||
TasksToDo: 6,
|
TasksToDo: 6,
|
||||||
|
TasksToDo2: 10,
|
||||||
LinkedApplication: 7,
|
LinkedApplication: 7,
|
||||||
InterviewStep1: 8,
|
InterviewStep1: 8,
|
||||||
InterviewStep2: 9
|
InterviewStep2: 9,
|
||||||
|
FinalInterview: 11
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ApplicationStatusIconMaping: Record<AsEnum<typeof ApplicationStatus>, string> = Object.freeze({
|
export const ApplicationStatusIconMaping: Record<AsEnum<typeof ApplicationStatus>, string> = Object.freeze({
|
||||||
@ -24,9 +26,11 @@ export const ApplicationStatusIconMaping: Record<AsEnum<typeof ApplicationStatus
|
|||||||
4: 'send',
|
4: 'send',
|
||||||
5: 'hourglass-bottom',
|
5: 'hourglass-bottom',
|
||||||
6: 'list-check',
|
6: 'list-check',
|
||||||
|
10: 'list-check',
|
||||||
7: 'link-45deg',
|
7: 'link-45deg',
|
||||||
8: 'person',
|
8: 'person',
|
||||||
9: 'people'
|
9: 'people',
|
||||||
|
11: 'badge-vo-fill'
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ApplicationStatusMaping: Record<AsEnum<typeof ApplicationStatus>, string> = Object.freeze({
|
export const ApplicationStatusMaping: Record<AsEnum<typeof ApplicationStatus>, string> = Object.freeze({
|
||||||
@ -37,9 +41,11 @@ export const ApplicationStatusMaping: Record<AsEnum<typeof ApplicationStatus>, s
|
|||||||
4: 'Applyed',
|
4: 'Applyed',
|
||||||
5: 'Expired',
|
5: 'Expired',
|
||||||
6: 'Tasks To Do',
|
6: 'Tasks To Do',
|
||||||
|
10: 'Tasks To Do 2',
|
||||||
7: 'Linked Application',
|
7: 'Linked Application',
|
||||||
8: 'Interview 1',
|
8: 'Interview 1',
|
||||||
9: 'Interview 2'
|
9: 'Interview 2',
|
||||||
|
11: 'Final Interview'
|
||||||
});
|
});
|
||||||
|
|
||||||
export type View = {
|
export type View = {
|
||||||
@ -73,6 +79,7 @@ export type Application = {
|
|||||||
payrange: string;
|
payrange: string;
|
||||||
status: AsEnum<typeof ApplicationStatus>;
|
status: AsEnum<typeof ApplicationStatus>;
|
||||||
recruiter: string;
|
recruiter: string;
|
||||||
|
agency: boolean;
|
||||||
company: string;
|
company: string;
|
||||||
message: string;
|
message: string;
|
||||||
linked_application: string;
|
linked_application: string;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
|
import 'bootstrap-icons/font/bootstrap-icons.min.css'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -16,9 +16,18 @@
|
|||||||
<ApplicationsList />
|
<ApplicationsList />
|
||||||
<WorkArea />
|
<WorkArea />
|
||||||
</div>
|
</div>
|
||||||
|
<PApplicationList status={ApplicationStatus.FinalInterview}>
|
||||||
|
Interview Final
|
||||||
|
</PApplicationList >
|
||||||
|
<PApplicationList status={ApplicationStatus.InterviewStep2}>
|
||||||
|
Interview II
|
||||||
|
</PApplicationList >
|
||||||
<PApplicationList status={ApplicationStatus.InterviewStep1}>
|
<PApplicationList status={ApplicationStatus.InterviewStep1}>
|
||||||
Interview I
|
Interview I
|
||||||
</PApplicationList >
|
</PApplicationList >
|
||||||
|
<PApplicationList status={ApplicationStatus.TasksToDo2}>
|
||||||
|
Tasks To do 2
|
||||||
|
</PApplicationList >
|
||||||
<PApplicationList status={ApplicationStatus.TasksToDo}>
|
<PApplicationList status={ApplicationStatus.TasksToDo}>
|
||||||
Tasks To do
|
Tasks To do
|
||||||
</PApplicationList >
|
</PApplicationList >
|
||||||
|
@ -7,18 +7,9 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
applicationStore.loadApplications();
|
applicationStore.loadApplications();
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="w-2/12 card p-3 flex flex-col flex-shrink min-h-0">
|
let internal = $derived(
|
||||||
<h1>To Apply</h1>
|
applicationStore.applications.filter((i) => {
|
||||||
<div class="flex">
|
|
||||||
<input placeholder="Filter" class="p-2 flex-grow" bind:value={filter} />
|
|
||||||
<div>
|
|
||||||
{applicationStore.applications.length}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="overflow-auto flex-grow p-2">
|
|
||||||
{#each applicationStore.applications.filter((i) => {
|
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -31,7 +22,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return x.match(f);
|
return x.match(f);
|
||||||
}) as item}
|
})
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-2/12 card p-3 flex flex-col flex-shrink min-h-0">
|
||||||
|
<h1>To Apply</h1>
|
||||||
|
<div class="flex pb-2">
|
||||||
|
<input placeholder="Filter" class="p-2 flex-grow" bind:value={filter} />
|
||||||
|
<div>
|
||||||
|
{internal.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-auto flex-grow p-2">
|
||||||
|
{#each internal as item}
|
||||||
<div
|
<div
|
||||||
class="card p-2 my-2 bg-slate-100 max-w-full"
|
class="card p-2 my-2 bg-slate-100 max-w-full"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
@ -43,7 +47,10 @@
|
|||||||
}}
|
}}
|
||||||
role="none"
|
role="none"
|
||||||
>
|
>
|
||||||
<div class="max-w-full" class:animate-pulse={applicationStore.dragging?.id === item.id}>
|
<div
|
||||||
|
class="max-w-full"
|
||||||
|
class:animate-pulse={applicationStore.dragging?.id === item.id}
|
||||||
|
>
|
||||||
<h2 class="text-lg text-blue-500 flex gap-2 max-w-full overflow-hidden">
|
<h2 class="text-lg text-blue-500 flex gap-2 max-w-full overflow-hidden">
|
||||||
<div class="flex-grow max-w-[90%]">
|
<div class="flex-grow max-w-[90%]">
|
||||||
<div class="whitespace-nowrap overflow-hidden">
|
<div class="whitespace-nowrap overflow-hidden">
|
||||||
|
@ -10,11 +10,11 @@
|
|||||||
let filter = $state('');
|
let filter = $state('');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card p-3 rounded-lg flex flex-col">
|
<div class="card p-3 rounded-lg">
|
||||||
<h1 class="flex gap-2">
|
<h1 class="flex gap-2">
|
||||||
Applied <input bind:value={filter} placeholder="search" class="flex-grow text-blue-500" />
|
Applied <input bind:value={filter} placeholder="search" class="flex-grow text-blue-500" />
|
||||||
</h1>
|
</h1>
|
||||||
<div class="overflow-auto flex-grow">
|
<div class="flex flex-wrap gap-4 justify-between">
|
||||||
{#each applicationStore.applyed.filter((i) => {
|
{#each applicationStore.applyed.filter((i) => {
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
return true;
|
return true;
|
||||||
@ -30,7 +30,7 @@
|
|||||||
return x.match(f);
|
return x.match(f);
|
||||||
}) as item}
|
}) as item}
|
||||||
<button
|
<button
|
||||||
class="card p-2 my-2 bg-slate-100 w-full text-left"
|
class="card p-2 my-2 bg-slate-100 text-left"
|
||||||
onclick={async () => {
|
onclick={async () => {
|
||||||
item.views = await get(`view/${item.id}`);
|
item.views = await get(`view/${item.id}`);
|
||||||
item.events = await get(`events/${item.id}`);
|
item.events = await get(`events/${item.id}`);
|
||||||
@ -50,14 +50,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</h2>
|
</h2>
|
||||||
<a
|
|
||||||
href={item.url}
|
|
||||||
class="text-violet-600 overflow-hidden whitespace-nowrap block"
|
|
||||||
>
|
|
||||||
{item.url}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="min-h-[40px]">
|
||||||
|
</div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { userStore } from '$lib/UserStore.svelte';
|
import { userStore } from '$lib/UserStore.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-7">
|
<div class="p-7 pb-1">
|
||||||
<div class="card p-2 rounded-xl flex">
|
<div class="card p-2 rounded-xl flex">
|
||||||
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/')}> Home </button>
|
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/')}> Home </button>
|
||||||
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/submit')}>
|
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/submit')}>
|
||||||
@ -15,6 +15,9 @@
|
|||||||
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/graphs')}>
|
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/graphs')}>
|
||||||
Graphs
|
Graphs
|
||||||
</button>
|
</button>
|
||||||
|
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/flow')}>
|
||||||
|
Flow
|
||||||
|
</button>
|
||||||
<div class="flex-grow"></div>
|
<div class="flex-grow"></div>
|
||||||
<div class="text-secudanry px-2">
|
<div class="text-secudanry px-2">
|
||||||
{userStore.user.username}
|
{userStore.user.username}
|
||||||
|
@ -44,12 +44,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</h2>
|
</h2>
|
||||||
<a
|
|
||||||
href={item.url}
|
|
||||||
class="text-violet-600 overflow-hidden whitespace-nowrap block"
|
|
||||||
>
|
|
||||||
{item.url}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { userStore } from '$lib/UserStore.svelte';
|
import { userStore } from '$lib/UserStore.svelte';
|
||||||
import { get } from '$lib/utils';
|
import { get } from '$lib/utils';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import ApplicationsList from '../ApplicationsList.svelte';
|
||||||
|
|
||||||
let id: string | undefined | null = $state(undefined);
|
let id: string | undefined | null = $state(undefined);
|
||||||
|
|
||||||
@ -23,6 +24,7 @@
|
|||||||
recruiter: string;
|
recruiter: string;
|
||||||
message: string;
|
message: string;
|
||||||
company: string;
|
company: string;
|
||||||
|
agency: boolean,
|
||||||
flairs: SimpleFlair[];
|
flairs: SimpleFlair[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -127,6 +129,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if application}
|
{#if application}
|
||||||
|
{#if !application.agency}
|
||||||
<h2 class="text-white p-3 text-4xl">
|
<h2 class="text-white p-3 text-4xl">
|
||||||
👋 Hello
|
👋 Hello
|
||||||
{#if application.recruiter}
|
{#if application.recruiter}
|
||||||
@ -136,6 +139,7 @@
|
|||||||
recruiter @ <span class="font-bold">{application.company}</span>
|
recruiter @ <span class="font-bold">{application.company}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</h2>
|
</h2>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if application.message}
|
{#if application.message}
|
||||||
<div class="p-3 bg-white w-[190mm] rounded-lg">
|
<div class="p-3 bg-white w-[190mm] rounded-lg">
|
||||||
|
513
site/src/routes/flow/+page.svelte
Normal file
513
site/src/routes/flow/+page.svelte
Normal file
@ -0,0 +1,513 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import NavBar from '../NavBar.svelte';
|
||||||
|
import Link from './Link.svelte';
|
||||||
|
import NodeRect from './NodeRect.svelte';
|
||||||
|
import Rect from './Rect.svelte';
|
||||||
|
import type { Node, ActionType, LinkNode, FullLink, FullLinkApi } from './types';
|
||||||
|
import { extractLinkNodePosX, extractLinkNodePosY } from './types';
|
||||||
|
import { deleteR, get, post, put } from '$lib/utils';
|
||||||
|
import icon_list from './icons-list.json';
|
||||||
|
import IconPicker from './IconPicker.svelte';
|
||||||
|
|
||||||
|
// TODOS: well a lot of stuff but
|
||||||
|
// - API
|
||||||
|
// - Automaticaly move to select mode if one of the targets fals inside the bounds
|
||||||
|
|
||||||
|
//
|
||||||
|
// constatns
|
||||||
|
//
|
||||||
|
const grid_size = 35;
|
||||||
|
|
||||||
|
//
|
||||||
|
// binds
|
||||||
|
//
|
||||||
|
let activeNode: Node | undefined = $state();
|
||||||
|
let linkMode = $state(false);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Data
|
||||||
|
//
|
||||||
|
|
||||||
|
let nodes: Node[] = $state([]);
|
||||||
|
|
||||||
|
let links: FullLink[] = $state([]);
|
||||||
|
|
||||||
|
// Load Data
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
const nodesRequest = await get('user/status/node');
|
||||||
|
nodes = [
|
||||||
|
{
|
||||||
|
id: null as any,
|
||||||
|
user_id: null as any,
|
||||||
|
x: -4,
|
||||||
|
y: 10,
|
||||||
|
width: 8,
|
||||||
|
height: 4,
|
||||||
|
name: 'Created',
|
||||||
|
icon: 'plus',
|
||||||
|
permission: 1
|
||||||
|
} as Node,
|
||||||
|
...nodesRequest
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodesAsMap: Record<string, Node> = {};
|
||||||
|
for (const node of nodes) {
|
||||||
|
nodesAsMap[node.id] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkRequests: FullLinkApi[] = await get('user/status/link');
|
||||||
|
links = linkRequests.map((a) => ({
|
||||||
|
id: a.id,
|
||||||
|
bi: a.bi,
|
||||||
|
sourceNode: {
|
||||||
|
node: nodesAsMap[a.source_node as any],
|
||||||
|
x: a.source_x,
|
||||||
|
y: a.source_y
|
||||||
|
},
|
||||||
|
targetNode: {
|
||||||
|
node: nodesAsMap[a.target_node as any],
|
||||||
|
x: a.target_x,
|
||||||
|
y: a.target_y
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
console.log("test", linkRequests)
|
||||||
|
} catch (e) {
|
||||||
|
console.log('TODO inform user', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Display stuff
|
||||||
|
//
|
||||||
|
|
||||||
|
// The canvas is not really a canvas but a div will kinda act like a canvas
|
||||||
|
let canvas: HTMLDivElement | undefined = $state();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Position + movement + interation
|
||||||
|
//
|
||||||
|
//
|
||||||
|
let y = $state(0);
|
||||||
|
let x = $state(0);
|
||||||
|
|
||||||
|
let initialGridOffsetX = $state(0);
|
||||||
|
let initialGridOffsetY = $state(0);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
let box = canvas?.getBoundingClientRect();
|
||||||
|
if (!box) return;
|
||||||
|
let w = box.width;
|
||||||
|
let h = box.height;
|
||||||
|
initialGridOffsetX = Math.round(w / 2 - Math.round(w / 2 / grid_size) * grid_size);
|
||||||
|
initialGridOffsetY = Math.round(h / 2 - Math.floor(h / 2 / grid_size) * grid_size);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Holdes the position for pan and select
|
||||||
|
let startMX = $state(0);
|
||||||
|
let startMY = $state(0);
|
||||||
|
let startX = $state(0);
|
||||||
|
let startY = $state(0);
|
||||||
|
let mouseAction: ActionType = $state(undefined);
|
||||||
|
|
||||||
|
let tempMouseAction: ActionType = $state(undefined);
|
||||||
|
|
||||||
|
let curPosX = $state(0);
|
||||||
|
let curPosY = $state(0);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Mouse interation
|
||||||
|
//
|
||||||
|
// Right click and drag to move around
|
||||||
|
function onmousedown(e: MouseEvent) {
|
||||||
|
// clear some varibales
|
||||||
|
activeNode = undefined;
|
||||||
|
|
||||||
|
let box = canvas?.getBoundingClientRect();
|
||||||
|
const mouseX = e.x - (box?.left ?? 0);
|
||||||
|
const mouseY = e.y - (box?.top ?? 0);
|
||||||
|
const wy = worldY(mouseY);
|
||||||
|
const wx = worldX(mouseX);
|
||||||
|
|
||||||
|
startMX = mouseX;
|
||||||
|
startMY = mouseY;
|
||||||
|
if (e.button === 2 || e.button === 1) {
|
||||||
|
tempMouseAction = mouseAction;
|
||||||
|
mouseAction = 'drag';
|
||||||
|
startX = x;
|
||||||
|
startY = y;
|
||||||
|
} else if (e.button === 0) {
|
||||||
|
if (linkMode) return;
|
||||||
|
// if inside a box allow clicks
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (
|
||||||
|
wx >= node.x &&
|
||||||
|
wx <= node.x + node.width &&
|
||||||
|
wy <= node.y &&
|
||||||
|
wy >= node.y - node.height
|
||||||
|
) {
|
||||||
|
if (!linkMode && node.permission === 0) {
|
||||||
|
activeNode = node;
|
||||||
|
startX = x;
|
||||||
|
startY = y;
|
||||||
|
startMX = node.x - wx;
|
||||||
|
startMY = node.y - wy;
|
||||||
|
mouseAction = 'move';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseAction = 'create';
|
||||||
|
curPosY = canvasY(Math.ceil(wy));
|
||||||
|
curPosX = canvasX(Math.floor(wx));
|
||||||
|
startMX = curPosX;
|
||||||
|
startMY = curPosY;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onmouseup(e: MouseEvent) {
|
||||||
|
//
|
||||||
|
// Create
|
||||||
|
//
|
||||||
|
|
||||||
|
// Maybe don't do this it's super bad
|
||||||
|
while (mouseAction === 'create') {
|
||||||
|
let width = Math.abs(startMX - curPosX) / grid_size;
|
||||||
|
let height = Math.abs(startMY - curPosY) / grid_size;
|
||||||
|
if (width <= 8 || height <= 4) break;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await post('user/status/node', {
|
||||||
|
id: '',
|
||||||
|
user_id: '',
|
||||||
|
x: worldX(Math.min(startMX, curPosX)),
|
||||||
|
y: worldY(Math.min(startMY, curPosY)),
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
icon: icon_list[Math.floor(Math.random() * icon_list.length)],
|
||||||
|
name: 'New Status',
|
||||||
|
permission: 0
|
||||||
|
} as Node);
|
||||||
|
nodes.push(result);
|
||||||
|
// Tell svelte that nodes is updated
|
||||||
|
nodes = nodes;
|
||||||
|
} catch (e) {
|
||||||
|
console.log('TODO inform user', e);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Move
|
||||||
|
//
|
||||||
|
if (mouseAction === 'move' && activeNode) {
|
||||||
|
try {
|
||||||
|
await put('user/status/node', activeNode);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('TODO: inform user', e);
|
||||||
|
}
|
||||||
|
activeNode = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Ohter
|
||||||
|
//
|
||||||
|
if (mouseAction === 'drag' && tempMouseAction === 'link' && startX === x && startY === y) {
|
||||||
|
mouseAction = undefined;
|
||||||
|
tempMouseAction = undefined;
|
||||||
|
linkSource = undefined;
|
||||||
|
startX = 0;
|
||||||
|
startY = 0;
|
||||||
|
startMX = 0;
|
||||||
|
startMY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseAction !== 'link' && mouseAction) {
|
||||||
|
mouseAction = tempMouseAction;
|
||||||
|
tempMouseAction = undefined;
|
||||||
|
startX = 0;
|
||||||
|
startY = 0;
|
||||||
|
startMX = 0;
|
||||||
|
startMY = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the mouse leaves the area clear all the stuffs
|
||||||
|
function onmouseleave(e: MouseEvent) {
|
||||||
|
if (mouseAction !== 'link') {
|
||||||
|
mouseAction = undefined;
|
||||||
|
startX = 0;
|
||||||
|
startY = 0;
|
||||||
|
startMX = 0;
|
||||||
|
startMY = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmousemove(e: MouseEvent) {
|
||||||
|
let box = canvas?.getBoundingClientRect();
|
||||||
|
let mouseX = e.x - (box?.left ?? 0);
|
||||||
|
let mouseY = e.y - (box?.top ?? 0);
|
||||||
|
if (mouseAction === 'drag') {
|
||||||
|
x = startX + (startMX - mouseX);
|
||||||
|
y = startY + (startMY - mouseY);
|
||||||
|
} else if (mouseAction === 'create') {
|
||||||
|
const wy = worldY(mouseY);
|
||||||
|
const wx = worldX(mouseX);
|
||||||
|
curPosY = canvasY(Math.floor(wy));
|
||||||
|
curPosX = canvasX(Math.ceil(wx));
|
||||||
|
} else if (mouseAction === 'move' && activeNode) {
|
||||||
|
const wy = worldY(mouseY);
|
||||||
|
const wx = worldX(mouseX);
|
||||||
|
activeNode.x = Math.floor(wx + startMX);
|
||||||
|
activeNode.y = Math.ceil(wy + startMY);
|
||||||
|
}
|
||||||
|
if (mouseAction === 'link' || tempMouseAction === 'link') {
|
||||||
|
curPosX = mouseX;
|
||||||
|
curPosY = mouseY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For now disable the right click menu
|
||||||
|
*/
|
||||||
|
function oncontextmenu(e: MouseEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Link stuff
|
||||||
|
*/
|
||||||
|
let linkSource: LinkNode | undefined = $state();
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Utils
|
||||||
|
//
|
||||||
|
//
|
||||||
|
function canvasX(inX: number) {
|
||||||
|
let box = canvas?.getBoundingClientRect();
|
||||||
|
return inX * grid_size + Math.round((box?.width ?? 0) / 2) - x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canvasY(inY: number) {
|
||||||
|
let box = canvas?.getBoundingClientRect();
|
||||||
|
return Math.round((box?.height ?? 0) / 2) - y - inY * grid_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
function worldX(canvasX: number) {
|
||||||
|
let box = canvas?.getBoundingClientRect();
|
||||||
|
return (canvasX + x - Math.round((box?.width ?? 0) / 2)) / grid_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
function worldY(canvasY: number) {
|
||||||
|
let box = canvas?.getBoundingClientRect();
|
||||||
|
return (Math.round((box?.height ?? 0) / 2) - y - canvasY) / grid_size;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col h-[100vh]">
|
||||||
|
<NavBar />
|
||||||
|
<div class="w-full flex-grow p-5 px-7">
|
||||||
|
<div
|
||||||
|
class="bg-white rounded-lg w-full h-full grid relative overflow-hidden"
|
||||||
|
style="--grid-size: {grid_size}px; cursor: {mouseAction === 'move' ? 'grab' : 'auto'};"
|
||||||
|
role="none"
|
||||||
|
bind:this={canvas}
|
||||||
|
{onmousedown}
|
||||||
|
{onmouseup}
|
||||||
|
{oncontextmenu}
|
||||||
|
{onmousemove}
|
||||||
|
{onmouseleave}
|
||||||
|
>
|
||||||
|
<!-- Background grid -->
|
||||||
|
<!-- x -->
|
||||||
|
<div class="absolute background" style="--offset-y: {-y + initialGridOffsetY}px;"></div>
|
||||||
|
<!-- y -->
|
||||||
|
<div
|
||||||
|
class="absolute background"
|
||||||
|
style="--grid-angle: 90deg; --offset-x: {-x + initialGridOffsetX}px;"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<!-- Control buttons -->
|
||||||
|
<div class="absolute left-5 top-5 flex gap-5 z-50">
|
||||||
|
<!-- Reset view port -->
|
||||||
|
{#if x !== 0 || y !== 0}
|
||||||
|
<button
|
||||||
|
class="bg-white p-2 shadow rounded-lg shadow-violet-500 h-[40px] w-[40px] text-violet-500"
|
||||||
|
onclick={() => {
|
||||||
|
x = 0;
|
||||||
|
y = 0;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="bi bi-arrow-clockwise"></span>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Enable link mode -->
|
||||||
|
<button
|
||||||
|
class="p-2 shadow rounded-lg shadow-violet-500 h-[40px] w-[40px] text-violet-500 {linkMode
|
||||||
|
? 'bg-blue-100'
|
||||||
|
: 'bg-white'}"
|
||||||
|
onclick={() => {
|
||||||
|
linkMode = !linkMode;
|
||||||
|
linkSource = undefined;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="bi bi-link-45deg"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
-
|
||||||
|
- Links
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Create link -->
|
||||||
|
{#if linkSource}
|
||||||
|
<Link
|
||||||
|
x1={extractLinkNodePosX(canvasX, linkSource, grid_size)}
|
||||||
|
y1={extractLinkNodePosY(canvasY, linkSource, grid_size)}
|
||||||
|
x2={curPosX}
|
||||||
|
y2={curPosY}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Existing nodes -->
|
||||||
|
{#each links as _, i}
|
||||||
|
{@const sourceN = links[i].sourceNode}
|
||||||
|
{@const targetN = links[i].targetNode}
|
||||||
|
<Link
|
||||||
|
x1={extractLinkNodePosX(canvasX, sourceN, grid_size)}
|
||||||
|
y1={extractLinkNodePosY(canvasY, sourceN, grid_size)}
|
||||||
|
x2={extractLinkNodePosX(canvasX, targetN, grid_size)}
|
||||||
|
y2={extractLinkNodePosY(canvasY, targetN, grid_size)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<!--
|
||||||
|
-
|
||||||
|
- Nodes
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Create rectangle -->
|
||||||
|
{#if mouseAction === 'create'}
|
||||||
|
<Rect x1={startMX} y1={startMY} x2={curPosX} y2={curPosY}>
|
||||||
|
{#if Math.abs(startMX - curPosX) / grid_size <= 8 || Math.abs(startMY - curPosY) / grid_size <= 4}
|
||||||
|
<div class="bg-red-400 bg-opacity-40 rounded-lg w-full h-full"></div>
|
||||||
|
{:else}
|
||||||
|
<div class="bg-green-400 bg-opacity-40 rounded-lg w-full h-full"></div>
|
||||||
|
{/if}
|
||||||
|
</Rect>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Time to do some fun shit here -->
|
||||||
|
{#each nodes as _, i}
|
||||||
|
<NodeRect
|
||||||
|
bind:node={nodes[i]}
|
||||||
|
{canvasX}
|
||||||
|
{canvasY}
|
||||||
|
{grid_size}
|
||||||
|
{mouseAction}
|
||||||
|
{linkMode}
|
||||||
|
linkSource={nodes[i] === linkSource?.node ? linkSource : undefined}
|
||||||
|
onremove={async () => {
|
||||||
|
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}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--grid-color: #c1c1c1;
|
||||||
|
--grid-size: 35px;
|
||||||
|
--border-size: 1px;
|
||||||
|
--grid-angle: 0deg;
|
||||||
|
--offset-x: 0px;
|
||||||
|
--offset-y: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: -1px;
|
||||||
|
bottom: -1px;
|
||||||
|
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
var(--grid-angle),
|
||||||
|
transparent,
|
||||||
|
transparent calc(var(--grid-size) - var(--border-size)),
|
||||||
|
var(--grid-color) calc(var(--grid-size) - var(--border-size)),
|
||||||
|
var(--grid-color) var(--grid-size)
|
||||||
|
);
|
||||||
|
|
||||||
|
background-position: var(--offset-x) var(--offset-y);
|
||||||
|
background-size: var(--grid-size) var(--grid-size);
|
||||||
|
}
|
||||||
|
</style>
|
33
site/src/routes/flow/IconPicker.svelte
Normal file
33
site/src/routes/flow/IconPicker.svelte
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import icon_list from './icons-list.json';
|
||||||
|
|
||||||
|
let { dialog = $bindable(), onselect }: { dialog?: HTMLDialogElement, onselect?: (text: string) => void } = $props();
|
||||||
|
|
||||||
|
let filter = $state('');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<dialog bind:this={dialog}>
|
||||||
|
<div class="bg-white rounded-lg">
|
||||||
|
<div>
|
||||||
|
<input class="finput" bind:value={filter} />
|
||||||
|
</div>
|
||||||
|
<div class="w-[60vw] max-w-[60vw] h-[60vh] max-h-[60vh]">
|
||||||
|
<div class="grid grid-flow-row p-3 gap-2 w-[60vw] max-w-[60vw]" style="grid-template-columns: repeat(auto-fit, minmax(62px, 1fr))">
|
||||||
|
{#each filter ? icon_list.filter((a) => {
|
||||||
|
return a.match(new RegExp(filter, 'ig'));
|
||||||
|
}) : icon_list as icon}
|
||||||
|
<button
|
||||||
|
class="text-violet-500 p-4 text-3xl shadow rounded-lg leading-none min-h-0 w-[62px] h-[62px]"
|
||||||
|
onclick={() => {
|
||||||
|
onselect?.(icon);
|
||||||
|
dialog?.close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="bi bi-{icon}"> </span>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="p-4 w-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
78
site/src/routes/flow/Link.svelte
Normal file
78
site/src/routes/flow/Link.svelte
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let {
|
||||||
|
x1,
|
||||||
|
x2,
|
||||||
|
y1,
|
||||||
|
y2
|
||||||
|
}: {
|
||||||
|
x1?: number;
|
||||||
|
x2?: number;
|
||||||
|
y1?: number;
|
||||||
|
y2?: number;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
let x = $state(0);
|
||||||
|
let y = $state(0);
|
||||||
|
let width = $state(0);
|
||||||
|
let height = $state(0);
|
||||||
|
|
||||||
|
let path = $state('');
|
||||||
|
|
||||||
|
const padding = 20;
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (x1 !== undefined && x2 !== undefined && y1 !== undefined && y2 !== undefined) {
|
||||||
|
const xMin = Math.min(x1, x2);
|
||||||
|
const yMin = Math.min(y1, y2);
|
||||||
|
|
||||||
|
x = xMin;
|
||||||
|
width = Math.abs(x1 - x2);
|
||||||
|
y = yMin;
|
||||||
|
height = Math.abs(y1 - y2);
|
||||||
|
|
||||||
|
const w = width;
|
||||||
|
const h = height;
|
||||||
|
const p = padding;
|
||||||
|
|
||||||
|
// Start on bottom left conner
|
||||||
|
if (xMin !== x1 && yMin === y1) {
|
||||||
|
path = `M ${p} ${h + p} Q ${w * 0.1 + p} ${h * 0.4 + p}, ${w / 2 + p} ${h / 2 + p} T ${w + p} ${p}`;
|
||||||
|
|
||||||
|
// Start on bottom right conner
|
||||||
|
} else if (xMin === x1 && yMin === y1) {
|
||||||
|
path = `M ${w + p} ${h + p} Q ${w * 0.9 + p} ${h * 0.4 + p}, ${w / 2 + p} ${h / 2 + p} T ${p} ${p}`;
|
||||||
|
|
||||||
|
// Start on top left conner
|
||||||
|
} else if (xMin !== x1 && yMin !== y1) {
|
||||||
|
path = `M ${p} ${p} Q ${w * 0.1 + p} ${h * 0.6 + p}, ${w / 2 + p} ${h / 2 + p} T ${w + p} ${h + p}`;
|
||||||
|
|
||||||
|
// Start on top right conner
|
||||||
|
} else if (xMin === x1 && yMin !== y1) {
|
||||||
|
path = `M ${w + p} ${p} Q ${w * 0.9 + p} ${h * 0.6 + p}, ${w / 2 + p} ${h / 2 + p} T ${p} ${h + p}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 {width + padding * 2} {height + padding * 2}"
|
||||||
|
class="absolute"
|
||||||
|
width={width + padding * 2}
|
||||||
|
height={height + padding * 2}
|
||||||
|
style="width: {width + padding * 2}px; height: {height + padding * 2}px; top: {y -
|
||||||
|
padding}px; left: {x - padding}px"
|
||||||
|
>
|
||||||
|
<marker
|
||||||
|
id="arrow"
|
||||||
|
viewBox="0 0 10 10"
|
||||||
|
refX="5"
|
||||||
|
refY="5"
|
||||||
|
markerWidth="6"
|
||||||
|
markerHeight="6"
|
||||||
|
orient="auto-start-reverse"
|
||||||
|
>
|
||||||
|
<path d="M 0 0 L 10 5 L 0 10 z" />
|
||||||
|
</marker>
|
||||||
|
|
||||||
|
<path d={path} stroke="black" stroke-width="2" fill="transparent" marker-start="url(#arrow)" />
|
||||||
|
</svg>
|
162
site/src/routes/flow/NodeRect.svelte
Normal file
162
site/src/routes/flow/NodeRect.svelte
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { put } from '$lib/utils';
|
||||||
|
import IconPicker from './IconPicker.svelte';
|
||||||
|
import Rect from './Rect.svelte';
|
||||||
|
import type { Node, ActionType, LinkNode } from './types';
|
||||||
|
|
||||||
|
//
|
||||||
|
// consts
|
||||||
|
//
|
||||||
|
const connectionPointSize = 10;
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
let hover = $state(false);
|
||||||
|
let iconSelector: HTMLDialogElement;
|
||||||
|
|
||||||
|
let {
|
||||||
|
node = $bindable(),
|
||||||
|
grid_size,
|
||||||
|
canvasX,
|
||||||
|
canvasY,
|
||||||
|
mouseAction,
|
||||||
|
onremove,
|
||||||
|
linkMode,
|
||||||
|
onNodePointClick,
|
||||||
|
linkSource
|
||||||
|
}: {
|
||||||
|
node: Node;
|
||||||
|
grid_size: number;
|
||||||
|
canvasX: (x: number) => number;
|
||||||
|
canvasY: (y: number) => number;
|
||||||
|
mouseAction: ActionType;
|
||||||
|
onremove: () => void;
|
||||||
|
linkMode: boolean;
|
||||||
|
onNodePointClick: (x: number, y: number) => void;
|
||||||
|
linkSource: LinkNode | undefined;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
let cx = $derived(canvasX(node.x));
|
||||||
|
let cy = $derived(canvasY(node.y));
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
try {
|
||||||
|
const r = await put('user/status/node', node);
|
||||||
|
node = r;
|
||||||
|
} catch (e) {
|
||||||
|
console.log('TODO inform the user', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Rect x={cx - 1} y={cy - 1} height={node.height * grid_size + 1} width={node.width * grid_size + 1}>
|
||||||
|
<div
|
||||||
|
class="bg-white shadow-lg border-slate-300 border-solid border rounded-lg w-full h-full flex flex-col justify-center items-center gap-2 relative"
|
||||||
|
role="none"
|
||||||
|
onmouseenter={() => {
|
||||||
|
if (mouseAction === undefined || mouseAction === 'link') {
|
||||||
|
hover = true;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onmouseleave={() => {
|
||||||
|
if (hover) {
|
||||||
|
hover = false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
disabled={node.permission !== 0}
|
||||||
|
onclick={() => {
|
||||||
|
iconSelector.showModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1>
|
||||||
|
{#if node.icon}
|
||||||
|
<span class="bi bi-{node.icon}"></span>
|
||||||
|
{:else}
|
||||||
|
Select an icon
|
||||||
|
{/if}
|
||||||
|
</h1>
|
||||||
|
</button>
|
||||||
|
<div class="p-2">
|
||||||
|
{#if node.permission === 0}
|
||||||
|
<input bind:value={node.name} class="finput text-center" onblur={save} />
|
||||||
|
{:else}
|
||||||
|
{node.name}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if node.permission === 0 && !linkMode}
|
||||||
|
<button
|
||||||
|
class="bg-red-400 hover:bg-red-800 -top-[10px] -right-[10px] rounded-full h-[20px] w-[20px] absolute text-center text-white leading-none"
|
||||||
|
onclick={onremove}
|
||||||
|
>
|
||||||
|
<div class="bi bi-x mt-[2px]"></div>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Node connections ponints
|
||||||
|
-->
|
||||||
|
{#if hover && linkMode}
|
||||||
|
<!-- Top -->
|
||||||
|
{#each { length: node.width } as _, i}
|
||||||
|
{@const adjust = grid_size / 2 + grid_size * i - connectionPointSize / 2}
|
||||||
|
<button
|
||||||
|
class="{linkSource?.x === i + 1 && linkSource?.y === 0
|
||||||
|
? 'bg-red-200 hover:bg-red-800'
|
||||||
|
: 'bg-blue-200 hover:bg-blue-800'} rounded-full absolute"
|
||||||
|
style="top: 0px; left: {adjust}px; width: {connectionPointSize}px; height: {connectionPointSize}px;"
|
||||||
|
onclick={() => onNodePointClick(i, -1)}
|
||||||
|
></button>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<!-- Bottom -->
|
||||||
|
{#each { length: node.width } as _, i}
|
||||||
|
{@const adjust = grid_size / 2 + grid_size * i - connectionPointSize / 2}
|
||||||
|
<button
|
||||||
|
class="{linkSource?.x === i + 1 && linkSource?.y === node.height
|
||||||
|
? 'bg-red-200 hover:bg-red-800'
|
||||||
|
: 'bg-blue-200 hover:bg-blue-800'} rounded-full absolute"
|
||||||
|
style="bottom: 0px; left: {adjust}px; width: {connectionPointSize}px; height: {connectionPointSize}px;"
|
||||||
|
onclick={() => onNodePointClick(i, node.height)}
|
||||||
|
></button>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<!-- Left -->
|
||||||
|
{#each { length: node.height } as _, i}
|
||||||
|
{@const adjust = grid_size / 2 + grid_size * i - connectionPointSize / 2}
|
||||||
|
<button
|
||||||
|
class="{linkSource?.x === 0 && linkSource?.y === i + 1
|
||||||
|
? 'bg-red-200 hover:bg-red-800'
|
||||||
|
: 'bg-blue-200 hover:bg-blue-800'} rounded-full absolute"
|
||||||
|
style="left: 0px; top: {adjust}px; width: {connectionPointSize}px; height: {connectionPointSize}px;"
|
||||||
|
onclick={() => onNodePointClick(-1, i)}
|
||||||
|
></button>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<!-- Right -->
|
||||||
|
{#each { length: node.height } as _, i}
|
||||||
|
{@const adjust = grid_size / 2 + grid_size * i - connectionPointSize / 2}
|
||||||
|
<button
|
||||||
|
class="{linkSource?.x === node.width && linkSource?.y === i + 1
|
||||||
|
? 'bg-red-200 hover:bg-red-800'
|
||||||
|
: 'bg-blue-200 hover:bg-blue-800'} rounded-full absolute"
|
||||||
|
style="right: 0px; top: {adjust}px; width: {connectionPointSize}px; height: {connectionPointSize}px;"
|
||||||
|
onclick={() => onNodePointClick(node.width, i)}
|
||||||
|
></button>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</Rect>
|
||||||
|
|
||||||
|
<IconPicker
|
||||||
|
bind:dialog={iconSelector}
|
||||||
|
onselect={(text) => {
|
||||||
|
node.icon = text;
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
save();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
41
site/src/routes/flow/Rect.svelte
Normal file
41
site/src/routes/flow/Rect.svelte
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
let {
|
||||||
|
x1,
|
||||||
|
x2,
|
||||||
|
y1,
|
||||||
|
y2,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
children
|
||||||
|
}: {
|
||||||
|
x1?: number;
|
||||||
|
x2?: number;
|
||||||
|
y1?: number;
|
||||||
|
y2?: number;
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
children: Snippet;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (x1 !== undefined && x2 !== undefined && y1 !== undefined && y2 !== undefined) {
|
||||||
|
const xMin = Math.min(x1, x2);
|
||||||
|
const yMin = Math.min(y1, y2);
|
||||||
|
|
||||||
|
x = xMin;
|
||||||
|
width = Math.abs(x1 - x2);
|
||||||
|
y = yMin;
|
||||||
|
height = Math.abs(y1 - y2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="absolute" style="left: {x}px; top: {y}px; width: {width}px; height: {height}px;">
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
5
site/src/routes/flow/generate-list-json.ts
Normal file
5
site/src/routes/flow/generate-list-json.ts
Normal file
@ -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))
|
1
site/src/routes/flow/icons-list.json
Normal file
1
site/src/routes/flow/icons-list.json
Normal file
File diff suppressed because one or more lines are too long
60
site/src/routes/flow/types.ts
Normal file
60
site/src/routes/flow/types.ts
Normal file
@ -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;
|
||||||
|
}
|
@ -16,6 +16,8 @@
|
|||||||
let showLinked = $state(false);
|
let showLinked = $state(false);
|
||||||
let showToApply = $state(false);
|
let showToApply = $state(false);
|
||||||
|
|
||||||
|
let totalPercetange = $state(true);
|
||||||
|
|
||||||
// Handle the graph creation
|
// Handle the graph creation
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!chartDiv || applications.length == 0) return;
|
if (!chartDiv || applications.length == 0) return;
|
||||||
@ -46,6 +48,8 @@
|
|||||||
Linkedin: 0
|
Linkedin: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
applications.forEach((a) => {
|
applications.forEach((a) => {
|
||||||
let source: NodeType;
|
let source: NodeType;
|
||||||
|
|
||||||
@ -65,6 +69,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
|
||||||
if (a.url.includes('linkedin')) {
|
if (a.url.includes('linkedin')) {
|
||||||
source = 'Linkedin';
|
source = 'Linkedin';
|
||||||
sourceData['Linkedin'] += 1;
|
sourceData['Linkedin'] += 1;
|
||||||
@ -106,6 +112,12 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (history.includes(`${ApplicationStatus.TasksToDo2}`)) {
|
||||||
|
source = addGraph(source, ApplicationStatus.TasksToDo2);
|
||||||
|
if (a.status == ApplicationStatus.TasksToDo2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (history.includes(`${ApplicationStatus.InterviewStep1}`)) {
|
if (history.includes(`${ApplicationStatus.InterviewStep1}`)) {
|
||||||
source = addGraph(source, ApplicationStatus.InterviewStep1);
|
source = addGraph(source, ApplicationStatus.InterviewStep1);
|
||||||
if (a.status == ApplicationStatus.InterviewStep1) {
|
if (a.status == ApplicationStatus.InterviewStep1) {
|
||||||
@ -118,6 +130,12 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (history.includes(`${ApplicationStatus.FinalInterview}`)) {
|
||||||
|
source = addGraph(source, ApplicationStatus.FinalInterview);
|
||||||
|
if (a.status == ApplicationStatus.FinalInterview) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
addGraph(source, ApplicationStatus.ApplyedButSaidNo);
|
addGraph(source, ApplicationStatus.ApplyedButSaidNo);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -154,7 +172,9 @@
|
|||||||
originalValue: node,
|
originalValue: node,
|
||||||
id: name,
|
id: name,
|
||||||
index: i,
|
index: i,
|
||||||
percentage: Math.trunc((value / applications.length) * 100)
|
percentage: Math.trunc(
|
||||||
|
(value / (totalPercetange ? applications.length : count)) * 100
|
||||||
|
)
|
||||||
};
|
};
|
||||||
return base;
|
return base;
|
||||||
});
|
});
|
||||||
@ -202,7 +222,7 @@
|
|||||||
// let color = d3.interpolateTurbo(nodes.length);
|
// let color = d3.interpolateTurbo(nodes.length);
|
||||||
|
|
||||||
function getColor(index: number) {
|
function getColor(index: number) {
|
||||||
return d3.interpolateRainbow(index/nodes.length);
|
return d3.interpolateRainbow(index / nodes.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add in the links
|
// add in the links
|
||||||
@ -221,10 +241,12 @@
|
|||||||
.attr('class', 'link')
|
.attr('class', 'link')
|
||||||
.attr('d', path)
|
.attr('d', path)
|
||||||
.style('stroke', function (d) {
|
.style('stroke', function (d) {
|
||||||
return d3.rgb(
|
return d3
|
||||||
|
.rgb(
|
||||||
getColor(d.source.index)
|
getColor(d.source.index)
|
||||||
// color[d.source.index]
|
// color[d.source.index]
|
||||||
).toString();
|
)
|
||||||
|
.toString();
|
||||||
})
|
})
|
||||||
.style('stroke-width', function (d) {
|
.style('stroke-width', function (d) {
|
||||||
return Math.max(1, d.dy);
|
return Math.max(1, d.dy);
|
||||||
@ -267,10 +289,13 @@
|
|||||||
//color[d.index];
|
//color[d.index];
|
||||||
})
|
})
|
||||||
.style('stroke', (d) => {
|
.style('stroke', (d) => {
|
||||||
return d3.rgb(
|
return d3
|
||||||
|
.rgb(
|
||||||
getColor(d.index)
|
getColor(d.index)
|
||||||
//color[d.index]
|
//color[d.index]
|
||||||
).darker(2).toString();
|
)
|
||||||
|
.darker(2)
|
||||||
|
.toString();
|
||||||
})
|
})
|
||||||
.append('title')
|
.append('title')
|
||||||
.text(function (d) {
|
.text(function (d) {
|
||||||
@ -329,6 +354,10 @@
|
|||||||
<label for="showToApply">Show To Apply</label>
|
<label for="showToApply">Show To Apply</label>
|
||||||
<input id="showToApply" type="checkbox" bind:checked={showToApply} />
|
<input id="showToApply" type="checkbox" bind:checked={showToApply} />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="totalPercetange">Total percentage</label>
|
||||||
|
<input id="totalPercetange" type="checkbox" bind:checked={totalPercetange} />
|
||||||
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-grow flex-col p-5">
|
<div class="flex flex-grow flex-col p-5">
|
||||||
|
28
site/src/routes/work-area/CompanyField.svelte
Normal file
28
site/src/routes/work-area/CompanyField.svelte
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { applicationStore } from '$lib/ApplicationsStore.svelte';
|
||||||
|
|
||||||
|
let { save, company = $bindable() }: { company: string; save: () => void } = $props();
|
||||||
|
|
||||||
|
let companies = $derived(new Set(applicationStore.all.map((a) => a.company)));
|
||||||
|
|
||||||
|
let fcomps = $derived(
|
||||||
|
company === ''
|
||||||
|
? []
|
||||||
|
: [...companies.values()].filter((a) => {
|
||||||
|
// TODO improve this a lot I want to make like matching algo
|
||||||
|
return a.match(company) && a !== company;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<fieldset class="grow">
|
||||||
|
<label class="flabel" for="title">Company</label>
|
||||||
|
<input class="finput" id="title" bind:value={company} onchange={save} />
|
||||||
|
<div class="flex gap-2 flex-wrap">
|
||||||
|
{#each fcomps as comp}
|
||||||
|
<button class="bg-blue-200 px-3 p-1 min-h-0 rounded-lg" onclick={() => company = comp}>
|
||||||
|
{comp}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
198
site/src/routes/work-area/DropingZone.svelte
Normal file
198
site/src/routes/work-area/DropingZone.svelte
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
ApplicationStatus,
|
||||||
|
applicationStore,
|
||||||
|
type Application,
|
||||||
|
type AsEnum
|
||||||
|
} from '$lib/ApplicationsStore.svelte';
|
||||||
|
import { deleteR, put } from '$lib/utils';
|
||||||
|
import DropZone from './DropZone.svelte';
|
||||||
|
|
||||||
|
let { activeItem = $bindable() }: { activeItem: Application | undefined } = $props();
|
||||||
|
|
||||||
|
async function moveStatus(status: AsEnum<typeof ApplicationStatus>, 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;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if applicationStore.dragging && activeItem}
|
||||||
|
<div
|
||||||
|
class="flex w-full flex-grow rounded-lg p-3 gap-2 absolute bottom-0 left-0 right-0 bg-white"
|
||||||
|
>
|
||||||
|
<!-- Do nothing -->
|
||||||
|
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
|
||||||
|
<DropZone
|
||||||
|
icon="box-arrow-down"
|
||||||
|
ondrop={async () => {
|
||||||
|
await moveStatus(ApplicationStatus.ToApply);
|
||||||
|
applicationStore.loadAplyed(true);
|
||||||
|
applicationStore.loadAll(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
To apply
|
||||||
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
|
||||||
|
<!-- Ignore -->
|
||||||
|
<DropZone icon="trash-fill" ondrop={() => moveStatus(ApplicationStatus.Ignore)}>
|
||||||
|
Ignore it
|
||||||
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if ([ApplicationStatus.WorkingOnIt, ApplicationStatus.Expired] as number[]).includes(activeItem.status)}
|
||||||
|
<!-- Expired -->
|
||||||
|
<DropZone
|
||||||
|
icon="clock-fill text-orange-500"
|
||||||
|
ondrop={() => {
|
||||||
|
if (activeItem && activeItem.status === ApplicationStatus.Expired) {
|
||||||
|
moveStatus(ApplicationStatus.ToApply, false);
|
||||||
|
} else {
|
||||||
|
moveStatus(ApplicationStatus.Expired);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Mark as expired
|
||||||
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
|
||||||
|
<!-- Repeated -->
|
||||||
|
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}>Delete it</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if ([ApplicationStatus.WorkingOnIt, ApplicationStatus.TasksToDo] as number[]).includes(activeItem.status)}
|
||||||
|
<!-- Applyed -->
|
||||||
|
<DropZone
|
||||||
|
icon="server text-confirm"
|
||||||
|
ondrop={async () => {
|
||||||
|
await moveStatus(ApplicationStatus.Applyed);
|
||||||
|
applicationStore.loadAll(true);
|
||||||
|
applicationStore.loadAplyed(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Apply
|
||||||
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if activeItem.status === ApplicationStatus.Applyed}
|
||||||
|
<!-- Tasks to do -->
|
||||||
|
<DropZone
|
||||||
|
icon="server text-confirm"
|
||||||
|
ondrop={async () => {
|
||||||
|
await moveStatus(ApplicationStatus.TasksToDo);
|
||||||
|
applicationStore.loadAll(true);
|
||||||
|
applicationStore.loadAplyed(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tasks To Do
|
||||||
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if activeItem.status === ApplicationStatus.TasksToDo}
|
||||||
|
<!-- Tasks to do -->
|
||||||
|
<DropZone
|
||||||
|
icon="server text-confirm"
|
||||||
|
ondrop={async () => {
|
||||||
|
await moveStatus(ApplicationStatus.TasksToDo2);
|
||||||
|
applicationStore.loadAll(true);
|
||||||
|
applicationStore.loadAplyed(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tasks To Do 2
|
||||||
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if ([ApplicationStatus.TasksToDo, ApplicationStatus.Applyed] as number[]).includes(activeItem.status)}
|
||||||
|
<!-- Tasks to do -->
|
||||||
|
<DropZone
|
||||||
|
icon="server text-confirm"
|
||||||
|
ondrop={async () => {
|
||||||
|
await moveStatus(ApplicationStatus.InterviewStep1);
|
||||||
|
applicationStore.loadAll(true);
|
||||||
|
applicationStore.loadAplyed(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Interview 1
|
||||||
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if ([ApplicationStatus.InterviewStep1] as number[]).includes(activeItem.status)}
|
||||||
|
<!-- Tasks to do -->
|
||||||
|
<DropZone
|
||||||
|
icon="server text-confirm"
|
||||||
|
ondrop={async () => {
|
||||||
|
await moveStatus(ApplicationStatus.InterviewStep2);
|
||||||
|
applicationStore.loadAll(true);
|
||||||
|
applicationStore.loadAplyed(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Interview 2
|
||||||
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if ([ApplicationStatus.InterviewStep1, ApplicationStatus.InterviewStep2] as number[]).includes(activeItem.status)}
|
||||||
|
<!-- Tasks to do -->
|
||||||
|
<DropZone
|
||||||
|
icon="badge-vo-fill text-confirm"
|
||||||
|
ondrop={async () => {
|
||||||
|
await moveStatus(ApplicationStatus.FinalInterview);
|
||||||
|
applicationStore.loadAll(true);
|
||||||
|
applicationStore.loadAplyed(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Interview Final
|
||||||
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !([ApplicationStatus.ApplyedButSaidNo] as number[]).includes(activeItem.status)}
|
||||||
|
<!-- Rejected -->
|
||||||
|
<DropZone
|
||||||
|
icon="fire text-danger"
|
||||||
|
ondrop={() => {
|
||||||
|
moveStatus(ApplicationStatus.ApplyedButSaidNo);
|
||||||
|
applicationStore.loadAll(true);
|
||||||
|
applicationStore.loadAplyed(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
I was rejeted :(
|
||||||
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
@ -27,8 +27,11 @@
|
|||||||
async function submit(item: Application) {
|
async function submit(item: Application) {
|
||||||
try {
|
try {
|
||||||
application.linked_application = item.id;
|
application.linked_application = item.id;
|
||||||
application.status = ApplicationStatus.LinkedApplication;
|
|
||||||
await put('application/update', application);
|
await put('application/update', application);
|
||||||
|
await put('application/status', {
|
||||||
|
id: application.id,
|
||||||
|
status: ApplicationStatus.LinkedApplication
|
||||||
|
});
|
||||||
dialog.close();
|
dialog.close();
|
||||||
onreload(item);
|
onreload(item);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -70,8 +73,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</h2>
|
</h2>
|
||||||
<span class="text-violet-600 overflow-hidden whitespace-nowrap block max-w-full">
|
<span>
|
||||||
{item.url}
|
{ApplicationStatusMaping[item.status]}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,18 +36,9 @@
|
|||||||
document.removeEventListener('keydown', docKey);
|
document.removeEventListener('keydown', docKey);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
|
|
||||||
<dialog class="card max-w-[50vw]" bind:this={dialogElement}>
|
let internal = $derived(
|
||||||
<div class="flex">
|
applications.filter((i) => {
|
||||||
<input placeholder="Filter" class="p-2 flex-grow" bind:value={filter} />
|
|
||||||
<div>
|
|
||||||
{applications.length}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="overflow-y-auto overflow-x-hidden flex-grow p-2">
|
|
||||||
<!-- TODO loading screen -->
|
|
||||||
{#each applications.filter((i) => {
|
|
||||||
if (application && i.id == application.id) {
|
if (application && i.id == application.id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -71,7 +62,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return x.match(f);
|
return x.match(f);
|
||||||
}) as item}
|
})
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<dialog class="card max-w-[50vw]" bind:this={dialogElement}>
|
||||||
|
<div class="flex">
|
||||||
|
<input placeholder="Filter" class="p-2 flex-grow" bind:value={filter} />
|
||||||
|
<div>
|
||||||
|
{internal.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-y-auto overflow-x-hidden flex-grow p-2">
|
||||||
|
<!-- TODO loading screen -->
|
||||||
|
{#each internal as item}
|
||||||
<div class="card p-2 my-2 bg-slate-100 max-w-full" role="none">
|
<div class="card p-2 my-2 bg-slate-100 max-w-full" role="none">
|
||||||
<button
|
<button
|
||||||
class="text-left max-w-full"
|
class="text-left max-w-full"
|
||||||
@ -90,14 +94,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
{ApplicationStatusMaping[item.status]}
|
|
||||||
</div>
|
|
||||||
</h2>
|
</h2>
|
||||||
<span
|
<span>
|
||||||
class="text-violet-600 overflow-hidden whitespace-nowrap block max-w-full"
|
{ApplicationStatusMaping[item.status]}
|
||||||
>
|
|
||||||
{item.url}
|
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
ApplicationStatusMaping
|
ApplicationStatusMaping
|
||||||
} from '$lib/ApplicationsStore.svelte';
|
} from '$lib/ApplicationsStore.svelte';
|
||||||
|
|
||||||
let { application }: { application: Application } = $props();
|
let { application, showAll }: { application: Application, showAll: boolean } = $props();
|
||||||
|
|
||||||
let events: (ApplicationEvent & { timeDiff: string })[] = $state([]);
|
let events: (ApplicationEvent & { timeDiff: string })[] = $state([]);
|
||||||
|
|
||||||
@ -58,7 +58,7 @@
|
|||||||
if (event.event_type === EventType.Creation) {
|
if (event.event_type === EventType.Creation) {
|
||||||
status = ApplicationStatus.ToApply;
|
status = ApplicationStatus.ToApply;
|
||||||
}
|
}
|
||||||
if (event.event_type !== EventType.StatusUpdate) {
|
if (event.event_type !== EventType.StatusUpdate || showAll) {
|
||||||
if (time) {
|
if (time) {
|
||||||
_events[_events.length - 1].timeDiff = 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"
|
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}
|
{#if event.event_type == EventType.Creation}
|
||||||
<span class="bi bi-plus"></span>
|
<span
|
||||||
|
title={`Created @\n ${new Date(event.time).toLocaleString()}`}
|
||||||
|
class="bi bi-plus"
|
||||||
|
></span>
|
||||||
{:else if event.event_type == EventType.View}
|
{:else if event.event_type == EventType.View}
|
||||||
<span
|
<span
|
||||||
title="Views {new Date(event.time).toLocaleString()}"
|
title={`Viewed @\n ${new Date(event.time).toLocaleString()}`}
|
||||||
class="bi bi-eye"
|
class="bi bi-eye"
|
||||||
></span>
|
></span>
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -3,21 +3,21 @@
|
|||||||
ApplicationStatus,
|
ApplicationStatus,
|
||||||
ApplicationStatusMaping,
|
ApplicationStatusMaping,
|
||||||
applicationStore,
|
applicationStore,
|
||||||
type Application,
|
type Application
|
||||||
type AsEnum
|
|
||||||
} from '$lib/ApplicationsStore.svelte';
|
} 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 { onMount } from 'svelte';
|
||||||
import ExtractTextDialog from './ExtractTextDialog.svelte';
|
import ExtractTextDialog from './ExtractTextDialog.svelte';
|
||||||
import Flair from '../flair/Flair.svelte';
|
import Flair from '../flair/Flair.svelte';
|
||||||
import NewUrlDialog from './NewUrlDialog.svelte';
|
import NewUrlDialog from './NewUrlDialog.svelte';
|
||||||
import DropZone from './DropZone.svelte';
|
|
||||||
import { userStore } from '$lib/UserStore.svelte';
|
import { userStore } from '$lib/UserStore.svelte';
|
||||||
import LinkApplication from './LinkApplication.svelte';
|
import LinkApplication from './LinkApplication.svelte';
|
||||||
import SearchApplication from './SearchApplication.svelte';
|
import SearchApplication from './SearchApplication.svelte';
|
||||||
import NewApplication from './NewApplication.svelte';
|
import NewApplication from './NewApplication.svelte';
|
||||||
import Timeline from './Timeline.svelte';
|
import Timeline from './Timeline.svelte';
|
||||||
|
import DropingZone from './DropingZone.svelte';
|
||||||
|
import CompanyField from './CompanyField.svelte';
|
||||||
|
|
||||||
let activeItem: Application | undefined = $state();
|
let activeItem: Application | undefined = $state();
|
||||||
|
|
||||||
@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
let showExtraData = $state(false);
|
let showExtraData = $state(false);
|
||||||
|
|
||||||
|
let drag = $state(true);
|
||||||
|
|
||||||
async function activate(item?: Application) {
|
async function activate(item?: Application) {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
@ -94,6 +96,9 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Make the CV open on a new page
|
||||||
|
//
|
||||||
function docKey(e: KeyboardEvent) {
|
function docKey(e: KeyboardEvent) {
|
||||||
if (activeItem && e.ctrlKey && e.code === 'KeyO') {
|
if (activeItem && e.ctrlKey && e.code === 'KeyO') {
|
||||||
openCV(activeItem.id);
|
openCV(activeItem.id);
|
||||||
@ -108,6 +113,9 @@
|
|||||||
document.removeEventListener('keydown', docKey);
|
document.removeEventListener('keydown', docKey);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
async function loadActive() {
|
async function loadActive() {
|
||||||
try {
|
try {
|
||||||
@ -175,46 +183,6 @@
|
|||||||
applicationStore.loadItem = undefined;
|
applicationStore.loadItem = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function moveStatus(status: AsEnum<typeof ApplicationStatus>, 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() {
|
async function save() {
|
||||||
try {
|
try {
|
||||||
await put('application/update', activeItem);
|
await put('application/update', activeItem);
|
||||||
@ -232,14 +200,6 @@
|
|||||||
console.log('info data', e);
|
console.log('info data', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let drag = $state(true);
|
|
||||||
|
|
||||||
const statusMapping: string = $derived(
|
|
||||||
(ApplicationStatusMaping[
|
|
||||||
activeItem?.status as (typeof ApplicationStatus)[keyof typeof ApplicationStatus]
|
|
||||||
] as string) ?? ''
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col w-full gap-2 min-w-0 relative" role="none">
|
<div class="flex flex-col w-full gap-2 min-w-0 relative" role="none">
|
||||||
@ -260,7 +220,9 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
{#if activeItem.status != 1}
|
{#if activeItem.status != 1}
|
||||||
<div class="bg-danger text-white p-2 rounded-lg text-lg font-bold">
|
<div class="bg-danger text-white p-2 rounded-lg text-lg font-bold">
|
||||||
{statusMapping}
|
{(ApplicationStatusMaping[
|
||||||
|
activeItem?.status as (typeof ApplicationStatus)[keyof typeof ApplicationStatus]
|
||||||
|
] as string) ?? ''}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if showExtraData}
|
{#if showExtraData}
|
||||||
@ -278,15 +240,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<fieldset class="grow">
|
<CompanyField bind:company={activeItem.company} {save} />
|
||||||
<label class="flabel" for="title">Company</label>
|
|
||||||
<input
|
|
||||||
class="finput"
|
|
||||||
id="title"
|
|
||||||
bind:value={activeItem.company}
|
|
||||||
onchange={save}
|
|
||||||
/>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset class="grow">
|
<fieldset class="grow">
|
||||||
<label class="flabel" for="title">Recruiter</label>
|
<label class="flabel" for="title">Recruiter</label>
|
||||||
<input
|
<input
|
||||||
@ -296,6 +250,16 @@
|
|||||||
onchange={save}
|
onchange={save}
|
||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label class="flabel" for="title">Agency</label>
|
||||||
|
<input
|
||||||
|
class="finput"
|
||||||
|
id="title"
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={activeItem.agency}
|
||||||
|
onchange={save}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label class="flabel" for="title">Title</label>
|
<label class="flabel" for="title">Title</label>
|
||||||
@ -316,7 +280,7 @@
|
|||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{#if !activeItem.unique_url || showExtraData}
|
{#if !activeItem.unique_url || showExtraData}
|
||||||
<fieldset draggable="false" class="max-w-full min-w-0 overflow-hidden">
|
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||||
<div class="flabel">Url</div>
|
<div class="flabel">Url</div>
|
||||||
<div class="finput bg-white w-full break-keep">
|
<div class="finput bg-white w-full break-keep">
|
||||||
{activeItem.url}
|
{activeItem.url}
|
||||||
@ -324,7 +288,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeItem.unique_url}
|
{#if activeItem.unique_url}
|
||||||
<fieldset draggable="false">
|
<fieldset>
|
||||||
<div class="flabel">Unique Url</div>
|
<div class="flabel">Unique Url</div>
|
||||||
<div class="finput bg-white">
|
<div class="finput bg-white">
|
||||||
{activeItem.unique_url}
|
{activeItem.unique_url}
|
||||||
@ -332,7 +296,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeItem.linked_application}
|
{#if activeItem.linked_application}
|
||||||
<fieldset draggable="false">
|
<fieldset>
|
||||||
<div class="flabel">Linked Application</div>
|
<div class="flabel">Linked Application</div>
|
||||||
<div class="finput bg-white">
|
<div class="finput bg-white">
|
||||||
{activeItem.linked_application}
|
{activeItem.linked_application}
|
||||||
@ -340,7 +304,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeItem.application_time}
|
{#if activeItem.application_time}
|
||||||
<fieldset draggable="false">
|
<fieldset>
|
||||||
<div class="flabel">Application Time</div>
|
<div class="flabel">Application Time</div>
|
||||||
<div class="finput bg-white">
|
<div class="finput bg-white">
|
||||||
{activeItem.application_time}
|
{activeItem.application_time}
|
||||||
@ -372,7 +336,6 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<label class="flabel" for="extra">Message</label>
|
<label class="flabel" for="extra">Message</label>
|
||||||
<textarea
|
<textarea
|
||||||
draggable={false}
|
|
||||||
class="finput"
|
class="finput"
|
||||||
id="extra"
|
id="extra"
|
||||||
bind:value={activeItem.message}
|
bind:value={activeItem.message}
|
||||||
@ -442,131 +405,9 @@
|
|||||||
🔬
|
🔬
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Timeline application={activeItem} />
|
<Timeline application={activeItem} showAll={showExtraData} />
|
||||||
</div>
|
</div>
|
||||||
{#if applicationStore.dragging}
|
<DropingZone bind:activeItem />
|
||||||
<div
|
|
||||||
class="flex w-full flex-grow rounded-lg p-3 gap-2 absolute bottom-0 left-0 right-0 bg-white"
|
|
||||||
>
|
|
||||||
<!-- Do nothing -->
|
|
||||||
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
|
|
||||||
<DropZone
|
|
||||||
icon="box-arrow-down"
|
|
||||||
ondrop={() => {
|
|
||||||
moveStatus(ApplicationStatus.ToApply);
|
|
||||||
applicationStore.loadAplyed(true);
|
|
||||||
applicationStore.loadAll(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
To apply
|
|
||||||
</DropZone>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
|
|
||||||
<!-- Ignore -->
|
|
||||||
<DropZone
|
|
||||||
icon="trash-fill"
|
|
||||||
ondrop={() => {
|
|
||||||
moveStatus(ApplicationStatus.Ignore);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Ignore it
|
|
||||||
</DropZone>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if [ApplicationStatus.WorkingOnIt, ApplicationStatus.Expired].includes(activeItem.status)}
|
|
||||||
<!-- Expired -->
|
|
||||||
<DropZone
|
|
||||||
icon="clock-fill text-orange-500"
|
|
||||||
ondrop={() => {
|
|
||||||
if (activeItem && activeItem.status === ApplicationStatus.Expired) {
|
|
||||||
moveStatus(ApplicationStatus.ToApply, false);
|
|
||||||
} else {
|
|
||||||
moveStatus(ApplicationStatus.Expired);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Mark as expired
|
|
||||||
</DropZone>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
|
|
||||||
<!-- Repeated -->
|
|
||||||
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}
|
|
||||||
>Delete it</DropZone
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if [ApplicationStatus.WorkingOnIt, ApplicationStatus.TasksToDo].includes(activeItem.status)}
|
|
||||||
<!-- Applyed -->
|
|
||||||
<DropZone
|
|
||||||
icon="server text-confirm"
|
|
||||||
ondrop={async () => {
|
|
||||||
await moveStatus(ApplicationStatus.Applyed);
|
|
||||||
applicationStore.loadAll(true);
|
|
||||||
applicationStore.loadAplyed(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Apply
|
|
||||||
</DropZone>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if activeItem.status === ApplicationStatus.Applyed}
|
|
||||||
<!-- Tasks to do -->
|
|
||||||
<DropZone
|
|
||||||
icon="server text-confirm"
|
|
||||||
ondrop={async () => {
|
|
||||||
await moveStatus(ApplicationStatus.TasksToDo);
|
|
||||||
applicationStore.loadAll(true);
|
|
||||||
applicationStore.loadAplyed(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Tasks To Do
|
|
||||||
</DropZone>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if [ApplicationStatus.TasksToDo, ApplicationStatus.Applyed].includes(activeItem.status)}
|
|
||||||
<!-- Tasks to do -->
|
|
||||||
<DropZone
|
|
||||||
icon="server text-confirm"
|
|
||||||
ondrop={async () => {
|
|
||||||
await moveStatus(ApplicationStatus.InterviewStep1);
|
|
||||||
applicationStore.loadAll(true);
|
|
||||||
applicationStore.loadAplyed(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Interview 1
|
|
||||||
</DropZone>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if [ApplicationStatus.InterviewStep1].includes(activeItem.status)}
|
|
||||||
<!-- Tasks to do -->
|
|
||||||
<DropZone
|
|
||||||
icon="server text-confirm"
|
|
||||||
ondrop={async () => {
|
|
||||||
await moveStatus(ApplicationStatus.InterviewStep2);
|
|
||||||
applicationStore.loadAll(true);
|
|
||||||
applicationStore.loadAplyed(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Interview 2
|
|
||||||
</DropZone>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if ![ApplicationStatus.ApplyedButSaidNo].includes(activeItem.status)}
|
|
||||||
<!-- Rejected -->
|
|
||||||
<DropZone
|
|
||||||
icon="fire text-danger"
|
|
||||||
ondrop={() => {
|
|
||||||
moveStatus(ApplicationStatus.ApplyedButSaidNo)
|
|
||||||
applicationStore.loadAll(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
I was rejeted :(
|
|
||||||
</DropZone>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="p-2 h-full w-full gap-2 flex-grow card grid place-items-center min-h-[50vh]"
|
class="p-2 h-full w-full gap-2 flex-grow card grid place-items-center min-h-[50vh]"
|
||||||
|
Loading…
Reference in New Issue
Block a user