feat: a lot
This commit is contained in:
parent
26301d1a13
commit
941875ff21
@ -1,8 +1,6 @@
|
||||
package com.andr3h3nriqu3s.applications
|
||||
|
||||
import java.sql.ResultSet
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.UUID
|
||||
import kotlin.collections.emptyList
|
||||
import kotlin.collections.setOf
|
||||
@ -23,27 +21,35 @@ import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
|
||||
data class ApplicationUrl(
|
||||
var application_id: String,
|
||||
var url: String,
|
||||
) {
|
||||
companion object : RowMapper<ApplicationUrl> {
|
||||
override public fun mapRow(rs: ResultSet, rowNum: Int): ApplicationUrl {
|
||||
return ApplicationUrl(
|
||||
rs.getString("application_id"),
|
||||
rs.getString("url"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Application(
|
||||
var id: String,
|
||||
var url: String,
|
||||
var original_url: String?,
|
||||
var unique_url: String?,
|
||||
var title: String,
|
||||
var user_id: String,
|
||||
var extra_data: String,
|
||||
var payrange: String,
|
||||
var status: Int,
|
||||
var status_id: String?,
|
||||
var company: String,
|
||||
var recruiter: String,
|
||||
var agency: Boolean,
|
||||
var message: String,
|
||||
var linked_application: String,
|
||||
var status_history: String,
|
||||
var application_time: String,
|
||||
var create_time: String,
|
||||
var simple_url: String,
|
||||
var flairs: List<Flair>,
|
||||
var views: List<View>,
|
||||
var events: List<Event>,
|
||||
) {
|
||||
companion object : RowMapper<Application> {
|
||||
@ -51,23 +57,17 @@ data class Application(
|
||||
return Application(
|
||||
rs.getString("id"),
|
||||
rs.getString("url"),
|
||||
rs.getString("original_url"),
|
||||
rs.getString("unique_url"),
|
||||
rs.getString("title"),
|
||||
rs.getString("user_id"),
|
||||
rs.getString("extra_data"),
|
||||
rs.getString("payrange"),
|
||||
rs.getInt("status"),
|
||||
rs.getString("status_id"),
|
||||
rs.getString("company"),
|
||||
rs.getString("recruiter"),
|
||||
rs.getBoolean("agency"),
|
||||
rs.getString("message"),
|
||||
rs.getString("linked_application"),
|
||||
rs.getString("status_history"),
|
||||
rs.getString("application_time"),
|
||||
rs.getString("create_time"),
|
||||
emptyList(),
|
||||
rs.getString("simple_url"),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
)
|
||||
@ -77,9 +77,7 @@ data class Application(
|
||||
|
||||
data class SubmitRequest(val text: String)
|
||||
|
||||
data class ListRequest(val status: Int? = null, val views: Boolean? = null)
|
||||
|
||||
data class StatusRequest(val id: String, val status: Int)
|
||||
data class StatusRequest(val id: String, val status_id: String?)
|
||||
|
||||
data class FlairRequest(val id: String, val text: String)
|
||||
|
||||
@ -100,7 +98,6 @@ class ApplicationsController(
|
||||
val sessionService: SessionService,
|
||||
val applicationService: ApplicationService,
|
||||
val flairService: FlairService,
|
||||
val viewService: ViewService,
|
||||
val eventService: EventService,
|
||||
) {
|
||||
|
||||
@ -118,7 +115,13 @@ class ApplicationsController(
|
||||
|
||||
val flairs = application.flairs.map { it.toFlairSimple() }
|
||||
|
||||
return CVData(application.company, application.recruiter, application.message, application.agency, flairs)
|
||||
return CVData(
|
||||
application.company,
|
||||
application.recruiter,
|
||||
application.message,
|
||||
application.agency,
|
||||
flairs
|
||||
)
|
||||
}
|
||||
|
||||
/** Create a new application from the link */
|
||||
@ -133,13 +136,10 @@ class ApplicationsController(
|
||||
Application(
|
||||
UUID.randomUUID().toString(),
|
||||
submit.text,
|
||||
submit.text,
|
||||
submit.text,
|
||||
"New Application",
|
||||
user.id,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
null,
|
||||
"",
|
||||
"",
|
||||
@ -147,9 +147,6 @@ class ApplicationsController(
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
)
|
||||
@ -199,7 +196,7 @@ class ApplicationsController(
|
||||
public fun submitText(
|
||||
@RequestBody submit: SubmitRequest,
|
||||
@RequestHeader("token") token: String
|
||||
): Int {
|
||||
): List<Application> {
|
||||
val user = sessionService.verifyTokenThrow(token)
|
||||
|
||||
var text = submit.text.replace("=\n", "")
|
||||
@ -263,23 +260,17 @@ class ApplicationsController(
|
||||
Application(
|
||||
UUID.randomUUID().toString(),
|
||||
if (elm.contains("linkedin")) elm.split("?")[0] else elm,
|
||||
if (elm.contains("linkedin")) elm.split("?")[0] else null,
|
||||
if (elm.contains("linkedin")) elm.split("?")[0] else null,
|
||||
"New Application",
|
||||
user.id,
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
null,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
emptyList(),
|
||||
if (elm.contains("linkedin")) elm.split("?")[0] else "",
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
)
|
||||
@ -292,26 +283,46 @@ class ApplicationsController(
|
||||
print(applications.size)
|
||||
print(" links\n")
|
||||
|
||||
return applications.size
|
||||
return applications
|
||||
}
|
||||
|
||||
@PostMapping(path = ["/list"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
public fun list(
|
||||
@RequestBody info: ListRequest,
|
||||
@GetMapping(path = ["/list"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
public fun list(@RequestHeader("token") token: String): List<Application> {
|
||||
val user = sessionService.verifyTokenThrow(token)
|
||||
return applicationService.findAll(user)
|
||||
}
|
||||
|
||||
@GetMapping(path = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
public fun get(@PathVariable id: String, @RequestHeader("token") token: String): Application? {
|
||||
val user = sessionService.verifyTokenThrow(token)
|
||||
val app = applicationService.findApplicationById(user, id)
|
||||
if (app == null) {
|
||||
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Application not found", null)
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
@PostMapping(
|
||||
path = ["/link/application/{toLink}/{surviving}"],
|
||||
produces = [MediaType.APPLICATION_JSON_VALUE]
|
||||
)
|
||||
public fun get(
|
||||
@PathVariable toLink: String,
|
||||
@PathVariable surviving: String,
|
||||
@RequestHeader("token") token: String
|
||||
): List<Application> {
|
||||
): Application {
|
||||
val user = sessionService.verifyTokenThrow(token)
|
||||
return applicationService.findAll(user, info)
|
||||
val toLinkApp = applicationService.findApplicationById(user, toLink)
|
||||
val app = applicationService.findApplicationById(user, surviving)
|
||||
|
||||
if (app == null || toLinkApp == null) {
|
||||
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Application not found", null)
|
||||
}
|
||||
|
||||
@GetMapping(path = ["/active"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
public fun active(@RequestHeader("token") token: String): Application? {
|
||||
val user = sessionService.verifyTokenThrow(token)
|
||||
val possibleApplications = applicationService.findAll(user, ListRequest(1))
|
||||
if (possibleApplications.size == 0) {
|
||||
return null
|
||||
}
|
||||
return applicationService.findApplicationById(user, possibleApplications[0].id)
|
||||
applicationService.linkApplications(toLinkApp, app)
|
||||
applicationService.delete(toLinkApp)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@PutMapping(path = ["/status"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ -325,11 +336,11 @@ class ApplicationsController(
|
||||
throw NotFound()
|
||||
}
|
||||
|
||||
if (application.status == info.status) {
|
||||
return application;
|
||||
if (application.status_id == info.status_id) {
|
||||
return application
|
||||
}
|
||||
|
||||
application.status = info.status
|
||||
application.status_id = info.status_id
|
||||
|
||||
applicationService.updateStatus(application)
|
||||
|
||||
@ -362,68 +373,28 @@ class ApplicationsController(
|
||||
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Application not found", null)
|
||||
}
|
||||
|
||||
if (application.unique_url != null) {
|
||||
throw ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Application already has unique_url",
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
application.original_url = application.url
|
||||
application.url = info.url
|
||||
application.unique_url = info.url.split("?")[0]
|
||||
application.simple_url = info.url.split("?")[0]
|
||||
|
||||
var maybe_exists =
|
||||
applicationService.findApplicationByUrl(
|
||||
user,
|
||||
application.url,
|
||||
application.unique_url
|
||||
)
|
||||
if (maybe_exists != null) {
|
||||
applicationService.findApplicationByUrl(user, application.url)
|
||||
?: applicationService.findApplicationByUrl(user, application.simple_url)
|
||||
|
||||
if (maybe_exists != null && maybe_exists.id != application.id) {
|
||||
applicationService.delete(application)
|
||||
|
||||
if (maybe_exists.status == 0 && application.status == 1) {
|
||||
maybe_exists.status = 1
|
||||
applicationService.update(maybe_exists)
|
||||
}
|
||||
|
||||
maybe_exists.flairs = flairService.listFromLinkApplicationId(maybe_exists.id)
|
||||
maybe_exists.events = eventService.listFromApplicationId(maybe_exists.id).toList()
|
||||
|
||||
return maybe_exists
|
||||
}
|
||||
|
||||
applicationService.addUrl(application.id, info.url)
|
||||
applicationService.addUrl(application.id, info.url.split("?")[0])
|
||||
applicationService.update(application)
|
||||
|
||||
application.flairs = flairService.listFromLinkApplicationId(application.id)
|
||||
application.views = viewService.listFromApplicationId(application.id)
|
||||
|
||||
return application
|
||||
}
|
||||
|
||||
@PostMapping(path = ["/reset/url/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
public fun updateUrl(
|
||||
@PathVariable id: String,
|
||||
@RequestHeader("token") token: String
|
||||
): Application {
|
||||
val user = sessionService.verifyTokenThrow(token)
|
||||
var application = applicationService.findApplicationById(user, id)
|
||||
|
||||
if (application == null) {
|
||||
throw NotFound()
|
||||
}
|
||||
|
||||
if (application.unique_url == null) {
|
||||
throw BadRequest()
|
||||
}
|
||||
|
||||
application.url = application.original_url!!
|
||||
application.original_url = null
|
||||
application.unique_url = null
|
||||
|
||||
applicationService.update(application)
|
||||
|
||||
application.flairs = flairService.listFromLinkApplicationId(application.id)
|
||||
application.events = eventService.listFromApplicationId(application.id).toList()
|
||||
|
||||
return application
|
||||
}
|
||||
@ -466,28 +437,13 @@ class ApplicationsController(
|
||||
class ApplicationService(
|
||||
val db: JdbcTemplate,
|
||||
val flairService: FlairService,
|
||||
val viewService: ViewService,
|
||||
val eventService: EventService,
|
||||
) {
|
||||
|
||||
public fun findApplicationByUrl(user: UserDb, url: String, unique_url: String?): Application? {
|
||||
if (unique_url != null) {
|
||||
val unique: List<Application> =
|
||||
public fun findApplicationByUrl(user: UserDb, url: String): Application? {
|
||||
val applications =
|
||||
db.query(
|
||||
"select * from applications where unique_url=? and user_id=?",
|
||||
arrayOf(unique_url, user.id),
|
||||
Application
|
||||
)
|
||||
.toList()
|
||||
|
||||
if (unique.size != 0) {
|
||||
return unique[0]
|
||||
}
|
||||
}
|
||||
|
||||
val applications: List<Application> =
|
||||
db.query(
|
||||
"select * from applications where url=? and user_id=?",
|
||||
"select * from applications as app inner join applications_urls as app_url on app_url.application_id=app.id where app_url.url=? and user_id=?",
|
||||
arrayOf(url, user.id),
|
||||
Application
|
||||
)
|
||||
@ -516,7 +472,6 @@ class ApplicationService(
|
||||
var application = applications[0]
|
||||
|
||||
application.flairs = flairService.listFromLinkApplicationId(application.id)
|
||||
application.views = viewService.listFromApplicationId(application.id)
|
||||
application.events = eventService.listFromApplicationId(application.id).toList()
|
||||
|
||||
return application
|
||||
@ -538,40 +493,51 @@ class ApplicationService(
|
||||
return application
|
||||
}
|
||||
|
||||
public fun addUrl(id: String, url: String) {
|
||||
val applications =
|
||||
db.query(
|
||||
"select * from applications_urls as app_url where app_url.url=? and app_url.application_id=?",
|
||||
arrayOf(url, id),
|
||||
ApplicationUrl
|
||||
)
|
||||
.toList()
|
||||
|
||||
if (applications.size > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
db.update("insert into applications_urls (application_id, url) values (?, ?);", id, url)
|
||||
}
|
||||
|
||||
public fun createApplication(user: UserDb, application: Application): Boolean {
|
||||
if (this.findApplicationByUrl(user, application.url, application.unique_url) != null) {
|
||||
if (this.findApplicationByUrl(user, application.url) != null) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Create time is auto created by the database
|
||||
// The default status is null
|
||||
db.update(
|
||||
"insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status, company, recruiter, message, linked_application, status_history, application_time, agency) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
"insert into applications (id, url, title, user_id, extra_data, payrange, status_id, company, recruiter, message, agency, simple_url) values (?,?,?,?,?,?,?,?,?,?,?,?);",
|
||||
application.id,
|
||||
application.url,
|
||||
application.original_url,
|
||||
application.unique_url,
|
||||
application.title,
|
||||
application.user_id,
|
||||
application.extra_data,
|
||||
application.payrange,
|
||||
application.status,
|
||||
application.status_id,
|
||||
application.company,
|
||||
application.recruiter,
|
||||
application.message,
|
||||
application.linked_application,
|
||||
application.status_history,
|
||||
application.application_time,
|
||||
application.agency,
|
||||
application.simple_url,
|
||||
)
|
||||
|
||||
eventService.create(application.id, EventType.Creation)
|
||||
addUrl(application.id, application.url)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun internalFindAll(user: UserDb, info: ListRequest): Iterable<Application> {
|
||||
if (info.status == null) {
|
||||
private fun internalFindAll(user: UserDb): Iterable<Application> {
|
||||
return db.query(
|
||||
"select * from applications where user_id=? order by title asc;",
|
||||
arrayOf(user.id),
|
||||
@ -579,30 +545,8 @@ class ApplicationService(
|
||||
)
|
||||
}
|
||||
|
||||
// If it's to apply also remove the linked_application to only show the main
|
||||
if (info.status == 0) {
|
||||
return db.query(
|
||||
"select * from applications where user_id=? and linked_application='' and status=0 order by title asc;",
|
||||
arrayOf(user.id),
|
||||
Application
|
||||
)
|
||||
}
|
||||
|
||||
return db.query(
|
||||
"select * from applications where user_id=? and status=? order by title asc;",
|
||||
arrayOf(user.id, info.status),
|
||||
Application,
|
||||
)
|
||||
}
|
||||
|
||||
public fun findAll(user: UserDb, info: ListRequest): List<Application> {
|
||||
var iter = internalFindAll(user, info);
|
||||
if (info.views == true) {
|
||||
iter = iter.map {
|
||||
it.views = viewService.listFromApplicationId(it.id)
|
||||
it
|
||||
}
|
||||
}
|
||||
public fun findAll(user: UserDb): List<Application> {
|
||||
var iter = internalFindAll(user)
|
||||
return iter.toList()
|
||||
}
|
||||
|
||||
@ -618,26 +562,11 @@ class ApplicationService(
|
||||
// TODO how status history works
|
||||
public fun updateStatus(application: Application): Application {
|
||||
|
||||
val status_string = "${application.status}"
|
||||
var status_history = application.status_history.split(",").filter { it.length >= 1 }
|
||||
if (status_history.indexOf(status_string) == -1) {
|
||||
status_history = status_history.plus(status_string)
|
||||
}
|
||||
|
||||
application.status_history = status_history.joinToString(",") { it }
|
||||
|
||||
if (application.status == 4) {
|
||||
val sdf = SimpleDateFormat("dd/MM/yyyy hh:mm:ss")
|
||||
application.application_time = sdf.format(Date())
|
||||
}
|
||||
|
||||
eventService.create(application.id, EventType.StatusUpdate, application.status)
|
||||
eventService.create(application.id, EventType.StatusUpdate, application.status_id)
|
||||
|
||||
db.update(
|
||||
"update applications set status=?, status_history=?, application_time=? where id=?",
|
||||
application.status,
|
||||
application.status_history,
|
||||
application.application_time,
|
||||
"update applications set status_id=? where id=?",
|
||||
application.status_id,
|
||||
application.id,
|
||||
)
|
||||
return application
|
||||
@ -647,10 +576,8 @@ class ApplicationService(
|
||||
public fun update(application: Application): Application {
|
||||
// I don't want ot update create_time
|
||||
db.update(
|
||||
"update applications set url=?, original_url=?, unique_url=?, title=?, user_id=?, extra_data=?, payrange=?, company=?, recruiter=?, message=?, linked_application=?, agency=? where id=?",
|
||||
"update applications set url=?, title=?, user_id=?, extra_data=?, payrange=?, company=?, recruiter=?, message=?, agency=?, simple_url=? where id=?",
|
||||
application.url,
|
||||
application.original_url,
|
||||
application.unique_url,
|
||||
application.title,
|
||||
application.user_id,
|
||||
application.extra_data,
|
||||
@ -658,17 +585,29 @@ class ApplicationService(
|
||||
application.company,
|
||||
application.recruiter,
|
||||
application.message,
|
||||
application.linked_application,
|
||||
application.agency,
|
||||
application.simple_url,
|
||||
application.id,
|
||||
)
|
||||
return application
|
||||
}
|
||||
|
||||
public fun linkApplications(toLink: Application, surviving: Application) {
|
||||
db.update(
|
||||
"update applications_urls set application_id=? where application_id=?;",
|
||||
surviving.id,
|
||||
toLink.id,
|
||||
)
|
||||
}
|
||||
|
||||
public fun delete(application: Application) {
|
||||
db.update(
|
||||
"delete from applications where id=?",
|
||||
application.id,
|
||||
)
|
||||
db.update(
|
||||
"delete from applications_urls where application_id=?",
|
||||
application.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ data class Event(
|
||||
var id: String,
|
||||
var application_id: String,
|
||||
var event_type: Int,
|
||||
var new_status: Int?,
|
||||
var new_status_id: String?,
|
||||
var time: Timestamp
|
||||
) {
|
||||
companion object : RowMapper<Event> {
|
||||
@ -34,7 +34,7 @@ data class Event(
|
||||
rs.getString("id"),
|
||||
rs.getString("application_id"),
|
||||
rs.getInt("event_type"),
|
||||
rs.getInt("new_status"),
|
||||
rs.getString("new_status_id"),
|
||||
rs.getTimestamp("time"),
|
||||
)
|
||||
}
|
||||
@ -93,23 +93,19 @@ public class EventService(val db: JdbcTemplate) {
|
||||
public fun create(
|
||||
application_id: String,
|
||||
event_type: EventType,
|
||||
new_status: Int? = null
|
||||
new_status_id: String? = null
|
||||
): Event {
|
||||
val id = UUID.randomUUID().toString()
|
||||
|
||||
if (event_type == EventType.StatusUpdate && new_status == null) {
|
||||
throw Exception("When event_type == StatusUpdate new_status must be set")
|
||||
}
|
||||
|
||||
var new_event =
|
||||
Event(id, application_id, event_type.value, new_status, Timestamp(Date().getTime()))
|
||||
Event(id, application_id, event_type.value, new_status_id, Timestamp(Date().getTime()))
|
||||
|
||||
db.update(
|
||||
"insert into events (id, application_id, event_type, new_status) values (?, ?, ? ,?)",
|
||||
"insert into events (id, application_id, event_type, new_status_id) values (?, ?, ? ,?)",
|
||||
new_event.id,
|
||||
new_event.application_id,
|
||||
new_event.event_type,
|
||||
new_event.new_status,
|
||||
new_event.new_status_id,
|
||||
)
|
||||
|
||||
return new_event
|
||||
|
@ -2,8 +2,8 @@ package com.andr3h3nriqu3s.applications
|
||||
|
||||
import java.sql.ResultSet
|
||||
import java.util.UUID
|
||||
import org.springframework.http.MediaType
|
||||
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
|
||||
@ -52,10 +52,6 @@ class FlairController(
|
||||
throw NotFound()
|
||||
}
|
||||
|
||||
if (old_flair.user_id != user.id) {
|
||||
throw NotAuth()
|
||||
}
|
||||
|
||||
flair.user_id = old_flair.user_id
|
||||
|
||||
return flairService.updateFlair(flair)
|
||||
@ -88,6 +84,8 @@ data class SimpleFlair(
|
||||
val name: String,
|
||||
val description: String,
|
||||
val color: String,
|
||||
val sort: Int,
|
||||
val showFullDescription: Int,
|
||||
)
|
||||
|
||||
data class Flair(
|
||||
@ -97,6 +95,8 @@ data class Flair(
|
||||
var name: String,
|
||||
var expr: String,
|
||||
var description: String,
|
||||
var sort: Int,
|
||||
var showFullDescription: Int,
|
||||
) {
|
||||
companion object : RowMapper<Flair> {
|
||||
override public fun mapRow(rs: ResultSet, rowNum: Int): Flair {
|
||||
@ -107,12 +107,20 @@ data class Flair(
|
||||
rs.getString("name"),
|
||||
rs.getString("expr"),
|
||||
rs.getString("description"),
|
||||
rs.getInt("sort"),
|
||||
rs.getInt("showFullDescription"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun toFlairSimple(): SimpleFlair {
|
||||
return SimpleFlair(this.name, this.description, this.color)
|
||||
return SimpleFlair(
|
||||
this.name,
|
||||
this.description,
|
||||
this.color,
|
||||
this.sort,
|
||||
this.showFullDescription
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,7 +168,7 @@ public class FlairService(val db: JdbcTemplate) {
|
||||
|
||||
public fun listFromLinkApplicationId(id: String): List<Flair> =
|
||||
db.query(
|
||||
"select f.id, f.user_id, f.color, f.name, f.expr, f.description from flair_link as fl inner join flair as f on f.id = fl.flair_id where application_id=? order by name asc;",
|
||||
"select f.id, f.user_id, f.color, f.name, f.expr, f.description, f.sort, f.showFullDescription from flair_link as fl inner join flair as f on f.id = fl.flair_id where application_id=? order by name asc;",
|
||||
arrayOf(id),
|
||||
Flair
|
||||
)
|
||||
@ -210,12 +218,14 @@ public class FlairService(val db: JdbcTemplate) {
|
||||
|
||||
public fun updateFlair(flair: Flair): Flair {
|
||||
db.update(
|
||||
"update flair set user_id=?, color=?, name=?, expr=?, description=? where id=?;",
|
||||
"update flair set user_id=?, color=?, name=?, expr=?, description=?, sort=?, showFullDescription=? where id=?;",
|
||||
flair.user_id,
|
||||
flair.color,
|
||||
flair.name,
|
||||
flair.expr,
|
||||
flair.description,
|
||||
flair.sort,
|
||||
flair.showFullDescription,
|
||||
flair.id,
|
||||
)
|
||||
|
||||
@ -231,16 +241,18 @@ public class FlairService(val db: JdbcTemplate) {
|
||||
description = flair.description!!
|
||||
}
|
||||
|
||||
var new_flair = Flair(id, user.id, flair.color, flair.name, flair.expr, description)
|
||||
var new_flair = Flair(id, user.id, flair.color, flair.name, flair.expr, description, 0, 1)
|
||||
|
||||
db.update(
|
||||
"insert into flair (id, user_id, color, name, expr, description) values (?, ?, ?, ?, ?, ?)",
|
||||
"insert into flair (id, user_id, color, name, expr, description, sort, showFullDescription) values (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
new_flair.id,
|
||||
new_flair.user_id,
|
||||
new_flair.color,
|
||||
new_flair.name,
|
||||
new_flair.expr,
|
||||
new_flair.description
|
||||
new_flair.description,
|
||||
new_flair.showFullDescription,
|
||||
new_flair.sort,
|
||||
)
|
||||
|
||||
return new_flair
|
||||
|
@ -28,7 +28,9 @@ data class UserStatusNode(
|
||||
var y: Int,
|
||||
var width: Int,
|
||||
var height: Int,
|
||||
var permission: Int
|
||||
var permission: Int,
|
||||
var visible: Boolean,
|
||||
var endable: Boolean,
|
||||
) {
|
||||
companion object : RowMapper<UserStatusNode> {
|
||||
override public fun mapRow(rs: ResultSet, rowNum: Int): UserStatusNode {
|
||||
@ -42,6 +44,8 @@ data class UserStatusNode(
|
||||
rs.getInt("width"),
|
||||
rs.getInt("height"),
|
||||
rs.getInt("permission"),
|
||||
rs.getBoolean("visible"),
|
||||
rs.getBoolean("endable"),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -211,7 +215,7 @@ public class UserStatusNodeService(val db: JdbcTemplate) {
|
||||
|
||||
public fun update(node: UserStatusNode): UserStatusNode {
|
||||
db.update(
|
||||
"update user_status_node set name=?, icon=?, x=?, y=?, width=?, height=?, permission=? where id=?;",
|
||||
"update user_status_node set name=?, icon=?, x=?, y=?, width=?, height=?, permission=?, visible=?, endable=? where id=?;",
|
||||
node.name,
|
||||
node.icon,
|
||||
node.x,
|
||||
@ -219,6 +223,8 @@ public class UserStatusNodeService(val db: JdbcTemplate) {
|
||||
node.width,
|
||||
node.height,
|
||||
node.permission,
|
||||
node.visible,
|
||||
node.endable,
|
||||
node.id,
|
||||
)
|
||||
|
||||
@ -230,7 +236,7 @@ public class UserStatusNodeService(val db: JdbcTemplate) {
|
||||
node.id = id
|
||||
|
||||
db.update(
|
||||
"insert into user_status_node (id, user_id, name, icon, x, y, width, height, permission) values (?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
"insert into user_status_node (id, user_id, name, icon, x, y, width, height, permission, visible, endable) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
node.id,
|
||||
node.user_id,
|
||||
node.name,
|
||||
@ -239,7 +245,9 @@ public class UserStatusNodeService(val db: JdbcTemplate) {
|
||||
node.y,
|
||||
node.width,
|
||||
node.height,
|
||||
node.permission
|
||||
node.permission,
|
||||
node.visible,
|
||||
node.endable,
|
||||
)
|
||||
|
||||
return node
|
||||
|
@ -1,103 +0,0 @@
|
||||
package com.andr3h3nriqu3s.applications
|
||||
|
||||
import java.sql.ResultSet
|
||||
import java.sql.Timestamp
|
||||
import java.util.Date
|
||||
import java.util.UUID
|
||||
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.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RequestHeader
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
data class View(var id: String, var application_id: String, var time: Timestamp) {
|
||||
companion object : RowMapper<View> {
|
||||
override public fun mapRow(rs: ResultSet, rowNum: Int): View {
|
||||
return View(
|
||||
rs.getString("id"),
|
||||
rs.getString("application_id"),
|
||||
rs.getTimestamp("time"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
@ControllerAdvice
|
||||
@RequestMapping("/api/view")
|
||||
class ViewController(
|
||||
val sessionService: SessionService,
|
||||
val applicationService: ApplicationService,
|
||||
val flairService: FlairService,
|
||||
val viewService: ViewService,
|
||||
) {
|
||||
|
||||
@GetMapping(path = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
public fun getCV(@PathVariable id: String, @RequestHeader("token") token: String): List<View> {
|
||||
val user = sessionService.verifyTokenThrow(token)
|
||||
|
||||
val application = applicationService.findApplicationById(user, id)
|
||||
|
||||
if (application == null) {
|
||||
throw NotFound()
|
||||
}
|
||||
|
||||
return application.views
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
public class ViewService(val db: JdbcTemplate) {
|
||||
|
||||
public fun listFromApplicationId(id: String): List<View> =
|
||||
db.query("select * from views where application_id=?;", arrayOf(id), View).toList()
|
||||
|
||||
public fun getById(id: String): View? {
|
||||
val items = db.query("select * from views where id=?;", arrayOf(id), View).toList()
|
||||
if (items.size == 0) {
|
||||
return null
|
||||
}
|
||||
return items[0]
|
||||
}
|
||||
|
||||
public fun deleteById(id: String): View {
|
||||
val view = this.getById(id)
|
||||
if (view == null) {
|
||||
throw NotFound()
|
||||
}
|
||||
|
||||
db.update("delete from views where id=?", id)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
public fun update(view: View): View {
|
||||
db.update(
|
||||
"update views set application_id=?, time=? where id=?;",
|
||||
view.application_id,
|
||||
view.time,
|
||||
view.id,
|
||||
)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
public fun create(application_id: String): View {
|
||||
val id = UUID.randomUUID().toString()
|
||||
|
||||
var new_view = View(id, application_id, Timestamp(Date().getTime()))
|
||||
|
||||
db.update(
|
||||
"insert into views (id, application_id) values (?, ?)",
|
||||
new_view.id,
|
||||
new_view.application_id
|
||||
)
|
||||
|
||||
return new_view
|
||||
}
|
||||
}
|
@ -6,37 +6,27 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
level INT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
token VARCHAR PRIMARY KEY,
|
||||
user_id VARCHAR
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS tokens (token VARCHAR PRIMARY KEY, user_id VARCHAR);
|
||||
|
||||
create table if not exists applications (
|
||||
id text primary key,
|
||||
url text not null,
|
||||
original_url text,
|
||||
unique_url text,
|
||||
simple_url text not null,
|
||||
company text,
|
||||
recruiter text,
|
||||
title text,
|
||||
mesasge text default '',
|
||||
status_history text default '',
|
||||
user_id text,
|
||||
extra_data text,
|
||||
-- this status will be deprecated in favor of the node style status
|
||||
status integer,
|
||||
status_id text default null,
|
||||
linked_application text default '',
|
||||
application_time text default '',
|
||||
agency boolean default false,
|
||||
create_time timestamp default now()
|
||||
create_time timestamp default now ()
|
||||
);
|
||||
|
||||
-- Views are deprecated will be removed in the future
|
||||
create table if not exists views (
|
||||
id text primary key,
|
||||
application_id text not null,
|
||||
time timestamp default now()
|
||||
create table if not exists applications_urls (
|
||||
application_id text,
|
||||
url text not null,
|
||||
primary key (application_id, url)
|
||||
);
|
||||
|
||||
create table if not exists flair (
|
||||
@ -45,7 +35,9 @@ create table if not exists flair (
|
||||
color text default '#ff0000',
|
||||
name text default 'New Flair',
|
||||
expr text default 'flair',
|
||||
description text default ''
|
||||
description text default '',
|
||||
showFullDescription integer default 1,
|
||||
sort integer default 0
|
||||
);
|
||||
|
||||
create table if not exists flair_link (
|
||||
@ -65,17 +57,14 @@ create table if not exists events (
|
||||
-- StatusUpdate(1),
|
||||
-- Page(2)
|
||||
event_type integer not null,
|
||||
|
||||
-- This only matters when event_type == 1
|
||||
new_status integer,
|
||||
|
||||
time timestamp default now()
|
||||
new_status_id text,
|
||||
time timestamp default now ()
|
||||
);
|
||||
|
||||
--
|
||||
-- User Controlled Status
|
||||
--
|
||||
|
||||
create table if not exists user_status_node (
|
||||
id text primary key,
|
||||
user_id text not null,
|
||||
@ -85,25 +74,23 @@ create table if not exists user_status_node (
|
||||
y integer default 0,
|
||||
width integer default 0,
|
||||
height integer default 0,
|
||||
permission integer default 0
|
||||
permission integer default 0,
|
||||
visible boolean default true,
|
||||
endable boolean default false
|
||||
);
|
||||
|
||||
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
|
||||
);
|
||||
|
@ -1,138 +1,67 @@
|
||||
import type { Flair } from './FlairStore.svelte';
|
||||
import { post } from './utils';
|
||||
import { get } from './utils';
|
||||
|
||||
export type AsEnum<T> = T[keyof T];
|
||||
|
||||
export const ApplicationStatus = Object.freeze({
|
||||
ToApply: 0,
|
||||
WorkingOnIt: 1,
|
||||
Ignore: 2,
|
||||
ApplyedButSaidNo: 3,
|
||||
Applyed: 4,
|
||||
Expired: 5,
|
||||
TasksToDo: 6,
|
||||
TasksToDo2: 10,
|
||||
LinkedApplication: 7,
|
||||
InterviewStep1: 8,
|
||||
InterviewStep2: 9,
|
||||
FinalInterview: 11
|
||||
});
|
||||
|
||||
export const ApplicationStatusIconMaping: Record<AsEnum<typeof ApplicationStatus>, string> = Object.freeze({
|
||||
0: 'clock',
|
||||
1: 'search',
|
||||
2: 'trash3',
|
||||
3: 'fire',
|
||||
4: 'send',
|
||||
5: 'hourglass-bottom',
|
||||
6: 'list-check',
|
||||
10: 'list-check',
|
||||
7: 'link-45deg',
|
||||
8: 'person',
|
||||
9: 'people',
|
||||
11: 'badge-vo-fill'
|
||||
});
|
||||
|
||||
export const ApplicationStatusMaping: Record<AsEnum<typeof ApplicationStatus>, string> = Object.freeze({
|
||||
0: 'To Apply',
|
||||
1: 'Working On It',
|
||||
2: 'Ignore',
|
||||
3: 'Applyed But Said No',
|
||||
4: 'Applyed',
|
||||
5: 'Expired',
|
||||
6: 'Tasks To Do',
|
||||
10: 'Tasks To Do 2',
|
||||
7: 'Linked Application',
|
||||
8: 'Interview 1',
|
||||
9: 'Interview 2',
|
||||
11: 'Final Interview'
|
||||
});
|
||||
|
||||
export type View = {
|
||||
id: string;
|
||||
application_id: string;
|
||||
time: string;
|
||||
};
|
||||
|
||||
export const EventType = Object.freeze({
|
||||
Creation: 0,
|
||||
StatusUpdate: 1,
|
||||
View: 2,
|
||||
View: 2
|
||||
});
|
||||
|
||||
export type ApplicationEvent = {
|
||||
id: string,
|
||||
application_id: string,
|
||||
event_type: AsEnum<typeof EventType>,
|
||||
new_status: number,
|
||||
time: string
|
||||
}
|
||||
id: string;
|
||||
application_id: string;
|
||||
event_type: AsEnum<typeof EventType>;
|
||||
new_status_id: string;
|
||||
time: string;
|
||||
};
|
||||
|
||||
export type Application = {
|
||||
id: string;
|
||||
url: string;
|
||||
original_url: string | null;
|
||||
unique_url: string | null;
|
||||
simple_url: string;
|
||||
title: string;
|
||||
user_id: string;
|
||||
extra_data: string;
|
||||
payrange: string;
|
||||
status: AsEnum<typeof ApplicationStatus>;
|
||||
status_id: string;
|
||||
recruiter: string;
|
||||
agency: boolean;
|
||||
company: string;
|
||||
message: string;
|
||||
linked_application: string;
|
||||
application_time: string;
|
||||
create_time: string;
|
||||
status_history: string;
|
||||
flairs: Flair[];
|
||||
views: View[];
|
||||
events: ApplicationEvent[];
|
||||
};
|
||||
|
||||
function createApplicationStore() {
|
||||
let applications: Application[] = $state([]);
|
||||
let applyed: Application[] = $state([]);
|
||||
let all: Application[] = $state([]);
|
||||
|
||||
let dragApplication: Application | undefined = $state(undefined);
|
||||
|
||||
let loadItem: Application | undefined = $state(undefined);
|
||||
let loadItemOpen = $state(true);
|
||||
|
||||
let req = false;
|
||||
|
||||
return {
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
async loadApplications(force = false) {
|
||||
if (!force && applications.length > 1) {
|
||||
return;
|
||||
}
|
||||
applications = await post('application/list', { status: 0 });
|
||||
},
|
||||
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
async loadAplyed(force = false) {
|
||||
if (!force && applyed.length > 1) {
|
||||
return;
|
||||
}
|
||||
applyed = await post('application/list', { status: ApplicationStatus.Applyed, views: true });
|
||||
},
|
||||
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
async loadAll(force = false) {
|
||||
if (req) return;
|
||||
if (!force && all.length > 1) {
|
||||
return;
|
||||
}
|
||||
all = await post('application/list', {});
|
||||
},
|
||||
|
||||
clear() {
|
||||
applications = [];
|
||||
req = true;
|
||||
try {
|
||||
all = await get('application/list');
|
||||
} finally {
|
||||
req = false;
|
||||
}
|
||||
},
|
||||
|
||||
dragStart(application: Application | undefined) {
|
||||
@ -146,28 +75,53 @@ function createApplicationStore() {
|
||||
dragApplication = undefined;
|
||||
},
|
||||
|
||||
removeByID(uuid: string) {
|
||||
all = all.filter((i) => i.id !== uuid);
|
||||
},
|
||||
|
||||
getIndexById(uuid: string): number | undefined {
|
||||
return all
|
||||
.map((a, i) => [a, i] as [Application, number])
|
||||
.filter((i) => i[0].id === uuid)[0]?.[1];
|
||||
},
|
||||
|
||||
get dragging() {
|
||||
return dragApplication;
|
||||
},
|
||||
|
||||
get applications() {
|
||||
return applications;
|
||||
},
|
||||
|
||||
get applyed() {
|
||||
return applyed;
|
||||
},
|
||||
|
||||
get all() {
|
||||
return all;
|
||||
},
|
||||
|
||||
reset() {
|
||||
// This just tells svelte that we changed all
|
||||
all = all;
|
||||
},
|
||||
|
||||
set(item: Application, index?: number) {
|
||||
if (index === undefined) {
|
||||
index = this.getIndexById(item.id);
|
||||
}
|
||||
if (index === undefined) return;
|
||||
all[index] = item;
|
||||
this.reset();
|
||||
},
|
||||
|
||||
get loadItem() {
|
||||
return loadItem;
|
||||
},
|
||||
|
||||
set loadItem(item: Application | undefined) {
|
||||
loadItem = item;
|
||||
loadItemOpen = true;
|
||||
},
|
||||
|
||||
get loadItemOpen() {
|
||||
return loadItemOpen;
|
||||
},
|
||||
|
||||
set loadItemOpen(_loadItemOpen: boolean) {
|
||||
loadItemOpen = _loadItemOpen;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ export type Flair = {
|
||||
name: string;
|
||||
expr: string;
|
||||
description: string;
|
||||
showFullDescription: number;
|
||||
sort: number;
|
||||
};
|
||||
|
||||
function createFlairStore() {
|
||||
|
80
site/src/lib/Types.svelte.ts
Normal file
80
site/src/lib/Types.svelte.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { get } from './utils';
|
||||
import type { Node, FullLinkApi } from '../routes/flow/types';
|
||||
|
||||
function createStatusStore() {
|
||||
let nodes: Node[] = $state([]);
|
||||
let links: FullLinkApi[] = $state([]);
|
||||
|
||||
let nodesR: Record<string, Node> = $state({});
|
||||
let dirLinks: Record<string, Node[]> = $state({});
|
||||
|
||||
let request = false;
|
||||
|
||||
return {
|
||||
/**
|
||||
* @throws {Error}
|
||||
**/
|
||||
async load(clear = false) {
|
||||
if (!clear && nodes.length !== 0) {
|
||||
return;
|
||||
}
|
||||
if (request) return;
|
||||
|
||||
request = true;
|
||||
|
||||
try {
|
||||
nodes = await get('user/status/node');
|
||||
links = await get('user/status/link');
|
||||
} catch (e) {
|
||||
request = false;
|
||||
throw e;
|
||||
}
|
||||
|
||||
dirLinks = {};
|
||||
nodesR = {};
|
||||
|
||||
nodes.sort((a, b) => {
|
||||
if (a.y !== b.y) return a.y - b.y;
|
||||
return b.x - a.x;
|
||||
});
|
||||
|
||||
for (const node of nodes) {
|
||||
nodesR[node.id] = node;
|
||||
}
|
||||
|
||||
const nodesId: string[] = [null as any, ...Object.keys(nodesR)];
|
||||
|
||||
nodesR[null as any] = {
|
||||
icon: 'plus',
|
||||
id: null as any,
|
||||
name: 'Created'
|
||||
} as any;
|
||||
|
||||
for (const nodeId of nodesId) {
|
||||
const targets = [];
|
||||
for (const link of links) {
|
||||
if (link.source_node === nodeId) {
|
||||
targets.push(nodesR[link.target_node as any]);
|
||||
}
|
||||
}
|
||||
dirLinks[nodeId] = targets;
|
||||
}
|
||||
|
||||
request = false;
|
||||
},
|
||||
|
||||
get nodes() {
|
||||
return nodes;
|
||||
},
|
||||
|
||||
get nodesR() {
|
||||
return nodesR;
|
||||
},
|
||||
|
||||
get dirLinks() {
|
||||
return dirLinks;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const statusStore = createStatusStore();
|
@ -3,9 +3,7 @@
|
||||
import HasUser from '$lib/HasUser.svelte';
|
||||
import ApplicationsList from './ApplicationsList.svelte';
|
||||
import WorkArea from './work-area/WorkArea.svelte';
|
||||
import AppliyedList from './AppliyedList.svelte';
|
||||
import PApplicationList from './PApplicationList.svelte';
|
||||
import { ApplicationStatus } from '$lib/ApplicationsStore.svelte';
|
||||
import ApplicationTypesList from './ApplicationTypesList.svelte';
|
||||
</script>
|
||||
|
||||
<HasUser redirect="/cv">
|
||||
@ -16,22 +14,8 @@
|
||||
<ApplicationsList />
|
||||
<WorkArea />
|
||||
</div>
|
||||
<PApplicationList status={ApplicationStatus.FinalInterview}>
|
||||
Interview Final
|
||||
</PApplicationList >
|
||||
<PApplicationList status={ApplicationStatus.InterviewStep2}>
|
||||
Interview II
|
||||
</PApplicationList >
|
||||
<PApplicationList status={ApplicationStatus.InterviewStep1}>
|
||||
Interview I
|
||||
</PApplicationList >
|
||||
<PApplicationList status={ApplicationStatus.TasksToDo2}>
|
||||
Tasks To do 2
|
||||
</PApplicationList >
|
||||
<PApplicationList status={ApplicationStatus.TasksToDo}>
|
||||
Tasks To do
|
||||
</PApplicationList >
|
||||
<AppliyedList />
|
||||
<ApplicationTypesList />
|
||||
<div class="p-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</HasUser>
|
||||
|
17
site/src/routes/ApplicationTypesList.svelte
Normal file
17
site/src/routes/ApplicationTypesList.svelte
Normal file
@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { statusStore } from '$lib/Types.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import SmartApplicationList from './SmartApplicationList.svelte';
|
||||
|
||||
onMount(async () => {
|
||||
await statusStore.load();
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if statusStore.nodes.length > 0}
|
||||
{#each statusStore.nodes as node}
|
||||
{#if node.visible}
|
||||
<SmartApplicationList title={node.name} status_id={node.id} />
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
@ -5,11 +5,15 @@
|
||||
let filter = $state('');
|
||||
|
||||
onMount(() => {
|
||||
applicationStore.loadApplications();
|
||||
applicationStore.loadAll();
|
||||
});
|
||||
|
||||
let apps = $derived(
|
||||
applicationStore.all.filter((i) => i.status_id === null && !i.linked_application)
|
||||
);
|
||||
|
||||
let internal = $derived(
|
||||
applicationStore.applications.filter((i) => {
|
||||
apps.filter((i) => {
|
||||
if (!filter) {
|
||||
return true;
|
||||
}
|
||||
@ -47,10 +51,7 @@
|
||||
}}
|
||||
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">
|
||||
<div class="flex-grow max-w-[90%]">
|
||||
<div class="whitespace-nowrap overflow-hidden">
|
||||
|
@ -1,59 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { applicationStore } from '$lib/ApplicationsStore.svelte';
|
||||
import { get } from '$lib/utils';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(() => {
|
||||
applicationStore.loadAplyed();
|
||||
});
|
||||
|
||||
let filter = $state('');
|
||||
</script>
|
||||
|
||||
<div class="card p-3 rounded-lg">
|
||||
<h1 class="flex gap-2">
|
||||
Applied <input bind:value={filter} placeholder="search" class="flex-grow text-blue-500" />
|
||||
</h1>
|
||||
<div class="flex flex-wrap gap-4 justify-between">
|
||||
{#each applicationStore.applyed.filter((i) => {
|
||||
if (!filter) {
|
||||
return true;
|
||||
}
|
||||
const f = new RegExp(filter, 'ig');
|
||||
|
||||
let x = i.title;
|
||||
|
||||
if (i.company) {
|
||||
x = `${x} @ ${i.company}`;
|
||||
}
|
||||
|
||||
return x.match(f);
|
||||
}) as item}
|
||||
<button
|
||||
class="card p-2 my-2 bg-slate-100 text-left"
|
||||
onclick={async () => {
|
||||
item.views = await get(`view/${item.id}`);
|
||||
item.events = await get(`events/${item.id}`);
|
||||
applicationStore.loadItem = item;
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h2 class="text-lg text-blue-500">
|
||||
{item.title}
|
||||
{#if item.company}
|
||||
<div class="text-violet-800">
|
||||
@ {item.company}
|
||||
</div>
|
||||
{/if}
|
||||
</h2>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-h-[40px]">
|
||||
</div>
|
@ -5,9 +5,8 @@
|
||||
|
||||
<div class="p-7 pb-1">
|
||||
<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('/submit')}>
|
||||
Submit text
|
||||
<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('/flair')}>
|
||||
Flair
|
||||
|
@ -1,52 +0,0 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
ApplicationStatus,
|
||||
applicationStore,
|
||||
type AsEnum
|
||||
} from '$lib/ApplicationsStore.svelte';
|
||||
import { get } from '$lib/utils';
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
|
||||
let { children, status }: { children: Snippet; status: AsEnum<typeof ApplicationStatus> } =
|
||||
$props();
|
||||
|
||||
let applications = $derived(applicationStore.all.filter((item) => item.status == status))
|
||||
|
||||
onMount(() => {
|
||||
applicationStore.loadAll();
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if applications.length > 0}
|
||||
<div class="card p-3 rounded-lg flex flex-col">
|
||||
<h1>{@render children()}</h1>
|
||||
<div class="overflow-auto flex-grow">
|
||||
{#each applications as item}
|
||||
<button
|
||||
class="card p-2 my-2 bg-slate-100 w-full text-left"
|
||||
onclick={async () => {
|
||||
item.views = await get(`view/${item.id}`);
|
||||
item.events = await get(`events/${item.id}`);
|
||||
|
||||
applicationStore.loadItem = item;
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h2 class="text-lg text-blue-500">
|
||||
{item.title}
|
||||
{#if item.company}
|
||||
<div class="text-violet-800">
|
||||
@ {item.company}
|
||||
</div>
|
||||
{/if}
|
||||
</h2>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
64
site/src/routes/SmartApplicationList.svelte
Normal file
64
site/src/routes/SmartApplicationList.svelte
Normal file
@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { applicationStore } from '$lib/ApplicationsStore.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const { title, status_id }: { title: string; status_id: string } = $props();
|
||||
|
||||
onMount(() => {
|
||||
applicationStore.loadAll();
|
||||
});
|
||||
|
||||
let filter = $state('');
|
||||
|
||||
let sorted = $derived(
|
||||
applicationStore.all.filter((i) => {
|
||||
return i.status_id === status_id;
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
{#if sorted.length > 0}
|
||||
<div class="card p-3 rounded-lg">
|
||||
<h1 class="flex gap-2">
|
||||
{title} <input bind:value={filter} placeholder="search" class="flex-grow text-blue-500" />
|
||||
</h1>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
{#each sorted.filter((i) => {
|
||||
if (!filter) {
|
||||
return true;
|
||||
}
|
||||
const f = new RegExp(filter, 'ig');
|
||||
|
||||
let x = i.title;
|
||||
|
||||
if (i.company) {
|
||||
x = `${x} @ ${i.company}`;
|
||||
}
|
||||
|
||||
return x.match(f);
|
||||
}) as item}
|
||||
<button
|
||||
class="card p-2 my-2 bg-slate-100 text-left"
|
||||
onclick={async () => {
|
||||
applicationStore.loadItem = item;
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h2 class="text-lg text-blue-500">
|
||||
{item.title}
|
||||
{#if item.company}
|
||||
<div class="text-violet-800">
|
||||
@ {item.company}
|
||||
</div>
|
||||
{/if}
|
||||
</h2>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
@ -2,7 +2,7 @@
|
||||
import { userStore } from '$lib/UserStore.svelte';
|
||||
import { get } from '$lib/utils';
|
||||
import { onMount } from 'svelte';
|
||||
import ApplicationsList from '../ApplicationsList.svelte';
|
||||
import Pill from './pill.svelte';
|
||||
|
||||
let id: string | undefined | null = $state(undefined);
|
||||
|
||||
@ -18,13 +18,15 @@
|
||||
name: string;
|
||||
description: string;
|
||||
color: string;
|
||||
sort: number;
|
||||
showFullDescription: number;
|
||||
};
|
||||
|
||||
type Application = {
|
||||
recruiter: string;
|
||||
message: string;
|
||||
company: string;
|
||||
agency: boolean,
|
||||
agency: boolean;
|
||||
flairs: SimpleFlair[];
|
||||
};
|
||||
|
||||
@ -40,16 +42,19 @@
|
||||
}
|
||||
|
||||
application.flairs.sort((a, b) => {
|
||||
if (a.description && b.description) {
|
||||
return 0;
|
||||
const aDesc = a.description && a.showFullDescription === 1;
|
||||
const bDesc = b.description && b.showFullDescription === 1;
|
||||
|
||||
if (aDesc && bDesc) {
|
||||
return b.sort - a.sort;
|
||||
}
|
||||
if (a.description) {
|
||||
if (aDesc) {
|
||||
return -1;
|
||||
}
|
||||
if (b.description) {
|
||||
if (bDesc) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return b.sort - a.sort;
|
||||
});
|
||||
|
||||
loadFlairs();
|
||||
@ -82,7 +87,7 @@
|
||||
</svelte:head>
|
||||
|
||||
<div class="flex items-center w-full flex-col">
|
||||
<div class="py-10 w-[190mm]">
|
||||
<div class="py-5 pb-0 w-[190mm] print:py-0 print:pt-4">
|
||||
<div class="bg-white rounded-lg p-3">
|
||||
<div class="w-full flex">
|
||||
<h1 class="text-black text-5xl">Andre Henriques</h1>
|
||||
@ -113,7 +118,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex py-9">
|
||||
<div class="w-full flex py-3 print:pb-0">
|
||||
<div>
|
||||
I am a dedicated and versatile programmer with four years of professional
|
||||
experience. <br />
|
||||
@ -128,9 +133,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if application}
|
||||
{#if !application.agency}
|
||||
<h2 class="text-white p-3 text-4xl">
|
||||
<h2 class="text-white p-6 print:p-0 text-4xl print:text-3xl">
|
||||
👋 Hello
|
||||
{#if application.recruiter}
|
||||
<span class="font-bold">{application.recruiter}</span> @
|
||||
@ -139,6 +145,8 @@
|
||||
recruiter @ <span class="font-bold">{application.company}</span>
|
||||
{/if}
|
||||
</h2>
|
||||
{:else}
|
||||
<div class="p-5 print:hidden"></div>
|
||||
{/if}
|
||||
|
||||
{#if application.message}
|
||||
@ -153,41 +161,44 @@
|
||||
{#if application.flairs.length > 0}
|
||||
<div class="p-3 bg-white w-[190mm] rounded-lg">
|
||||
<h1 class="flex gap-5 items-end">
|
||||
Your Ad & My skills {#if flairs.length > 0}<input
|
||||
placeholder="Search other skills!"
|
||||
Skills {#if flairs.length > 0}<input
|
||||
placeholder="Click here to search skills!"
|
||||
class="flex-grow text-blue-500 print:hidden"
|
||||
bind:value={otherSearch}
|
||||
/>
|
||||
<span class="hidden print:inline text-slate-600 text-sm"><a href="https://www.andr3h3nriqu3s.com/cv?id={id}">Search other skills!</a></span>
|
||||
<span class="hidden print:inline text-slate-600 text-sm"
|
||||
><a href="https://www.andr3h3nriqu3s.com/cv?id={id}"
|
||||
>Click here to search other skills!</a
|
||||
></span
|
||||
>
|
||||
{/if}
|
||||
</h1>
|
||||
<div class="flex flex-wrap gap-2 py-2">
|
||||
<div class="flex flex-wrap gap-2 py-2 print:py-0">
|
||||
{#if otherSearch === ''}
|
||||
{#each application.flairs as flair}
|
||||
<div class="min-w-0 {flair.description ? 'flex-grow w-full' : ''}">
|
||||
{#if flair.description}
|
||||
{@const hasDesc = flair.description && flair.showFullDescription === 1}
|
||||
<div class="min-w-0 {hasDesc ? 'flex-grow w-full' : ''}">
|
||||
{#if hasDesc}
|
||||
<div
|
||||
class="p-2 rounded-lg forced-color-adjust-none"
|
||||
class="p-2 rounded-lg forced-color-adjust-none relative print:py-0"
|
||||
style="background: {flair.color};"
|
||||
>
|
||||
<div
|
||||
class="rounded-lg print:p-2 print:inline-block"
|
||||
style="background: {flair.color}; print-color-adjust: exact !important;"
|
||||
<Pill
|
||||
text-size="text-base print:text-sm"
|
||||
class="print:inline-block text-2xl py-0 mb-2"
|
||||
color={flair.color}
|
||||
>
|
||||
{flair.name}
|
||||
</div>
|
||||
</Pill>
|
||||
<span class="hidden print:inline">:</span>
|
||||
<div class="bg-white my-1 print:inline p-1 rounded-md">
|
||||
{flair.description}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="p-2 rounded-lg forced-color-adjust-none"
|
||||
style="background: {flair.color}; print-color-adjust: exact !important;"
|
||||
>
|
||||
<Pill text-size="text-base print:text-sm" color={flair.color}>
|
||||
{flair.name}
|
||||
</div>
|
||||
</Pill>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
@ -234,20 +245,18 @@
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<div class="p-2"></div>
|
||||
|
||||
<div class="w-[190mm]">
|
||||
<h2 class="pb-2 px-2 print:px-5 text-4xl print:text-3xl font-bold text-white">
|
||||
<h2 class="p-3 print:p-0 text-4xl print:text-2xl font-bold print:font-normal text-white">
|
||||
Work Expericence
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="p-2"></div>
|
||||
|
||||
<div class="w-[100vw] flex items-center flex-col">
|
||||
<div class="p-3 bg-white w-[190mm] rounded-lg">
|
||||
<div class="p-3 print:p-0 bg-white w-[190mm] rounded-lg">
|
||||
<h1>Senior Software Developer @ Planum Solucoes</h1>
|
||||
<div class="ml-5">
|
||||
<h2>4 years - May 2020 - Present</h2>
|
||||
<div class="ml-5">
|
||||
<h3>Developed various projects:</h3>
|
||||
<ul class="pl-5 list-disc">
|
||||
<li>Developing various websites using React and Svelte.</li>
|
||||
@ -255,30 +264,20 @@
|
||||
<li>Implemented an ORM system using Java Reflection</li>
|
||||
<li>Implemented automatic deployment with GitLab CI/CD tools.</li>
|
||||
<li>Linux Server Administration</li>
|
||||
<li>Technologies used: React, WebRTC, WebSockets, Rest, Google Maps AP</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-2"></div>
|
||||
<div class="p-2 print:p-1"></div>
|
||||
|
||||
<div class="w-[100vw] flex items-center flex-col">
|
||||
<div class="text-black w-[190mm] bg-white p-4 rounded-lg">
|
||||
<div class="text-black w-[190mm] bg-white p-4 print:p-0 rounded-lg">
|
||||
<div>
|
||||
<div>
|
||||
<h1>Associate Devops Engineer @ Sky UK</h1>
|
||||
<div class="ml-5">
|
||||
<h2>1 year - July 2022 - June 2023</h2>
|
||||
<h3>Working with:</h3>
|
||||
<ul class="pl-5 list-disc">
|
||||
<li>Python</li>
|
||||
<li>Jenkins</li>
|
||||
<li>GitLab CI</li>
|
||||
<li>Ansible</li>
|
||||
<li>Docker</li>
|
||||
</ul>
|
||||
<h3>Associated Software Developer / DevOps Engineer:</h3>
|
||||
<div class="ml-2">
|
||||
<ul class="pl-5 list-disc">
|
||||
<li>Developed web-based tools for the DevOps team to use</li>
|
||||
<li>
|
||||
@ -298,13 +297,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-5"></div>
|
||||
|
||||
<div class="bg-white p-3 text-black rounded-lg w-[190mm]">
|
||||
<h2 class="pb-2 text-3xl">Education</h2>
|
||||
<div class="w-[190mm]">
|
||||
<h2 class="p-3 print:p-0 text-4xl print:text-xl font-bold print:font-normal text-white">
|
||||
Education
|
||||
</h2>
|
||||
</div>
|
||||
<div class="bg-white p-2 text-black rounded-lg w-[190mm] print:text-sm">
|
||||
<div>
|
||||
<div>
|
||||
<h1>Bachelors of science in Computer Science @ University of Surrey</h1>
|
||||
<h1>BCompSc with First Class Honours @ University of Surrey</h1>
|
||||
<div class="ml-5">
|
||||
<h2>July 2020 - June 2024</h2>
|
||||
</div>
|
||||
@ -312,6 +313,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-3 print:hidden"></div>
|
||||
|
||||
<!--div class="p-5"></div>
|
||||
<div>TODO: Previous projetcs</div -->
|
||||
<!-- div class="p-5"></div>
|
||||
|
17
site/src/routes/cv/pill.svelte
Normal file
17
site/src/routes/cv/pill.svelte
Normal file
@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
const {
|
||||
children,
|
||||
color = 'rgb(134 239 172)',
|
||||
class: className = '',
|
||||
'text-size': textSize = 'text-xs'
|
||||
}: { children: Snippet; color?: string; class?: string; 'text-size'?: string } = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
class="px-2 py-1 rounded-lg forced-color-adjust-none bg-green-300 {className} {textSize}"
|
||||
style="print-color-adjust: exact !important; background-color: {color};"
|
||||
>
|
||||
{@render children()}
|
||||
</span>
|
@ -95,6 +95,18 @@
|
||||
<label class="flabel" for="color">Color</label>
|
||||
<input required type="color" id="color" bind:value={edit.color} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label class="flabel" for="color">Show description</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="showDescription"
|
||||
checked={edit.showFullDescription === 1}
|
||||
onchange={() => {
|
||||
if (!edit) return;
|
||||
edit.showFullDescription = (edit.showFullDescription + 1) % 2;
|
||||
}}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label class="flabel" for="name">Name</label>
|
||||
<input required id="name" class="finput w-full" bind:value={edit.name} />
|
||||
@ -106,12 +118,15 @@
|
||||
<fieldset>
|
||||
<label class="flabel" for="description">Description</label>
|
||||
<textarea
|
||||
required
|
||||
id="description"
|
||||
class="finput w-full"
|
||||
bind:value={edit.description}
|
||||
></textarea>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label class="flabel" for="sort">Sort</label>
|
||||
<input type="number" class="finput w-full" bind:value={edit.sort} />
|
||||
</fieldset>
|
||||
<div class="btns w-full">
|
||||
<button class="btn-confirm">Save</button>
|
||||
</div>
|
||||
|
@ -8,11 +8,6 @@
|
||||
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
|
||||
@ -47,7 +42,8 @@
|
||||
height: 4,
|
||||
name: 'Created',
|
||||
icon: 'plus',
|
||||
permission: 1
|
||||
permission: 1,
|
||||
visible: false
|
||||
} as Node,
|
||||
...nodesRequest
|
||||
];
|
||||
@ -72,7 +68,7 @@
|
||||
y: a.target_y
|
||||
}
|
||||
}));
|
||||
console.log("test", linkRequests)
|
||||
console.log('test', linkRequests);
|
||||
} catch (e) {
|
||||
console.log('TODO inform user', e);
|
||||
}
|
||||
@ -191,7 +187,8 @@
|
||||
width,
|
||||
icon: icon_list[Math.floor(Math.random() * icon_list.length)],
|
||||
name: 'New Status',
|
||||
permission: 0
|
||||
permission: 0,
|
||||
visible: true
|
||||
} as Node);
|
||||
nodes.push(result);
|
||||
// Tell svelte that nodes is updated
|
||||
@ -422,12 +419,15 @@
|
||||
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)
|
||||
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)
|
||||
console.log('TODO inform the user', e);
|
||||
}
|
||||
}}
|
||||
onNodePointClick={async (inNodeX, inNodeY) => {
|
||||
|
@ -94,6 +94,43 @@
|
||||
>
|
||||
<div class="bi bi-x mt-[2px]"></div>
|
||||
</button>
|
||||
<!-- Controll items -->
|
||||
<div class="absolute flex gap-1 top-1 left-1">
|
||||
<button
|
||||
class="text-purple-400 hover:text-purple-800 rounded-full h-[20px] w-[20px] text-center leading-none"
|
||||
onclick={async () => {
|
||||
node.visible = !node.visible;
|
||||
try {
|
||||
await put('user/status/node', node);
|
||||
} catch (e) {
|
||||
console.log('TODO inform the user', e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if node.visible}
|
||||
<div class="bi bi-eye-fill mt-[2px]"></div>
|
||||
{:else}
|
||||
<div class="bi bi-eye-slash-fill mt-[2px]"></div>
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
class="text-purple-400 hover:text-purple-800 rounded-full h-[20px] w-[20px] text-center leading-none"
|
||||
onclick={async () => {
|
||||
node.endable = !node.endable;
|
||||
try {
|
||||
await put('user/status/node', node);
|
||||
} catch (e) {
|
||||
console.log('TODO inform the user', e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if node.endable}
|
||||
<div class="bi bi-align-end mt-[2px]"></div>
|
||||
{:else}
|
||||
<div class="bi bi-align-start mt-[2px]"></div>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
export type Node = {
|
||||
id: string,
|
||||
user_id: string,
|
||||
id: string;
|
||||
user_id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
@ -9,37 +9,42 @@ export type Node = {
|
||||
icon: string;
|
||||
// 1 disables editing
|
||||
permission: number;
|
||||
visible: boolean;
|
||||
endable: boolean;
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
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) {
|
||||
export function extractLinkNodePosX(
|
||||
canvasX: (x: number) => number,
|
||||
node: LinkNode,
|
||||
grid_size: number
|
||||
) {
|
||||
if (node.x === -1) {
|
||||
return canvasX(node.node.x);
|
||||
}
|
||||
@ -49,7 +54,11 @@ export function extractLinkNodePosX(canvasX: (x: number) => number, node: LinkNo
|
||||
return canvasX(node.node.x) + node.x * grid_size + grid_size / 2;
|
||||
}
|
||||
|
||||
export function extractLinkNodePosY(canvasY: (x: number) => number, node: LinkNode, grid_size: number) {
|
||||
export function extractLinkNodePosY(
|
||||
canvasY: (x: number) => number,
|
||||
node: LinkNode,
|
||||
grid_size: number
|
||||
) {
|
||||
if (node.y === -1) {
|
||||
return canvasY(node.node.y);
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
async function submit() {
|
||||
try {
|
||||
await post('application/text', text);
|
||||
applicationStore.loadApplications(true);
|
||||
applicationStore.loadAll(true);
|
||||
goto('/');
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
63
site/src/routes/work-area/AutoDropZone.svelte
Normal file
63
site/src/routes/work-area/AutoDropZone.svelte
Normal file
@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
||||
import { statusStore } from '$lib/Types.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import DropZone from './DropZone.svelte';
|
||||
import { deleteR, put } from '$lib/utils';
|
||||
|
||||
let { index = $bindable() }: { index: number | undefined } = $props();
|
||||
|
||||
const derivedItem: Application | undefined = $derived(applicationStore.all[index ?? -1]);
|
||||
|
||||
async function moveStatus(status_id: string, endable?: boolean) {
|
||||
// Deactivate active item
|
||||
try {
|
||||
await put('application/status', {
|
||||
id: derivedItem?.id,
|
||||
status_id: status_id
|
||||
});
|
||||
} catch (e) {
|
||||
// TODO: Show User
|
||||
console.log('info data', e);
|
||||
return;
|
||||
}
|
||||
applicationStore.all[index ?? -1].status_id = status_id;
|
||||
applicationStore.reset();
|
||||
if (endable) {
|
||||
index = undefined;
|
||||
return;
|
||||
}
|
||||
applicationStore.loadItem = derivedItem;
|
||||
applicationStore.loadItemOpen = false;
|
||||
}
|
||||
|
||||
async function remove() {
|
||||
if (!derivedItem) return;
|
||||
// Deactivate active item
|
||||
try {
|
||||
await deleteR(`application/${derivedItem.id}`);
|
||||
} catch (e) {
|
||||
// TODO: Show User
|
||||
console.log('info data', e);
|
||||
return;
|
||||
}
|
||||
applicationStore.removeByID(derivedItem.id);
|
||||
index = undefined;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
statusStore.load();
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if applicationStore.dragging && derivedItem}
|
||||
<div class="flex w-full flex-grow rounded-lg p-3 gap-2 absolute bottom-0 left-0 right-0 bg-white">
|
||||
{#each statusStore.dirLinks[derivedItem.status_id] as node}
|
||||
<DropZone icon={node.icon} ondrop={() => moveStatus(node.id, node.endable)}
|
||||
>{node.name}</DropZone
|
||||
>
|
||||
{/each}
|
||||
|
||||
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}>Delete it</DropZone>
|
||||
</div>
|
||||
{/if}
|
@ -1,26 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { applicationStore } from '$lib/ApplicationsStore.svelte';
|
||||
|
||||
let { save, company = $bindable() }: { company: string; save: () => void } = $props();
|
||||
let { save, index }: { index: number; save: () => void } = $props();
|
||||
|
||||
let companies = $derived(new Set(applicationStore.all.map((a) => a.company)));
|
||||
|
||||
let fcomps = $derived(
|
||||
company === ''
|
||||
applicationStore.all[index].company === ''
|
||||
? []
|
||||
: [...companies.values()].filter((a) => {
|
||||
// TODO improve this a lot I want to make like matching algo
|
||||
return a.match(company) && a !== company;
|
||||
return (
|
||||
a.match(applicationStore.all[index].company) &&
|
||||
a !== applicationStore.all[index].company
|
||||
);
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<fieldset class="grow">
|
||||
<label class="flabel" for="title">Company</label>
|
||||
<input class="finput" id="title" bind:value={company} onchange={save} />
|
||||
<input
|
||||
class="finput"
|
||||
id="title"
|
||||
bind:value={applicationStore.all[index].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}>
|
||||
<button
|
||||
class="bg-blue-200 px-3 p-1 min-h-0 rounded-lg"
|
||||
onclick={() => (applicationStore.all[index].company = comp)}
|
||||
>
|
||||
{comp}
|
||||
</button>
|
||||
{/each}
|
||||
|
@ -1,198 +0,0 @@
|
||||
<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}
|
@ -22,7 +22,7 @@
|
||||
onreload();
|
||||
} catch (e) {
|
||||
// Show message to the user
|
||||
console.log(e);
|
||||
console.log('TODO inform the user', e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { ApplicationStatus, ApplicationStatusMaping, type Application } from '$lib/ApplicationsStore.svelte';
|
||||
import { post, put } from '$lib/utils';
|
||||
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
||||
import { statusStore } from '$lib/Types.svelte';
|
||||
import { post } from '$lib/utils';
|
||||
|
||||
let {
|
||||
application,
|
||||
@ -13,43 +14,21 @@
|
||||
} = $props();
|
||||
|
||||
let filter = $state('');
|
||||
let applications: Application[] = $state([]);
|
||||
|
||||
async function getApplicationList() {
|
||||
const app: Application[] = await post('application/list', {});
|
||||
applications = app.filter((a) => a.id != application.id);
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
getApplicationList();
|
||||
});
|
||||
|
||||
async function submit(item: Application) {
|
||||
try {
|
||||
application.linked_application = item.id;
|
||||
await put('application/update', application);
|
||||
await put('application/status', {
|
||||
id: application.id,
|
||||
status: ApplicationStatus.LinkedApplication
|
||||
});
|
||||
await post(`application/link/application/${application.id}/${item.id}`, {});
|
||||
applicationStore.removeByID(application.id);
|
||||
dialog.close();
|
||||
onreload(item);
|
||||
} catch (e) {
|
||||
// Show message to the user
|
||||
// TODO: Show message to the user
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<dialog class="card max-w-[50vw]" bind:this={dialog}>
|
||||
<div class="flex">
|
||||
<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">
|
||||
{#each applications.filter((i) => {
|
||||
let internal = $derived(
|
||||
applicationStore.all.filter((i) => {
|
||||
if (!filter) {
|
||||
return true;
|
||||
}
|
||||
@ -62,7 +41,19 @@
|
||||
}
|
||||
|
||||
return x.match(f);
|
||||
}) as item}
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<dialog class="card max-w-[50vw]" bind:this={dialog}>
|
||||
<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">
|
||||
{#each internal as item}
|
||||
<div class="card p-2 my-2 bg-slate-100 max-w-full" role="none">
|
||||
<button class="text-left max-w-full" type="button" onclick={() => submit(item)}>
|
||||
<h2 class="text-lg text-blue-500">
|
||||
@ -74,7 +65,7 @@
|
||||
{/if}
|
||||
</h2>
|
||||
<span>
|
||||
{ApplicationStatusMaping[item.status]}
|
||||
{statusStore.nodesR[item.status_id].name}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { Application } from '$lib/ApplicationsStore.svelte';
|
||||
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
||||
import { post, preventDefault } from '$lib/utils';
|
||||
|
||||
let {
|
||||
const {
|
||||
onreload
|
||||
}: {
|
||||
onreload: (item: Application) => void;
|
||||
@ -12,20 +12,50 @@
|
||||
|
||||
let link = $state('');
|
||||
|
||||
let dialogAddMultiple = $state(false);
|
||||
|
||||
let inputField: HTMLTextAreaElement;
|
||||
|
||||
async function createApplication() {
|
||||
try {
|
||||
if (dialogAddMultiple) {
|
||||
const r: Application[] = await post('application/text', {
|
||||
text: link
|
||||
});
|
||||
for (const app of r) {
|
||||
applicationStore.all.push(app);
|
||||
}
|
||||
if (r.length > 0) {
|
||||
onreload(r[0]);
|
||||
}
|
||||
dialogElement.close();
|
||||
link = '';
|
||||
return;
|
||||
}
|
||||
const r: Application = await post('application/link', {
|
||||
text: link
|
||||
});
|
||||
applicationStore.all.push(r);
|
||||
onreload(r);
|
||||
dialogElement.close()
|
||||
dialogElement.close();
|
||||
link = '';
|
||||
} catch (e) {
|
||||
console.log('Inform the user', e);
|
||||
console.log('TODO: Inform the user', e);
|
||||
}
|
||||
}
|
||||
|
||||
function docKey(e: KeyboardEvent) {
|
||||
if (e.ctrlKey && e.code === 'KeyN') {
|
||||
if (e.ctrlKey && e.shiftKey && e.code === 'KeyN') {
|
||||
dialogAddMultiple = true;
|
||||
dialogElement.showModal();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
window.requestAnimationFrame(() => {
|
||||
inputField?.focus();
|
||||
});
|
||||
return;
|
||||
} else if (e.ctrlKey && e.code === 'KeyN') {
|
||||
dialogAddMultiple = false;
|
||||
dialogElement.showModal();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
@ -41,11 +71,34 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<dialog class="card max-w-[50vw]" bind:this={dialogElement}>
|
||||
<dialog
|
||||
class="card max-w-[50vw]"
|
||||
bind:this={dialogElement}
|
||||
onclose={() => {
|
||||
dialogAddMultiple = false;
|
||||
}}
|
||||
>
|
||||
<form onsubmit={preventDefault(createApplication)}>
|
||||
<fieldset>
|
||||
<label class="flabel" for="title">Link</label>
|
||||
<label class="flabel" for="title"
|
||||
>{#if dialogAddMultiple}Email text{:else}Link{/if}</label
|
||||
>
|
||||
{#if dialogAddMultiple}
|
||||
<textarea
|
||||
class="finput"
|
||||
id="title"
|
||||
bind:value={link}
|
||||
required
|
||||
bind:this={inputField}
|
||||
></textarea>
|
||||
{:else}
|
||||
<input class="finput" id="title" bind:value={link} required />
|
||||
{/if}
|
||||
</fieldset>
|
||||
<div class="flex justify-end">
|
||||
{#if dialogAddMultiple}
|
||||
<button type="submit" class="btn-primary">Submit</button>
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
@ -1,16 +1,18 @@
|
||||
<script lang="ts">
|
||||
import type { Application } from '$lib/ApplicationsStore.svelte';
|
||||
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
||||
import { post, preventDefault } from '$lib/utils';
|
||||
|
||||
let {
|
||||
dialog = $bindable(),
|
||||
onreload,
|
||||
id
|
||||
id,
|
||||
index
|
||||
}: {
|
||||
dialog: HTMLDialogElement;
|
||||
onreload: (item: Application) => void;
|
||||
id: string;
|
||||
openWindow?: Window | null;
|
||||
index?: number;
|
||||
} = $props();
|
||||
|
||||
const data = $state({
|
||||
@ -27,6 +29,9 @@
|
||||
});
|
||||
data.url = '';
|
||||
dialog.close();
|
||||
if (newItem.id !== applicationStore.all[index ?? -1].id) {
|
||||
applicationStore.removeByID(applicationStore.all[index ?? -1].id);
|
||||
}
|
||||
onreload(newItem);
|
||||
} catch (e) {
|
||||
// Show message to the user
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { ApplicationStatusMaping, type Application } from '$lib/ApplicationsStore.svelte';
|
||||
import { post } from '$lib/utils';
|
||||
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
||||
import { statusStore } from '$lib/Types.svelte';
|
||||
|
||||
let {
|
||||
application,
|
||||
@ -11,18 +11,11 @@
|
||||
} = $props();
|
||||
|
||||
let filter = $state('');
|
||||
let applications: Application[] = $state([]);
|
||||
|
||||
let dialogElement: HTMLDialogElement;
|
||||
|
||||
async function getApplicationList() {
|
||||
applications = await post('application/list', {});
|
||||
}
|
||||
|
||||
function docKey(e: KeyboardEvent) {
|
||||
if (e.ctrlKey && e.code === 'KeyK') {
|
||||
applications = [];
|
||||
getApplicationList();
|
||||
dialogElement.showModal();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
@ -38,7 +31,10 @@
|
||||
});
|
||||
|
||||
let internal = $derived(
|
||||
applications.filter((i) => {
|
||||
applicationStore.all.filter((i) => {
|
||||
if (i.linked_application) {
|
||||
return false;
|
||||
}
|
||||
if (application && i.id == application.id) {
|
||||
return false;
|
||||
}
|
||||
@ -96,7 +92,7 @@
|
||||
</div>
|
||||
</h2>
|
||||
<span>
|
||||
{ApplicationStatusMaping[item.status]}
|
||||
{statusStore.nodesR[item.status_id]?.name ?? 'NOT FOUND'}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -2,13 +2,11 @@
|
||||
import {
|
||||
type Application,
|
||||
type ApplicationEvent,
|
||||
ApplicationStatus,
|
||||
EventType,
|
||||
ApplicationStatusIconMaping,
|
||||
ApplicationStatusMaping
|
||||
EventType
|
||||
} from '$lib/ApplicationsStore.svelte';
|
||||
import { statusStore } from '$lib/Types.svelte';
|
||||
|
||||
let { application, showAll }: { application: Application, showAll: boolean } = $props();
|
||||
let { application, showAll }: { application: Application; showAll: boolean } = $props();
|
||||
|
||||
let events: (ApplicationEvent & { timeDiff: string })[] = $state([]);
|
||||
|
||||
@ -36,17 +34,10 @@
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
let status: number | undefined = undefined;
|
||||
|
||||
let lastEvent: ApplicationEvent | undefined = undefined;
|
||||
|
||||
let _events: typeof events = [];
|
||||
|
||||
let checkArray: (number | undefined)[] = [
|
||||
ApplicationStatus.WorkingOnIt,
|
||||
ApplicationStatus.ToApply
|
||||
];
|
||||
|
||||
for (let event of application.events) {
|
||||
let time = '';
|
||||
if (lastEvent) {
|
||||
@ -55,25 +46,6 @@
|
||||
time = calcDiff(d2 - d1);
|
||||
}
|
||||
|
||||
if (event.event_type === EventType.Creation) {
|
||||
status = ApplicationStatus.ToApply;
|
||||
}
|
||||
if (event.event_type !== EventType.StatusUpdate || showAll) {
|
||||
if (time) {
|
||||
_events[_events.length - 1].timeDiff = time;
|
||||
}
|
||||
_events.push({ ...event, timeDiff: '' });
|
||||
lastEvent = event;
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
checkArray.includes(status) &&
|
||||
checkArray.includes(event.new_status) &&
|
||||
lastEvent?.event_type !== EventType.Creation
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastEvent) {
|
||||
let d1 = new Date(lastEvent.time).getTime();
|
||||
let d2 = new Date(event.time).getTime();
|
||||
@ -84,26 +56,21 @@
|
||||
_events[_events.length - 1].timeDiff = time;
|
||||
}
|
||||
|
||||
status = event.new_status;
|
||||
_events.push({ ...event, timeDiff: '' });
|
||||
lastEvent = event;
|
||||
}
|
||||
|
||||
if (_events.length > 0 && !endable.includes(_events[_events.length - 1].new_status)) {
|
||||
// Todo endable
|
||||
/*
|
||||
if (_events.length > 0 && !endable.includes(_events[_events.length - 1].new_status_id)) {
|
||||
let d1 = new Date(_events[_events.length - 1].time).getTime();
|
||||
let d2 = new Date().getTime();
|
||||
_events[_events.length - 1].timeDiff = calcDiff(d2 - d1);
|
||||
}
|
||||
*/
|
||||
|
||||
events = _events;
|
||||
});
|
||||
|
||||
let endable: number[] = [
|
||||
ApplicationStatus.Expired,
|
||||
ApplicationStatus.Ignore,
|
||||
ApplicationStatus.ApplyedButSaidNo,
|
||||
ApplicationStatus.LinkedApplication
|
||||
];
|
||||
</script>
|
||||
|
||||
{#if events.length > 0}
|
||||
@ -118,28 +85,24 @@
|
||||
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}
|
||||
<span
|
||||
title={`Created @\n ${new Date(event.time).toLocaleString()}`}
|
||||
class="bi bi-plus"
|
||||
<span title={`Created @\n ${new Date(event.time).toLocaleString()}`} class="bi bi-plus"
|
||||
></span>
|
||||
{:else if event.event_type == EventType.View}
|
||||
<span
|
||||
title={`Viewed @\n ${new Date(event.time).toLocaleString()}`}
|
||||
class="bi bi-eye"
|
||||
<span title={`Viewed @\n ${new Date(event.time).toLocaleString()}`} class="bi bi-eye"
|
||||
></span>
|
||||
{:else}
|
||||
<span
|
||||
title={`${ApplicationStatusMaping[event.new_status]}\n${new Date(
|
||||
title={`${statusStore.nodesR[event.new_status_id].name}\n${new Date(
|
||||
event.time
|
||||
).toLocaleString()}`}
|
||||
class="bi bi-{ApplicationStatusIconMaping[event.new_status]}"
|
||||
class="bi bi-{statusStore.nodesR[event.new_status_id].icon}"
|
||||
></span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if i != events.length - 1 || !endable.includes(event.new_status)}
|
||||
<div
|
||||
class="min-w-[70px] h-[18px] bg-blue-500 -mx-[10px] px-[20px] flex-grow text-center"
|
||||
>
|
||||
<!-- TODO -->
|
||||
<!-- || !endable.includes(event.new_status) -->
|
||||
{#if i != events.length - 1 || !statusStore.nodesR[event.new_status_id].endable}
|
||||
<div class="min-w-[70px] h-[18px] bg-blue-500 -mx-[10px] px-[20px] flex-grow text-center">
|
||||
{#if event.timeDiff}
|
||||
<div class="-mt-[3px] text-white">
|
||||
<span class="bi bi-clock"></span>
|
||||
|
@ -1,10 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
ApplicationStatus,
|
||||
ApplicationStatusMaping,
|
||||
applicationStore,
|
||||
type Application
|
||||
} from '$lib/ApplicationsStore.svelte';
|
||||
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
||||
|
||||
import { put, preventDefault, post, get } from '$lib/utils';
|
||||
import { onMount } from 'svelte';
|
||||
@ -16,10 +11,15 @@
|
||||
import SearchApplication from './SearchApplication.svelte';
|
||||
import NewApplication from './NewApplication.svelte';
|
||||
import Timeline from './Timeline.svelte';
|
||||
import DropingZone from './DropingZone.svelte';
|
||||
import CompanyField from './CompanyField.svelte';
|
||||
import AutoDropZone from './AutoDropZone.svelte';
|
||||
import { statusStore } from '$lib/Types.svelte';
|
||||
import { thresholdFreedmanDiaconis } from 'd3';
|
||||
|
||||
let activeItem: Application | undefined = $state();
|
||||
// Not this represents the index in the store array
|
||||
let activeItem: number | undefined = $state();
|
||||
|
||||
let derivedItem: Application | undefined = $derived(applicationStore.all[activeItem ?? -1]);
|
||||
|
||||
let extractTokens: HTMLDialogElement;
|
||||
let changeUrl: HTMLDialogElement;
|
||||
@ -32,45 +32,30 @@
|
||||
|
||||
let drag = $state(true);
|
||||
|
||||
async function activate(item?: Application) {
|
||||
async function activate(item?: Application, ow = true) {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
if (activeItem && activeItem.id !== item.id && activeItem.status === 1) {
|
||||
// Deactivate active item
|
||||
try {
|
||||
await put('application/status', {
|
||||
id: activeItem.id,
|
||||
status: 0
|
||||
});
|
||||
} catch (e) {
|
||||
// Show User
|
||||
console.log('info data', e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.status === 0) {
|
||||
if (ow && item.id !== derivedItem?.id) {
|
||||
openWindow(item.url);
|
||||
openCV(item.id);
|
||||
}
|
||||
openCV(item.id);
|
||||
|
||||
try {
|
||||
await put('application/status', {
|
||||
id: item.id,
|
||||
status: ApplicationStatus.WorkingOnIt
|
||||
});
|
||||
activeItem = await get('application/active');
|
||||
if (!activeItem?.unique_url) {
|
||||
const toLoadItem: Application = await get(`application/${item.id}`);
|
||||
if (!toLoadItem?.simple_url) {
|
||||
window.requestAnimationFrame(() => {
|
||||
changeUrl.showModal();
|
||||
});
|
||||
}
|
||||
activeItem = applicationStore.getIndexById(toLoadItem.id);
|
||||
applicationStore.all[activeItem ?? -1] = toLoadItem;
|
||||
applicationStore.reset();
|
||||
} catch (e) {
|
||||
// Show User
|
||||
console.log('info data', e);
|
||||
}
|
||||
applicationStore.loadApplications(true);
|
||||
|
||||
autoExtData = false;
|
||||
lastExtData = undefined;
|
||||
@ -90,7 +75,7 @@
|
||||
|
||||
function openCV(id?: string) {
|
||||
window.open(
|
||||
`/cv?id=${id ?? activeItem!.id}`,
|
||||
`/cv?id=${id ?? derivedItem!.id}`,
|
||||
'cv viwer',
|
||||
`location,height=${window.screen.height},width=${window.screen.width},scrollbars,status,toolbar,menubar,popup`
|
||||
);
|
||||
@ -100,8 +85,8 @@
|
||||
// Make the CV open on a new page
|
||||
//
|
||||
function docKey(e: KeyboardEvent) {
|
||||
if (activeItem && e.ctrlKey && e.code === 'KeyO') {
|
||||
openCV(activeItem.id);
|
||||
if (derivedItem && e.ctrlKey && e.code === 'KeyO') {
|
||||
openCV(derivedItem.id);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return;
|
||||
@ -117,18 +102,6 @@
|
||||
//
|
||||
//
|
||||
|
||||
async function loadActive() {
|
||||
try {
|
||||
activeItem = await get('application/active');
|
||||
if (activeItem?.unique_url === null) {
|
||||
changeUrl.showModal();
|
||||
}
|
||||
} catch (e) {
|
||||
// Show User
|
||||
console.log('info data', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Load Ext
|
||||
$effect(() => {
|
||||
function onMessage(e: MessageEvent) {
|
||||
@ -150,21 +123,22 @@
|
||||
});
|
||||
|
||||
function setExtData() {
|
||||
if (!lastExtData || !activeItem) return;
|
||||
activeItem.title = lastExtData.jobTitle;
|
||||
activeItem.company = lastExtData.company;
|
||||
activeItem.payrange = lastExtData.money;
|
||||
if (!lastExtData || activeItem === undefined || !derivedItem) return;
|
||||
applicationStore.all[activeItem].title = lastExtData.jobTitle;
|
||||
applicationStore.all[activeItem].company = lastExtData.company;
|
||||
applicationStore.all[activeItem].payrange = lastExtData.money;
|
||||
window.requestAnimationFrame(() => {
|
||||
save().then(async () => {
|
||||
if (activeItem === undefined) return;
|
||||
if (!lastExtData.description) {
|
||||
lastExtData = undefined;
|
||||
return;
|
||||
}
|
||||
await post('application/text/flair', {
|
||||
id: activeItem?.id ?? '',
|
||||
id: derivedItem.id ?? '',
|
||||
text: lastExtData.description
|
||||
});
|
||||
loadActive();
|
||||
activate(derivedItem, false);
|
||||
lastExtData = undefined;
|
||||
});
|
||||
});
|
||||
@ -172,20 +146,26 @@
|
||||
|
||||
onMount(() => {
|
||||
userStore.checkLogin();
|
||||
loadActive();
|
||||
statusStore.load();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!applicationStore.loadItem) {
|
||||
return;
|
||||
}
|
||||
activeItem = applicationStore.loadItem;
|
||||
(async () => {
|
||||
await activate(applicationStore.loadItem, applicationStore.loadItemOpen);
|
||||
applicationStore.loadItem = undefined;
|
||||
})();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
applicationStore.loadAll();
|
||||
});
|
||||
|
||||
async function save() {
|
||||
try {
|
||||
await put('application/update', activeItem);
|
||||
await put('application/update', applicationStore.all[activeItem ?? -1]);
|
||||
} catch (e) {
|
||||
// Show User
|
||||
console.log('info data', e);
|
||||
@ -194,7 +174,7 @@
|
||||
|
||||
async function resetUrl() {
|
||||
try {
|
||||
activeItem = await post(`application/reset/url/${activeItem?.id}`, {});
|
||||
activeItem = await post(`application/reset/url/${derivedItem?.id}`, {});
|
||||
} catch (e) {
|
||||
// Show User
|
||||
console.log('info data', e);
|
||||
@ -203,11 +183,11 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col w-full gap-2 min-w-0 relative" role="none">
|
||||
{#if activeItem && (!applicationStore.dragging || applicationStore.dragging?.id === activeItem.id)}
|
||||
{#if activeItem !== undefined && (!applicationStore.dragging || applicationStore.dragging?.id === derivedItem.id)}
|
||||
<div
|
||||
draggable={drag}
|
||||
ondrag={() => {
|
||||
applicationStore.dragStart(activeItem);
|
||||
applicationStore.dragStart(derivedItem);
|
||||
}}
|
||||
ondragend={() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
@ -218,35 +198,34 @@
|
||||
class="flex flex-col p-2 h-full gap-2 card min-w-0 flex-grow min-h-[50vh]"
|
||||
>
|
||||
<div class="w-full">
|
||||
{#if activeItem.status != 1}
|
||||
<!-- TODO add item -->
|
||||
{#if derivedItem.status_id !== null}
|
||||
<div class="bg-danger text-white p-2 rounded-lg text-lg font-bold">
|
||||
{(ApplicationStatusMaping[
|
||||
activeItem?.status as (typeof ApplicationStatus)[keyof typeof ApplicationStatus]
|
||||
] as string) ?? ''}
|
||||
{statusStore.nodesR[derivedItem.status_id].name}
|
||||
</div>
|
||||
{/if}
|
||||
{#if showExtraData}
|
||||
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||
<div class="flabel">Id</div>
|
||||
<div class="finput bg-white w-full break-keep">
|
||||
{activeItem.id}
|
||||
{derivedItem.id}
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||
<div class="flabel">Create Time</div>
|
||||
<div class="finput bg-white w-full break-keep">
|
||||
{activeItem.create_time}
|
||||
{derivedItem.create_time}
|
||||
</div>
|
||||
</fieldset>
|
||||
{/if}
|
||||
<div class="flex gap-2">
|
||||
<CompanyField bind:company={activeItem.company} {save} />
|
||||
<CompanyField index={activeItem} {save} />
|
||||
<fieldset class="grow">
|
||||
<label class="flabel" for="title">Recruiter</label>
|
||||
<input
|
||||
class="finput"
|
||||
id="title"
|
||||
bind:value={activeItem.recruiter}
|
||||
bind:value={applicationStore.all[activeItem].recruiter}
|
||||
onchange={save}
|
||||
/>
|
||||
</fieldset>
|
||||
@ -256,7 +235,7 @@
|
||||
class="finput"
|
||||
id="title"
|
||||
type="checkbox"
|
||||
bind:checked={activeItem.agency}
|
||||
bind:checked={applicationStore.all[activeItem].agency}
|
||||
onchange={save}
|
||||
/>
|
||||
</fieldset>
|
||||
@ -266,7 +245,7 @@
|
||||
<input
|
||||
class="finput"
|
||||
id="title"
|
||||
bind:value={activeItem.title}
|
||||
bind:value={applicationStore.all[activeItem].title}
|
||||
onchange={save}
|
||||
/>
|
||||
</fieldset>
|
||||
@ -275,51 +254,36 @@
|
||||
<input
|
||||
class="finput"
|
||||
id="payrange"
|
||||
bind:value={activeItem.payrange}
|
||||
bind:value={applicationStore.all[activeItem].payrange}
|
||||
onchange={save}
|
||||
/>
|
||||
</fieldset>
|
||||
{#if !activeItem.unique_url || showExtraData}
|
||||
{#if !derivedItem.simple_url || showExtraData}
|
||||
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||
<div class="flabel">Url</div>
|
||||
<div class="finput bg-white w-full break-keep">
|
||||
{activeItem.url}
|
||||
{derivedItem.url}
|
||||
</div>
|
||||
</fieldset>
|
||||
{/if}
|
||||
{#if activeItem.unique_url}
|
||||
<fieldset>
|
||||
<div class="flabel">Unique Url</div>
|
||||
<div class="finput bg-white">
|
||||
{activeItem.unique_url}
|
||||
</div>
|
||||
</fieldset>
|
||||
{/if}
|
||||
{#if activeItem.linked_application}
|
||||
<fieldset>
|
||||
<div class="flabel">Linked Application</div>
|
||||
<div class="finput bg-white">
|
||||
{activeItem.linked_application}
|
||||
</div>
|
||||
</fieldset>
|
||||
{/if}
|
||||
{#if activeItem.application_time}
|
||||
<fieldset>
|
||||
<div class="flabel">Application Time</div>
|
||||
<div class="finput bg-white">
|
||||
{activeItem.application_time}
|
||||
{#if derivedItem.simple_url}
|
||||
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||
<div class="flabel">Simple Url</div>
|
||||
<div class="finput bg-white w-full break-keep">
|
||||
{derivedItem.simple_url}
|
||||
</div>
|
||||
</fieldset>
|
||||
{/if}
|
||||
<div>
|
||||
<div class="flabel">Tags</div>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
{#each activeItem.flairs as item}
|
||||
{#each derivedItem.flairs as item}
|
||||
<Flair
|
||||
{item}
|
||||
allowDelete
|
||||
application={activeItem}
|
||||
updateApplication={(item) => (activeItem = item)}
|
||||
application={derivedItem}
|
||||
updateApplication={(item) =>
|
||||
(applicationStore.all[activeItem ?? -1] = item)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
@ -329,7 +293,7 @@
|
||||
<textarea
|
||||
class="finput"
|
||||
id="extra"
|
||||
bind:value={activeItem.extra_data}
|
||||
bind:value={applicationStore.all[activeItem].extra_data}
|
||||
onchange={save}
|
||||
></textarea>
|
||||
</fieldset>
|
||||
@ -338,24 +302,16 @@
|
||||
<textarea
|
||||
class="finput"
|
||||
id="extra"
|
||||
bind:value={activeItem.message}
|
||||
bind:value={applicationStore.all[activeItem].message}
|
||||
onchange={save}
|
||||
></textarea>
|
||||
</fieldset>
|
||||
{#if activeItem.views.length > 0}
|
||||
<h1 class="text-sm">Non Loggedin Views Time ({activeItem.views.length})</h1>
|
||||
{#each activeItem.views as view}
|
||||
<div>
|
||||
{view.time}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex btns">
|
||||
<button
|
||||
class="btn-confirm"
|
||||
onclick={() => {
|
||||
openWindow(activeItem!.url);
|
||||
openWindow(derivedItem!.url);
|
||||
}}
|
||||
>
|
||||
Open
|
||||
@ -378,18 +334,16 @@
|
||||
Get Ext Data
|
||||
</button>
|
||||
{/if}
|
||||
{#if activeItem.original_url == null}
|
||||
<button class="btn-primary" onclick={() => changeUrl.showModal()}>
|
||||
<button
|
||||
class={derivedItem.simple_url ? 'btn-danger' : 'btn-primary'}
|
||||
onclick={() => changeUrl.showModal()}
|
||||
>
|
||||
Update Url
|
||||
</button>
|
||||
{/if}
|
||||
<div class="px-10"></div>
|
||||
<button class="btn-primary" onclick={() => linkApplication.showModal()}>
|
||||
Link Application
|
||||
</button>
|
||||
{#if activeItem.original_url != null}
|
||||
<button class="btn-danger" onclick={resetUrl}> Reset Url </button>
|
||||
{/if}
|
||||
<button
|
||||
class:btn-primary={drag}
|
||||
class:btn-danger={!drag}
|
||||
@ -405,9 +359,9 @@
|
||||
🔬
|
||||
</button>
|
||||
</div>
|
||||
<Timeline application={activeItem} showAll={showExtraData} />
|
||||
<Timeline application={derivedItem} showAll={showExtraData} />
|
||||
</div>
|
||||
<DropingZone bind:activeItem />
|
||||
<AutoDropZone bind:index={activeItem} />
|
||||
{:else}
|
||||
<div
|
||||
class="p-2 h-full w-full gap-2 flex-grow card grid place-items-center min-h-[50vh]"
|
||||
@ -432,32 +386,35 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<ExtractTextDialog id={activeItem?.id ?? ''} bind:dialog={extractTokens} onreload={loadActive} />
|
||||
<ExtractTextDialog
|
||||
id={derivedItem?.id ?? ''}
|
||||
bind:dialog={extractTokens}
|
||||
onreload={() => activate(derivedItem, false)}
|
||||
/>
|
||||
|
||||
{#if !activeItem?.original_url}
|
||||
{#if derivedItem}
|
||||
<NewUrlDialog
|
||||
id={activeItem?.id ?? ''}
|
||||
id={derivedItem?.id ?? ''}
|
||||
bind:dialog={changeUrl}
|
||||
index={activeItem}
|
||||
onreload={async (item) => {
|
||||
item.events = await get(`events/${item.id}`);
|
||||
activeItem = item;
|
||||
activate(item, false);
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if activeItem}
|
||||
{#if derivedItem}
|
||||
<LinkApplication
|
||||
application={activeItem}
|
||||
application={derivedItem}
|
||||
bind:dialog={linkApplication}
|
||||
onreload={(item) => (activeItem = item)}
|
||||
onreload={(item) => activate(item, false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<SearchApplication
|
||||
application={activeItem}
|
||||
application={derivedItem}
|
||||
onreload={async (item) => {
|
||||
item.events = await get(`events/${item.id}`);
|
||||
activeItem = item;
|
||||
activate(item);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user