feat: a lot
This commit is contained in:
parent
26301d1a13
commit
941875ff21
@ -1,8 +1,6 @@
|
|||||||
package com.andr3h3nriqu3s.applications
|
package com.andr3h3nriqu3s.applications
|
||||||
|
|
||||||
import java.sql.ResultSet
|
import java.sql.ResultSet
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.collections.emptyList
|
import kotlin.collections.emptyList
|
||||||
import kotlin.collections.setOf
|
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.bind.annotation.RestController
|
||||||
import org.springframework.web.server.ResponseStatusException
|
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(
|
data class Application(
|
||||||
var id: String,
|
var id: String,
|
||||||
var url: String,
|
var url: String,
|
||||||
var original_url: String?,
|
|
||||||
var unique_url: String?,
|
|
||||||
var title: String,
|
var title: String,
|
||||||
var user_id: String,
|
var user_id: String,
|
||||||
var extra_data: String,
|
var extra_data: String,
|
||||||
var payrange: String,
|
var payrange: String,
|
||||||
var status: Int,
|
|
||||||
var status_id: String?,
|
var status_id: String?,
|
||||||
var company: String,
|
var company: String,
|
||||||
var recruiter: String,
|
var recruiter: String,
|
||||||
var agency: Boolean,
|
var agency: Boolean,
|
||||||
var message: String,
|
var message: String,
|
||||||
var linked_application: String,
|
|
||||||
var status_history: String,
|
|
||||||
var application_time: String,
|
|
||||||
var create_time: String,
|
var create_time: String,
|
||||||
|
var simple_url: String,
|
||||||
var flairs: List<Flair>,
|
var flairs: List<Flair>,
|
||||||
var views: List<View>,
|
|
||||||
var events: List<Event>,
|
var events: List<Event>,
|
||||||
) {
|
) {
|
||||||
companion object : RowMapper<Application> {
|
companion object : RowMapper<Application> {
|
||||||
@ -51,23 +57,17 @@ data class Application(
|
|||||||
return Application(
|
return Application(
|
||||||
rs.getString("id"),
|
rs.getString("id"),
|
||||||
rs.getString("url"),
|
rs.getString("url"),
|
||||||
rs.getString("original_url"),
|
|
||||||
rs.getString("unique_url"),
|
|
||||||
rs.getString("title"),
|
rs.getString("title"),
|
||||||
rs.getString("user_id"),
|
rs.getString("user_id"),
|
||||||
rs.getString("extra_data"),
|
rs.getString("extra_data"),
|
||||||
rs.getString("payrange"),
|
rs.getString("payrange"),
|
||||||
rs.getInt("status"),
|
|
||||||
rs.getString("status_id"),
|
rs.getString("status_id"),
|
||||||
rs.getString("company"),
|
rs.getString("company"),
|
||||||
rs.getString("recruiter"),
|
rs.getString("recruiter"),
|
||||||
rs.getBoolean("agency"),
|
rs.getBoolean("agency"),
|
||||||
rs.getString("message"),
|
rs.getString("message"),
|
||||||
rs.getString("linked_application"),
|
|
||||||
rs.getString("status_history"),
|
|
||||||
rs.getString("application_time"),
|
|
||||||
rs.getString("create_time"),
|
rs.getString("create_time"),
|
||||||
emptyList(),
|
rs.getString("simple_url"),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
)
|
)
|
||||||
@ -77,9 +77,7 @@ data class Application(
|
|||||||
|
|
||||||
data class SubmitRequest(val text: String)
|
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_id: String?)
|
||||||
|
|
||||||
data class StatusRequest(val id: String, val status: Int)
|
|
||||||
|
|
||||||
data class FlairRequest(val id: String, val text: String)
|
data class FlairRequest(val id: String, val text: String)
|
||||||
|
|
||||||
@ -100,7 +98,6 @@ class ApplicationsController(
|
|||||||
val sessionService: SessionService,
|
val sessionService: SessionService,
|
||||||
val applicationService: ApplicationService,
|
val applicationService: ApplicationService,
|
||||||
val flairService: FlairService,
|
val flairService: FlairService,
|
||||||
val viewService: ViewService,
|
|
||||||
val eventService: EventService,
|
val eventService: EventService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -118,7 +115,13 @@ class ApplicationsController(
|
|||||||
|
|
||||||
val flairs = application.flairs.map { it.toFlairSimple() }
|
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 */
|
/** Create a new application from the link */
|
||||||
@ -133,13 +136,10 @@ class ApplicationsController(
|
|||||||
Application(
|
Application(
|
||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
submit.text,
|
submit.text,
|
||||||
submit.text,
|
|
||||||
submit.text,
|
|
||||||
"New Application",
|
"New Application",
|
||||||
user.id,
|
user.id,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
0,
|
|
||||||
null,
|
null,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
@ -147,9 +147,6 @@ class ApplicationsController(
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
|
||||||
"",
|
|
||||||
emptyList(),
|
|
||||||
emptyList(),
|
emptyList(),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
)
|
)
|
||||||
@ -199,7 +196,7 @@ class ApplicationsController(
|
|||||||
public fun submitText(
|
public fun submitText(
|
||||||
@RequestBody submit: SubmitRequest,
|
@RequestBody submit: SubmitRequest,
|
||||||
@RequestHeader("token") token: String
|
@RequestHeader("token") token: String
|
||||||
): Int {
|
): List<Application> {
|
||||||
val user = sessionService.verifyTokenThrow(token)
|
val user = sessionService.verifyTokenThrow(token)
|
||||||
|
|
||||||
var text = submit.text.replace("=\n", "")
|
var text = submit.text.replace("=\n", "")
|
||||||
@ -263,23 +260,17 @@ class ApplicationsController(
|
|||||||
Application(
|
Application(
|
||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
if (elm.contains("linkedin")) elm.split("?")[0] else elm,
|
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",
|
"New Application",
|
||||||
user.id,
|
user.id,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
0,
|
|
||||||
null,
|
null,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
if (elm.contains("linkedin")) elm.split("?")[0] else "",
|
||||||
"",
|
|
||||||
"",
|
|
||||||
emptyList(),
|
|
||||||
emptyList(),
|
emptyList(),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
)
|
)
|
||||||
@ -292,26 +283,46 @@ class ApplicationsController(
|
|||||||
print(applications.size)
|
print(applications.size)
|
||||||
print(" links\n")
|
print(" links\n")
|
||||||
|
|
||||||
return applications.size
|
return applications
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(path = ["/list"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
@GetMapping(path = ["/list"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
public fun list(
|
public fun list(@RequestHeader("token") token: String): List<Application> {
|
||||||
@RequestBody info: ListRequest,
|
|
||||||
@RequestHeader("token") token: String
|
|
||||||
): List<Application> {
|
|
||||||
val user = sessionService.verifyTokenThrow(token)
|
val user = sessionService.verifyTokenThrow(token)
|
||||||
return applicationService.findAll(user, info)
|
return applicationService.findAll(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = ["/active"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
@GetMapping(path = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
public fun active(@RequestHeader("token") token: String): Application? {
|
public fun get(@PathVariable id: String, @RequestHeader("token") token: String): Application? {
|
||||||
val user = sessionService.verifyTokenThrow(token)
|
val user = sessionService.verifyTokenThrow(token)
|
||||||
val possibleApplications = applicationService.findAll(user, ListRequest(1))
|
val app = applicationService.findApplicationById(user, id)
|
||||||
if (possibleApplications.size == 0) {
|
if (app == null) {
|
||||||
return null
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Application not found", null)
|
||||||
}
|
}
|
||||||
return applicationService.findApplicationById(user, possibleApplications[0].id)
|
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
|
||||||
|
): Application {
|
||||||
|
val user = sessionService.verifyTokenThrow(token)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationService.linkApplications(toLinkApp, app)
|
||||||
|
applicationService.delete(toLinkApp)
|
||||||
|
|
||||||
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping(path = ["/status"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
@PutMapping(path = ["/status"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
@ -325,11 +336,11 @@ class ApplicationsController(
|
|||||||
throw NotFound()
|
throw NotFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (application.status == info.status) {
|
if (application.status_id == info.status_id) {
|
||||||
return application;
|
return application
|
||||||
}
|
}
|
||||||
|
|
||||||
application.status = info.status
|
application.status_id = info.status_id
|
||||||
|
|
||||||
applicationService.updateStatus(application)
|
applicationService.updateStatus(application)
|
||||||
|
|
||||||
@ -362,68 +373,28 @@ class ApplicationsController(
|
|||||||
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Application not found", null)
|
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.url = info.url
|
||||||
application.unique_url = info.url.split("?")[0]
|
application.simple_url = info.url.split("?")[0]
|
||||||
|
|
||||||
var maybe_exists =
|
var maybe_exists =
|
||||||
applicationService.findApplicationByUrl(
|
applicationService.findApplicationByUrl(user, application.url)
|
||||||
user,
|
?: applicationService.findApplicationByUrl(user, application.simple_url)
|
||||||
application.url,
|
|
||||||
application.unique_url
|
if (maybe_exists != null && maybe_exists.id != application.id) {
|
||||||
)
|
|
||||||
if (maybe_exists != null) {
|
|
||||||
applicationService.delete(application)
|
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.flairs = flairService.listFromLinkApplicationId(maybe_exists.id)
|
||||||
|
maybe_exists.events = eventService.listFromApplicationId(maybe_exists.id).toList()
|
||||||
|
|
||||||
return maybe_exists
|
return maybe_exists
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applicationService.addUrl(application.id, info.url)
|
||||||
|
applicationService.addUrl(application.id, info.url.split("?")[0])
|
||||||
applicationService.update(application)
|
applicationService.update(application)
|
||||||
|
|
||||||
application.flairs = flairService.listFromLinkApplicationId(application.id)
|
application.flairs = flairService.listFromLinkApplicationId(application.id)
|
||||||
application.views = viewService.listFromApplicationId(application.id)
|
application.events = eventService.listFromApplicationId(application.id).toList()
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
return application
|
return application
|
||||||
}
|
}
|
||||||
@ -466,28 +437,13 @@ class ApplicationsController(
|
|||||||
class ApplicationService(
|
class ApplicationService(
|
||||||
val db: JdbcTemplate,
|
val db: JdbcTemplate,
|
||||||
val flairService: FlairService,
|
val flairService: FlairService,
|
||||||
val viewService: ViewService,
|
|
||||||
val eventService: EventService,
|
val eventService: EventService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
public fun findApplicationByUrl(user: UserDb, url: String, unique_url: String?): Application? {
|
public fun findApplicationByUrl(user: UserDb, url: String): Application? {
|
||||||
if (unique_url != null) {
|
val applications =
|
||||||
val unique: List<Application> =
|
|
||||||
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(
|
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),
|
arrayOf(url, user.id),
|
||||||
Application
|
Application
|
||||||
)
|
)
|
||||||
@ -516,7 +472,6 @@ class ApplicationService(
|
|||||||
var application = applications[0]
|
var application = applications[0]
|
||||||
|
|
||||||
application.flairs = flairService.listFromLinkApplicationId(application.id)
|
application.flairs = flairService.listFromLinkApplicationId(application.id)
|
||||||
application.views = viewService.listFromApplicationId(application.id)
|
|
||||||
application.events = eventService.listFromApplicationId(application.id).toList()
|
application.events = eventService.listFromApplicationId(application.id).toList()
|
||||||
|
|
||||||
return application
|
return application
|
||||||
@ -538,106 +493,80 @@ class ApplicationService(
|
|||||||
return application
|
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 {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create time is auto created by the database
|
// Create time is auto created by the database
|
||||||
// The default status is null
|
|
||||||
db.update(
|
db.update(
|
||||||
"insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status, company, recruiter, message, linked_application, status_history, application_time, 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.id,
|
||||||
application.url,
|
application.url,
|
||||||
application.original_url,
|
|
||||||
application.unique_url,
|
|
||||||
application.title,
|
application.title,
|
||||||
application.user_id,
|
application.user_id,
|
||||||
application.extra_data,
|
application.extra_data,
|
||||||
application.payrange,
|
application.payrange,
|
||||||
application.status,
|
application.status_id,
|
||||||
application.company,
|
application.company,
|
||||||
application.recruiter,
|
application.recruiter,
|
||||||
application.message,
|
application.message,
|
||||||
application.linked_application,
|
|
||||||
application.status_history,
|
|
||||||
application.application_time,
|
|
||||||
application.agency,
|
application.agency,
|
||||||
|
application.simple_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
eventService.create(application.id, EventType.Creation)
|
eventService.create(application.id, EventType.Creation)
|
||||||
|
addUrl(application.id, application.url)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun internalFindAll(user: UserDb, info: ListRequest): Iterable<Application> {
|
private fun internalFindAll(user: UserDb): Iterable<Application> {
|
||||||
if (info.status == null) {
|
|
||||||
return db.query(
|
|
||||||
"select * from applications where user_id=? order by title asc;",
|
|
||||||
arrayOf(user.id),
|
|
||||||
Application
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(
|
return db.query(
|
||||||
"select * from applications where user_id=? and status=? order by title asc;",
|
"select * from applications where user_id=? order by title asc;",
|
||||||
arrayOf(user.id, info.status),
|
arrayOf(user.id),
|
||||||
Application,
|
Application
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun findAll(user: UserDb, info: ListRequest): List<Application> {
|
public fun findAll(user: UserDb): List<Application> {
|
||||||
var iter = internalFindAll(user, info);
|
var iter = internalFindAll(user)
|
||||||
if (info.views == true) {
|
|
||||||
iter = iter.map {
|
|
||||||
it.views = viewService.listFromApplicationId(it.id)
|
|
||||||
it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return iter.toList()
|
return iter.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun findAllByUserStatusId(statusId: String, user: UserDb): List<Application> {
|
public fun findAllByUserStatusId(statusId: String, user: UserDb): List<Application> {
|
||||||
return db.query(
|
return db.query(
|
||||||
"select * from applications where status_id=? and user_id=? order by title asc;",
|
"select * from applications where status_id=? and user_id=? order by title asc;",
|
||||||
arrayOf(statusId, user.id),
|
arrayOf(statusId, user.id),
|
||||||
Application
|
Application
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the stauts on the application object before giving it to this function
|
// Update the stauts on the application object before giving it to this function
|
||||||
// TODO how status history works
|
// TODO how status history works
|
||||||
public fun updateStatus(application: Application): Application {
|
public fun updateStatus(application: Application): Application {
|
||||||
|
|
||||||
val status_string = "${application.status}"
|
eventService.create(application.id, EventType.StatusUpdate, application.status_id)
|
||||||
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)
|
|
||||||
|
|
||||||
db.update(
|
db.update(
|
||||||
"update applications set status=?, status_history=?, application_time=? where id=?",
|
"update applications set status_id=? where id=?",
|
||||||
application.status,
|
application.status_id,
|
||||||
application.status_history,
|
|
||||||
application.application_time,
|
|
||||||
application.id,
|
application.id,
|
||||||
)
|
)
|
||||||
return application
|
return application
|
||||||
@ -647,10 +576,8 @@ class ApplicationService(
|
|||||||
public fun update(application: Application): Application {
|
public fun update(application: Application): Application {
|
||||||
// I don't want ot update create_time
|
// I don't want ot update create_time
|
||||||
db.update(
|
db.update(
|
||||||
"update applications set url=?, original_url=?, unique_url=?, title=?, user_id=?, extra_data=?, payrange=?, company=?, recruiter=?, message=?, linked_application=?, agency=? where id=?",
|
"update applications set url=?, title=?, user_id=?, extra_data=?, payrange=?, company=?, recruiter=?, message=?, agency=?, simple_url=? where id=?",
|
||||||
application.url,
|
application.url,
|
||||||
application.original_url,
|
|
||||||
application.unique_url,
|
|
||||||
application.title,
|
application.title,
|
||||||
application.user_id,
|
application.user_id,
|
||||||
application.extra_data,
|
application.extra_data,
|
||||||
@ -658,17 +585,29 @@ class ApplicationService(
|
|||||||
application.company,
|
application.company,
|
||||||
application.recruiter,
|
application.recruiter,
|
||||||
application.message,
|
application.message,
|
||||||
application.linked_application,
|
|
||||||
application.agency,
|
application.agency,
|
||||||
|
application.simple_url,
|
||||||
application.id,
|
application.id,
|
||||||
)
|
)
|
||||||
return application
|
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) {
|
public fun delete(application: Application) {
|
||||||
db.update(
|
db.update(
|
||||||
"delete from applications where id=?",
|
"delete from applications where id=?",
|
||||||
application.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 id: String,
|
||||||
var application_id: String,
|
var application_id: String,
|
||||||
var event_type: Int,
|
var event_type: Int,
|
||||||
var new_status: Int?,
|
var new_status_id: String?,
|
||||||
var time: Timestamp
|
var time: Timestamp
|
||||||
) {
|
) {
|
||||||
companion object : RowMapper<Event> {
|
companion object : RowMapper<Event> {
|
||||||
@ -34,7 +34,7 @@ data class Event(
|
|||||||
rs.getString("id"),
|
rs.getString("id"),
|
||||||
rs.getString("application_id"),
|
rs.getString("application_id"),
|
||||||
rs.getInt("event_type"),
|
rs.getInt("event_type"),
|
||||||
rs.getInt("new_status"),
|
rs.getString("new_status_id"),
|
||||||
rs.getTimestamp("time"),
|
rs.getTimestamp("time"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -93,23 +93,19 @@ public class EventService(val db: JdbcTemplate) {
|
|||||||
public fun create(
|
public fun create(
|
||||||
application_id: String,
|
application_id: String,
|
||||||
event_type: EventType,
|
event_type: EventType,
|
||||||
new_status: Int? = null
|
new_status_id: String? = null
|
||||||
): Event {
|
): Event {
|
||||||
val id = UUID.randomUUID().toString()
|
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 =
|
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(
|
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.id,
|
||||||
new_event.application_id,
|
new_event.application_id,
|
||||||
new_event.event_type,
|
new_event.event_type,
|
||||||
new_event.new_status,
|
new_event.new_status_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
return new_event
|
return new_event
|
||||||
|
@ -2,8 +2,8 @@ package com.andr3h3nriqu3s.applications
|
|||||||
|
|
||||||
import java.sql.ResultSet
|
import java.sql.ResultSet
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import org.springframework.http.MediaType
|
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.jdbc.core.JdbcTemplate
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
import org.springframework.jdbc.core.RowMapper
|
import org.springframework.jdbc.core.RowMapper
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@ -52,10 +52,6 @@ class FlairController(
|
|||||||
throw NotFound()
|
throw NotFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (old_flair.user_id != user.id) {
|
|
||||||
throw NotAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
flair.user_id = old_flair.user_id
|
flair.user_id = old_flair.user_id
|
||||||
|
|
||||||
return flairService.updateFlair(flair)
|
return flairService.updateFlair(flair)
|
||||||
@ -88,6 +84,8 @@ data class SimpleFlair(
|
|||||||
val name: String,
|
val name: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
val color: String,
|
val color: String,
|
||||||
|
val sort: Int,
|
||||||
|
val showFullDescription: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Flair(
|
data class Flair(
|
||||||
@ -97,6 +95,8 @@ data class Flair(
|
|||||||
var name: String,
|
var name: String,
|
||||||
var expr: String,
|
var expr: String,
|
||||||
var description: String,
|
var description: String,
|
||||||
|
var sort: Int,
|
||||||
|
var showFullDescription: Int,
|
||||||
) {
|
) {
|
||||||
companion object : RowMapper<Flair> {
|
companion object : RowMapper<Flair> {
|
||||||
override public fun mapRow(rs: ResultSet, rowNum: Int): Flair {
|
override public fun mapRow(rs: ResultSet, rowNum: Int): Flair {
|
||||||
@ -107,12 +107,20 @@ data class Flair(
|
|||||||
rs.getString("name"),
|
rs.getString("name"),
|
||||||
rs.getString("expr"),
|
rs.getString("expr"),
|
||||||
rs.getString("description"),
|
rs.getString("description"),
|
||||||
|
rs.getInt("sort"),
|
||||||
|
rs.getInt("showFullDescription"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toFlairSimple(): SimpleFlair {
|
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> =
|
public fun listFromLinkApplicationId(id: String): List<Flair> =
|
||||||
db.query(
|
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),
|
arrayOf(id),
|
||||||
Flair
|
Flair
|
||||||
)
|
)
|
||||||
@ -210,12 +218,14 @@ public class FlairService(val db: JdbcTemplate) {
|
|||||||
|
|
||||||
public fun updateFlair(flair: Flair): Flair {
|
public fun updateFlair(flair: Flair): Flair {
|
||||||
db.update(
|
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.user_id,
|
||||||
flair.color,
|
flair.color,
|
||||||
flair.name,
|
flair.name,
|
||||||
flair.expr,
|
flair.expr,
|
||||||
flair.description,
|
flair.description,
|
||||||
|
flair.sort,
|
||||||
|
flair.showFullDescription,
|
||||||
flair.id,
|
flair.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -231,16 +241,18 @@ public class FlairService(val db: JdbcTemplate) {
|
|||||||
description = flair.description!!
|
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(
|
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.id,
|
||||||
new_flair.user_id,
|
new_flair.user_id,
|
||||||
new_flair.color,
|
new_flair.color,
|
||||||
new_flair.name,
|
new_flair.name,
|
||||||
new_flair.expr,
|
new_flair.expr,
|
||||||
new_flair.description
|
new_flair.description,
|
||||||
|
new_flair.showFullDescription,
|
||||||
|
new_flair.sort,
|
||||||
)
|
)
|
||||||
|
|
||||||
return new_flair
|
return new_flair
|
||||||
|
@ -28,7 +28,9 @@ data class UserStatusNode(
|
|||||||
var y: Int,
|
var y: Int,
|
||||||
var width: Int,
|
var width: Int,
|
||||||
var height: Int,
|
var height: Int,
|
||||||
var permission: Int
|
var permission: Int,
|
||||||
|
var visible: Boolean,
|
||||||
|
var endable: Boolean,
|
||||||
) {
|
) {
|
||||||
companion object : RowMapper<UserStatusNode> {
|
companion object : RowMapper<UserStatusNode> {
|
||||||
override public fun mapRow(rs: ResultSet, rowNum: Int): UserStatusNode {
|
override public fun mapRow(rs: ResultSet, rowNum: Int): UserStatusNode {
|
||||||
@ -42,6 +44,8 @@ data class UserStatusNode(
|
|||||||
rs.getInt("width"),
|
rs.getInt("width"),
|
||||||
rs.getInt("height"),
|
rs.getInt("height"),
|
||||||
rs.getInt("permission"),
|
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 {
|
public fun update(node: UserStatusNode): UserStatusNode {
|
||||||
db.update(
|
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.name,
|
||||||
node.icon,
|
node.icon,
|
||||||
node.x,
|
node.x,
|
||||||
@ -219,6 +223,8 @@ public class UserStatusNodeService(val db: JdbcTemplate) {
|
|||||||
node.width,
|
node.width,
|
||||||
node.height,
|
node.height,
|
||||||
node.permission,
|
node.permission,
|
||||||
|
node.visible,
|
||||||
|
node.endable,
|
||||||
node.id,
|
node.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -230,7 +236,7 @@ public class UserStatusNodeService(val db: JdbcTemplate) {
|
|||||||
node.id = id
|
node.id = id
|
||||||
|
|
||||||
db.update(
|
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.id,
|
||||||
node.user_id,
|
node.user_id,
|
||||||
node.name,
|
node.name,
|
||||||
@ -239,7 +245,9 @@ public class UserStatusNodeService(val db: JdbcTemplate) {
|
|||||||
node.y,
|
node.y,
|
||||||
node.width,
|
node.width,
|
||||||
node.height,
|
node.height,
|
||||||
node.permission
|
node.permission,
|
||||||
|
node.visible,
|
||||||
|
node.endable,
|
||||||
)
|
)
|
||||||
|
|
||||||
return node
|
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +1,53 @@
|
|||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id VARCHAR PRIMARY KEY,
|
id VARCHAR PRIMARY KEY,
|
||||||
username VARCHAR NOT NULL,
|
username VARCHAR NOT NULL,
|
||||||
email VARCHAR NOT NULL,
|
email VARCHAR NOT NULL,
|
||||||
passwd VARCHAR NOT NULL,
|
passwd VARCHAR NOT NULL,
|
||||||
level INT NOT NULL
|
level INT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tokens (
|
CREATE TABLE IF NOT EXISTS tokens (token VARCHAR PRIMARY KEY, user_id VARCHAR);
|
||||||
token VARCHAR PRIMARY KEY,
|
|
||||||
user_id VARCHAR
|
|
||||||
);
|
|
||||||
|
|
||||||
create table if not exists applications (
|
create table if not exists applications (
|
||||||
id text primary key,
|
id text primary key,
|
||||||
url text not null,
|
url text not null,
|
||||||
original_url text,
|
simple_url text not null,
|
||||||
unique_url text,
|
company text,
|
||||||
company text,
|
recruiter text,
|
||||||
recruiter text,
|
title text,
|
||||||
title text,
|
mesasge text default '',
|
||||||
mesasge text default '',
|
user_id text,
|
||||||
status_history text default '',
|
extra_data text,
|
||||||
user_id text,
|
status_id text default null,
|
||||||
extra_data text,
|
agency boolean default false,
|
||||||
-- this status will be deprecated in favor of the node style status
|
create_time timestamp default now ()
|
||||||
status integer,
|
|
||||||
status_id text default null,
|
|
||||||
linked_application text default '',
|
|
||||||
application_time text default '',
|
|
||||||
agency boolean default false,
|
|
||||||
create_time timestamp default now()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Views are deprecated will be removed in the future
|
create table if not exists applications_urls (
|
||||||
create table if not exists views (
|
application_id text,
|
||||||
id text primary key,
|
url text not null,
|
||||||
application_id text not null,
|
primary key (application_id, url)
|
||||||
time timestamp default now()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
create table if not exists flair (
|
create table if not exists flair (
|
||||||
id text primary key,
|
id text primary key,
|
||||||
user_id text not null,
|
user_id text not null,
|
||||||
color text default '#ff0000',
|
color text default '#ff0000',
|
||||||
name text default 'New Flair',
|
name text default 'New Flair',
|
||||||
expr text default '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 (
|
create table if not exists flair_link (
|
||||||
id text primary key,
|
id text primary key,
|
||||||
application_id text not null,
|
application_id text not null,
|
||||||
flair_id text not null
|
flair_id text not null
|
||||||
);
|
);
|
||||||
|
|
||||||
create table if not exists events (
|
create table if not exists events (
|
||||||
id text primary key,
|
id text primary key,
|
||||||
application_id text not null,
|
application_id text not null,
|
||||||
--
|
--
|
||||||
-- Event Types
|
-- Event Types
|
||||||
@ -64,46 +56,41 @@ create table if not exists events (
|
|||||||
-- Creation(0),
|
-- Creation(0),
|
||||||
-- StatusUpdate(1),
|
-- StatusUpdate(1),
|
||||||
-- Page(2)
|
-- Page(2)
|
||||||
event_type integer not null,
|
event_type integer not null,
|
||||||
|
|
||||||
-- This only matters when event_type == 1
|
-- This only matters when event_type == 1
|
||||||
new_status integer,
|
new_status_id text,
|
||||||
|
time timestamp default now ()
|
||||||
time timestamp default now()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
--
|
--
|
||||||
-- User Controlled Status
|
-- User Controlled Status
|
||||||
--
|
--
|
||||||
|
|
||||||
create table if not exists user_status_node (
|
create table if not exists user_status_node (
|
||||||
id text primary key,
|
id text primary key,
|
||||||
user_id text not null,
|
user_id text not null,
|
||||||
name text not null,
|
name text not null,
|
||||||
icon text not null,
|
icon text not null,
|
||||||
x integer default 0,
|
x integer default 0,
|
||||||
y integer default 0,
|
y integer default 0,
|
||||||
width integer default 0,
|
width integer default 0,
|
||||||
height 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 (
|
create table if not exists user_status_link (
|
||||||
id text primary key,
|
id text primary key,
|
||||||
-- You technically can get this by loking a the source and target nodes but that seams more complicated
|
-- You technically can get this by loking a the source and target nodes but that seams more complicated
|
||||||
user_id text not null,
|
user_id text not null,
|
||||||
|
|
||||||
-- This can be null because null means creation
|
-- This can be null because null means creation
|
||||||
source_node text,
|
source_node text,
|
||||||
-- This can be null because null means creation
|
-- This can be null because null means creation
|
||||||
target_node text,
|
target_node text,
|
||||||
|
source_x integer default 0,
|
||||||
source_x integer default 0,
|
source_y integer default 0,
|
||||||
source_y integer default 0,
|
target_x integer default 0,
|
||||||
|
target_y integer default 0,
|
||||||
target_x integer default 0,
|
|
||||||
target_y integer default 0,
|
|
||||||
|
|
||||||
-- If this link is bidiretoral
|
-- If this link is bidiretoral
|
||||||
bi boolean
|
bi boolean
|
||||||
);
|
);
|
||||||
|
@ -1,138 +1,67 @@
|
|||||||
import type { Flair } from './FlairStore.svelte';
|
import type { Flair } from './FlairStore.svelte';
|
||||||
import { post } from './utils';
|
import { get } from './utils';
|
||||||
|
|
||||||
export type AsEnum<T> = T[keyof T];
|
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({
|
export const EventType = Object.freeze({
|
||||||
Creation: 0,
|
Creation: 0,
|
||||||
StatusUpdate: 1,
|
StatusUpdate: 1,
|
||||||
View: 2,
|
View: 2
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ApplicationEvent = {
|
export type ApplicationEvent = {
|
||||||
id: string,
|
id: string;
|
||||||
application_id: string,
|
application_id: string;
|
||||||
event_type: AsEnum<typeof EventType>,
|
event_type: AsEnum<typeof EventType>;
|
||||||
new_status: number,
|
new_status_id: string;
|
||||||
time: string
|
time: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Application = {
|
export type Application = {
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
original_url: string | null;
|
simple_url: string;
|
||||||
unique_url: string | null;
|
|
||||||
title: string;
|
title: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
extra_data: string;
|
extra_data: string;
|
||||||
payrange: string;
|
payrange: string;
|
||||||
status: AsEnum<typeof ApplicationStatus>;
|
status_id: string;
|
||||||
recruiter: string;
|
recruiter: string;
|
||||||
agency: boolean;
|
agency: boolean;
|
||||||
company: string;
|
company: string;
|
||||||
message: string;
|
message: string;
|
||||||
linked_application: string;
|
linked_application: string;
|
||||||
application_time: string;
|
|
||||||
create_time: string;
|
create_time: string;
|
||||||
status_history: string;
|
status_history: string;
|
||||||
flairs: Flair[];
|
flairs: Flair[];
|
||||||
views: View[];
|
|
||||||
events: ApplicationEvent[];
|
events: ApplicationEvent[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function createApplicationStore() {
|
function createApplicationStore() {
|
||||||
let applications: Application[] = $state([]);
|
|
||||||
let applyed: Application[] = $state([]);
|
|
||||||
let all: Application[] = $state([]);
|
let all: Application[] = $state([]);
|
||||||
|
|
||||||
let dragApplication: Application | undefined = $state(undefined);
|
let dragApplication: Application | undefined = $state(undefined);
|
||||||
|
|
||||||
let loadItem: Application | undefined = $state(undefined);
|
let loadItem: Application | undefined = $state(undefined);
|
||||||
|
let loadItemOpen = $state(true);
|
||||||
|
|
||||||
|
let req = false;
|
||||||
|
|
||||||
return {
|
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}
|
* @throws {Error}
|
||||||
*/
|
*/
|
||||||
async loadAll(force = false) {
|
async loadAll(force = false) {
|
||||||
|
if (req) return;
|
||||||
if (!force && all.length > 1) {
|
if (!force && all.length > 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
all = await post('application/list', {});
|
req = true;
|
||||||
},
|
try {
|
||||||
|
all = await get('application/list');
|
||||||
clear() {
|
} finally {
|
||||||
applications = [];
|
req = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
dragStart(application: Application | undefined) {
|
dragStart(application: Application | undefined) {
|
||||||
@ -146,28 +75,53 @@ function createApplicationStore() {
|
|||||||
dragApplication = undefined;
|
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() {
|
get dragging() {
|
||||||
return dragApplication;
|
return dragApplication;
|
||||||
},
|
},
|
||||||
|
|
||||||
get applications() {
|
|
||||||
return applications;
|
|
||||||
},
|
|
||||||
|
|
||||||
get applyed() {
|
|
||||||
return applyed;
|
|
||||||
},
|
|
||||||
|
|
||||||
get all() {
|
get all() {
|
||||||
return 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() {
|
get loadItem() {
|
||||||
return loadItem;
|
return loadItem;
|
||||||
},
|
},
|
||||||
|
|
||||||
set loadItem(item: Application | undefined) {
|
set loadItem(item: Application | undefined) {
|
||||||
loadItem = item;
|
loadItem = item;
|
||||||
|
loadItemOpen = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
get loadItemOpen() {
|
||||||
|
return loadItemOpen;
|
||||||
|
},
|
||||||
|
|
||||||
|
set loadItemOpen(_loadItemOpen: boolean) {
|
||||||
|
loadItemOpen = _loadItemOpen;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,31 @@
|
|||||||
import { get } from './utils';
|
import { get } from './utils';
|
||||||
|
|
||||||
export type Flair = {
|
export type Flair = {
|
||||||
id: string;
|
id: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
color: string;
|
color: string;
|
||||||
name: string;
|
name: string;
|
||||||
expr: string;
|
expr: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
showFullDescription: number;
|
||||||
|
sort: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function createFlairStore() {
|
function createFlairStore() {
|
||||||
let flairList: Flair[] = $state([]);
|
let flairList: Flair[] = $state([]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get flairs() {
|
get flairs() {
|
||||||
return flairList;
|
return flairList;
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadItems(force?: boolean) {
|
async loadItems(force?: boolean) {
|
||||||
if (flairList.length > 0 && !force) {
|
if (flairList.length > 0 && !force) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
flairList = await get('flair/');
|
flairList = await get('flair/');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const flairStore = createFlairStore();
|
export const flairStore = 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 HasUser from '$lib/HasUser.svelte';
|
||||||
import ApplicationsList from './ApplicationsList.svelte';
|
import ApplicationsList from './ApplicationsList.svelte';
|
||||||
import WorkArea from './work-area/WorkArea.svelte';
|
import WorkArea from './work-area/WorkArea.svelte';
|
||||||
import AppliyedList from './AppliyedList.svelte';
|
import ApplicationTypesList from './ApplicationTypesList.svelte';
|
||||||
import PApplicationList from './PApplicationList.svelte';
|
|
||||||
import { ApplicationStatus } from '$lib/ApplicationsStore.svelte';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<HasUser redirect="/cv">
|
<HasUser redirect="/cv">
|
||||||
@ -13,25 +11,11 @@
|
|||||||
<NavBar />
|
<NavBar />
|
||||||
<div class="w-full px-4 grow h-full gap-3 flex flex-col">
|
<div class="w-full px-4 grow h-full gap-3 flex flex-col">
|
||||||
<div class="flex gap-3 flex-grow max-h-[75%]">
|
<div class="flex gap-3 flex-grow max-h-[75%]">
|
||||||
<ApplicationsList />
|
<ApplicationsList />
|
||||||
<WorkArea />
|
<WorkArea />
|
||||||
</div>
|
</div>
|
||||||
<PApplicationList status={ApplicationStatus.FinalInterview}>
|
<ApplicationTypesList />
|
||||||
Interview Final
|
<div class="p-3"></div>
|
||||||
</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 />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</HasUser>
|
</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('');
|
let filter = $state('');
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
applicationStore.loadApplications();
|
applicationStore.loadAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let apps = $derived(
|
||||||
|
applicationStore.all.filter((i) => i.status_id === null && !i.linked_application)
|
||||||
|
);
|
||||||
|
|
||||||
let internal = $derived(
|
let internal = $derived(
|
||||||
applicationStore.applications.filter((i) => {
|
apps.filter((i) => {
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -47,10 +51,7 @@
|
|||||||
}}
|
}}
|
||||||
role="none"
|
role="none"
|
||||||
>
|
>
|
||||||
<div
|
<div class="max-w-full" class:animate-pulse={applicationStore.dragging?.id === item.id}>
|
||||||
class="max-w-full"
|
|
||||||
class:animate-pulse={applicationStore.dragging?.id === item.id}
|
|
||||||
>
|
|
||||||
<h2 class="text-lg text-blue-500 flex gap-2 max-w-full overflow-hidden">
|
<h2 class="text-lg text-blue-500 flex gap-2 max-w-full overflow-hidden">
|
||||||
<div class="flex-grow max-w-[90%]">
|
<div class="flex-grow max-w-[90%]">
|
||||||
<div class="whitespace-nowrap overflow-hidden">
|
<div class="whitespace-nowrap overflow-hidden">
|
||||||
|
@ -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="p-7 pb-1">
|
||||||
<div class="card p-2 rounded-xl flex">
|
<div class="card p-2 rounded-xl flex">
|
||||||
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/')}> Home </button>
|
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/')}>
|
||||||
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/submit')}>
|
Home
|
||||||
Submit text
|
|
||||||
</button>
|
</button>
|
||||||
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/flair')}>
|
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/flair')}>
|
||||||
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 { userStore } from '$lib/UserStore.svelte';
|
||||||
import { get } from '$lib/utils';
|
import { get } from '$lib/utils';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import ApplicationsList from '../ApplicationsList.svelte';
|
import Pill from './pill.svelte';
|
||||||
|
|
||||||
let id: string | undefined | null = $state(undefined);
|
let id: string | undefined | null = $state(undefined);
|
||||||
|
|
||||||
@ -18,13 +18,15 @@
|
|||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
sort: number;
|
||||||
|
showFullDescription: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Application = {
|
type Application = {
|
||||||
recruiter: string;
|
recruiter: string;
|
||||||
message: string;
|
message: string;
|
||||||
company: string;
|
company: string;
|
||||||
agency: boolean,
|
agency: boolean;
|
||||||
flairs: SimpleFlair[];
|
flairs: SimpleFlair[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,16 +42,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
application.flairs.sort((a, b) => {
|
application.flairs.sort((a, b) => {
|
||||||
if (a.description && b.description) {
|
const aDesc = a.description && a.showFullDescription === 1;
|
||||||
return 0;
|
const bDesc = b.description && b.showFullDescription === 1;
|
||||||
|
|
||||||
|
if (aDesc && bDesc) {
|
||||||
|
return b.sort - a.sort;
|
||||||
}
|
}
|
||||||
if (a.description) {
|
if (aDesc) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (b.description) {
|
if (bDesc) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return b.sort - a.sort;
|
||||||
});
|
});
|
||||||
|
|
||||||
loadFlairs();
|
loadFlairs();
|
||||||
@ -82,7 +87,7 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="flex items-center w-full flex-col">
|
<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="bg-white rounded-lg p-3">
|
||||||
<div class="w-full flex">
|
<div class="w-full flex">
|
||||||
<h1 class="text-black text-5xl">Andre Henriques</h1>
|
<h1 class="text-black text-5xl">Andre Henriques</h1>
|
||||||
@ -113,7 +118,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex py-9">
|
<div class="w-full flex py-3 print:pb-0">
|
||||||
<div>
|
<div>
|
||||||
I am a dedicated and versatile programmer with four years of professional
|
I am a dedicated and versatile programmer with four years of professional
|
||||||
experience. <br />
|
experience. <br />
|
||||||
@ -128,18 +133,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if application}
|
{#if application}
|
||||||
{#if !application.agency}
|
{#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
|
👋 Hello
|
||||||
{#if application.recruiter}
|
{#if application.recruiter}
|
||||||
<span class="font-bold">{application.recruiter}</span> @
|
<span class="font-bold">{application.recruiter}</span> @
|
||||||
<span class="font-bold">{application.company}</span>
|
<span class="font-bold">{application.company}</span>
|
||||||
{:else if application.company}
|
{:else if application.company}
|
||||||
recruiter @ <span class="font-bold">{application.company}</span>
|
recruiter @ <span class="font-bold">{application.company}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</h2>
|
</h2>
|
||||||
{/if}
|
{:else}
|
||||||
|
<div class="p-5 print:hidden"></div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if application.message}
|
{#if application.message}
|
||||||
<div class="p-3 bg-white w-[190mm] rounded-lg">
|
<div class="p-3 bg-white w-[190mm] rounded-lg">
|
||||||
@ -153,41 +161,44 @@
|
|||||||
{#if application.flairs.length > 0}
|
{#if application.flairs.length > 0}
|
||||||
<div class="p-3 bg-white w-[190mm] rounded-lg">
|
<div class="p-3 bg-white w-[190mm] rounded-lg">
|
||||||
<h1 class="flex gap-5 items-end">
|
<h1 class="flex gap-5 items-end">
|
||||||
Your Ad & My skills {#if flairs.length > 0}<input
|
Skills {#if flairs.length > 0}<input
|
||||||
placeholder="Search other skills!"
|
placeholder="Click here to search skills!"
|
||||||
class="flex-grow text-blue-500 print:hidden"
|
class="flex-grow text-blue-500 print:hidden"
|
||||||
bind:value={otherSearch}
|
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}
|
{/if}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex flex-wrap gap-2 py-2">
|
<div class="flex flex-wrap gap-2 py-2 print:py-0">
|
||||||
{#if otherSearch === ''}
|
{#if otherSearch === ''}
|
||||||
{#each application.flairs as flair}
|
{#each application.flairs as flair}
|
||||||
<div class="min-w-0 {flair.description ? 'flex-grow w-full' : ''}">
|
{@const hasDesc = flair.description && flair.showFullDescription === 1}
|
||||||
{#if flair.description}
|
<div class="min-w-0 {hasDesc ? 'flex-grow w-full' : ''}">
|
||||||
|
{#if hasDesc}
|
||||||
<div
|
<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};"
|
style="background: {flair.color};"
|
||||||
>
|
>
|
||||||
<div
|
<Pill
|
||||||
class="rounded-lg print:p-2 print:inline-block"
|
text-size="text-base print:text-sm"
|
||||||
style="background: {flair.color}; print-color-adjust: exact !important;"
|
class="print:inline-block text-2xl py-0 mb-2"
|
||||||
|
color={flair.color}
|
||||||
>
|
>
|
||||||
{flair.name}
|
{flair.name}
|
||||||
</div>
|
</Pill>
|
||||||
<span class="hidden print:inline">:</span>
|
<span class="hidden print:inline">:</span>
|
||||||
<div class="bg-white my-1 print:inline p-1 rounded-md">
|
<div class="bg-white my-1 print:inline p-1 rounded-md">
|
||||||
{flair.description}
|
{flair.description}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<Pill text-size="text-base print:text-sm" color={flair.color}>
|
||||||
class="p-2 rounded-lg forced-color-adjust-none"
|
|
||||||
style="background: {flair.color}; print-color-adjust: exact !important;"
|
|
||||||
>
|
|
||||||
{flair.name}
|
{flair.name}
|
||||||
</div>
|
</Pill>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@ -196,10 +207,10 @@
|
|||||||
a.name.match(new RegExp(otherSearch, 'i'))
|
a.name.match(new RegExp(otherSearch, 'i'))
|
||||||
)}
|
)}
|
||||||
{#if filtered_list.length == 0}
|
{#if filtered_list.length == 0}
|
||||||
<div class="w-full text-center text-blue-500 font-bold text-2xl py-10">
|
<div class="w-full text-center text-blue-500 font-bold text-2xl py-10">
|
||||||
Could not find the skill you are looking for.
|
Could not find the skill you are looking for.
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
{#each filtered_list as flair}
|
{#each filtered_list as flair}
|
||||||
<div class="min-w-0 {flair.description ? 'flex-grow w-full' : ''}">
|
<div class="min-w-0 {flair.description ? 'flex-grow w-full' : ''}">
|
||||||
{#if flair.description}
|
{#if flair.description}
|
||||||
@ -234,20 +245,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<div class="p-2"></div>
|
|
||||||
<div class="w-[190mm]">
|
<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
|
Work Expericence
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-2"></div>
|
|
||||||
|
|
||||||
<div class="w-[100vw] flex items-center flex-col">
|
<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>
|
<h1>Senior Software Developer @ Planum Solucoes</h1>
|
||||||
|
<h2>4 years - May 2020 - Present</h2>
|
||||||
<div class="ml-5">
|
<div class="ml-5">
|
||||||
<h2>4 years - May 2020 - Present</h2>
|
|
||||||
<h3>Developed various projects:</h3>
|
<h3>Developed various projects:</h3>
|
||||||
<ul class="pl-5 list-disc">
|
<ul class="pl-5 list-disc">
|
||||||
<li>Developing various websites using React and Svelte.</li>
|
<li>Developing various websites using React and Svelte.</li>
|
||||||
@ -255,30 +264,20 @@
|
|||||||
<li>Implemented an ORM system using Java Reflection</li>
|
<li>Implemented an ORM system using Java Reflection</li>
|
||||||
<li>Implemented automatic deployment with GitLab CI/CD tools.</li>
|
<li>Implemented automatic deployment with GitLab CI/CD tools.</li>
|
||||||
<li>Linux Server Administration</li>
|
<li>Linux Server Administration</li>
|
||||||
<li>Technologies used: React, WebRTC, WebSockets, Rest, Google Maps AP</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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="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>
|
||||||
<div>
|
<div>
|
||||||
<h1>Associate Devops Engineer @ Sky UK</h1>
|
<h1>Associate Devops Engineer @ Sky UK</h1>
|
||||||
<div class="ml-5">
|
<h2>1 year - July 2022 - June 2023</h2>
|
||||||
<h2>1 year - July 2022 - June 2023</h2>
|
<div class="ml-2">
|
||||||
<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>
|
|
||||||
<ul class="pl-5 list-disc">
|
<ul class="pl-5 list-disc">
|
||||||
<li>Developed web-based tools for the DevOps team to use</li>
|
<li>Developed web-based tools for the DevOps team to use</li>
|
||||||
<li>
|
<li>
|
||||||
@ -298,13 +297,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-5"></div>
|
<div class="w-[190mm]">
|
||||||
|
<h2 class="p-3 print:p-0 text-4xl print:text-xl font-bold print:font-normal text-white">
|
||||||
<div class="bg-white p-3 text-black rounded-lg w-[190mm]">
|
Education
|
||||||
<h2 class="pb-2 text-3xl">Education</h2>
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white p-2 text-black rounded-lg w-[190mm] print:text-sm">
|
||||||
<div>
|
<div>
|
||||||
<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">
|
<div class="ml-5">
|
||||||
<h2>July 2020 - June 2024</h2>
|
<h2>July 2020 - June 2024</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -312,6 +313,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="p-3 print:hidden"></div>
|
||||||
|
|
||||||
<!--div class="p-5"></div>
|
<!--div class="p-5"></div>
|
||||||
<div>TODO: Previous projetcs</div -->
|
<div>TODO: Previous projetcs</div -->
|
||||||
<!-- div class="p-5"></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>
|
<label class="flabel" for="color">Color</label>
|
||||||
<input required type="color" id="color" bind:value={edit.color} />
|
<input required type="color" id="color" bind:value={edit.color} />
|
||||||
</fieldset>
|
</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>
|
<fieldset>
|
||||||
<label class="flabel" for="name">Name</label>
|
<label class="flabel" for="name">Name</label>
|
||||||
<input required id="name" class="finput w-full" bind:value={edit.name} />
|
<input required id="name" class="finput w-full" bind:value={edit.name} />
|
||||||
@ -106,12 +118,15 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<label class="flabel" for="description">Description</label>
|
<label class="flabel" for="description">Description</label>
|
||||||
<textarea
|
<textarea
|
||||||
required
|
|
||||||
id="description"
|
id="description"
|
||||||
class="finput w-full"
|
class="finput w-full"
|
||||||
bind:value={edit.description}
|
bind:value={edit.description}
|
||||||
></textarea>
|
></textarea>
|
||||||
</fieldset>
|
</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">
|
<div class="btns w-full">
|
||||||
<button class="btn-confirm">Save</button>
|
<button class="btn-confirm">Save</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,11 +8,6 @@
|
|||||||
import { extractLinkNodePosX, extractLinkNodePosY } from './types';
|
import { extractLinkNodePosX, extractLinkNodePosY } from './types';
|
||||||
import { deleteR, get, post, put } from '$lib/utils';
|
import { deleteR, get, post, put } from '$lib/utils';
|
||||||
import icon_list from './icons-list.json';
|
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
|
// constatns
|
||||||
@ -47,7 +42,8 @@
|
|||||||
height: 4,
|
height: 4,
|
||||||
name: 'Created',
|
name: 'Created',
|
||||||
icon: 'plus',
|
icon: 'plus',
|
||||||
permission: 1
|
permission: 1,
|
||||||
|
visible: false
|
||||||
} as Node,
|
} as Node,
|
||||||
...nodesRequest
|
...nodesRequest
|
||||||
];
|
];
|
||||||
@ -72,7 +68,7 @@
|
|||||||
y: a.target_y
|
y: a.target_y
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
console.log("test", linkRequests)
|
console.log('test', linkRequests);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('TODO inform user', e);
|
console.log('TODO inform user', e);
|
||||||
}
|
}
|
||||||
@ -191,7 +187,8 @@
|
|||||||
width,
|
width,
|
||||||
icon: icon_list[Math.floor(Math.random() * icon_list.length)],
|
icon: icon_list[Math.floor(Math.random() * icon_list.length)],
|
||||||
name: 'New Status',
|
name: 'New Status',
|
||||||
permission: 0
|
permission: 0,
|
||||||
|
visible: true
|
||||||
} as Node);
|
} as Node);
|
||||||
nodes.push(result);
|
nodes.push(result);
|
||||||
// Tell svelte that nodes is updated
|
// Tell svelte that nodes is updated
|
||||||
@ -419,16 +416,19 @@
|
|||||||
{linkMode}
|
{linkMode}
|
||||||
linkSource={nodes[i] === linkSource?.node ? linkSource : undefined}
|
linkSource={nodes[i] === linkSource?.node ? linkSource : undefined}
|
||||||
onremove={async () => {
|
onremove={async () => {
|
||||||
try {
|
try {
|
||||||
await deleteR(`user/status/node/${nodes[i].id}`);
|
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.splice(i, 1);
|
||||||
nodes = nodes;
|
nodes = nodes;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("TODO inform the user", e)
|
console.log('TODO inform the user', e);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onNodePointClick={async (inNodeX, inNodeY) => {
|
onNodePointClick={async (inNodeX, inNodeY) => {
|
||||||
if (mouseAction === undefined) {
|
if (mouseAction === undefined) {
|
||||||
@ -471,7 +471,7 @@
|
|||||||
// Tell svelte that the links changed
|
// Tell svelte that the links changed
|
||||||
links = links;
|
links = links;
|
||||||
linkSource = undefined;
|
linkSource = undefined;
|
||||||
mouseAction = undefined;
|
mouseAction = undefined;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('inform the user', e);
|
console.log('inform the user', e);
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,43 @@
|
|||||||
>
|
>
|
||||||
<div class="bi bi-x mt-[2px]"></div>
|
<div class="bi bi-x mt-[2px]"></div>
|
||||||
</button>
|
</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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,60 +1,69 @@
|
|||||||
export type Node = {
|
export type Node = {
|
||||||
id: string,
|
id: string;
|
||||||
user_id: string,
|
user_id: string;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
// 1 disables editing
|
// 1 disables editing
|
||||||
permission: number;
|
permission: number;
|
||||||
|
visible: boolean;
|
||||||
|
endable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LinkNode = {
|
export type LinkNode = {
|
||||||
node: Node;
|
node: Node;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type FullLink = {
|
export type FullLink = {
|
||||||
sourceNode: LinkNode;
|
sourceNode: LinkNode;
|
||||||
targetNode: LinkNode;
|
targetNode: LinkNode;
|
||||||
bi?: boolean;
|
bi?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type FullLinkApi = {
|
export type FullLinkApi = {
|
||||||
id: string,
|
id: string;
|
||||||
user_id: string,
|
user_id: string;
|
||||||
target_node: string | null,
|
target_node: string | null;
|
||||||
source_node: string | null,
|
source_node: string | null;
|
||||||
bi: boolean,
|
bi: boolean;
|
||||||
source_x: number,
|
source_x: number;
|
||||||
source_y: number,
|
source_y: number;
|
||||||
|
|
||||||
target_x: number,
|
|
||||||
target_y: number
|
|
||||||
}
|
|
||||||
|
|
||||||
|
target_x: number;
|
||||||
|
target_y: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ActionType = undefined | 'drag' | 'create' | 'move' | 'link';
|
export type ActionType = undefined | 'drag' | 'create' | 'move' | 'link';
|
||||||
|
|
||||||
export function extractLinkNodePosX(canvasX: (x: number) => number, node: LinkNode, grid_size: number) {
|
export function extractLinkNodePosX(
|
||||||
if (node.x === -1) {
|
canvasX: (x: number) => number,
|
||||||
return canvasX(node.node.x);
|
node: LinkNode,
|
||||||
}
|
grid_size: number
|
||||||
if (node.x === node.node.width) {
|
) {
|
||||||
return canvasX(node.node.x) + node.node.width * grid_size;
|
if (node.x === -1) {
|
||||||
}
|
return canvasX(node.node.x);
|
||||||
return canvasX(node.node.x) + node.x * grid_size + grid_size / 2;
|
}
|
||||||
|
if (node.x === node.node.width) {
|
||||||
|
return canvasX(node.node.x) + node.node.width * grid_size;
|
||||||
|
}
|
||||||
|
return canvasX(node.node.x) + node.x * grid_size + grid_size / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractLinkNodePosY(canvasY: (x: number) => number, node: LinkNode, grid_size: number) {
|
export function extractLinkNodePosY(
|
||||||
if (node.y === -1) {
|
canvasY: (x: number) => number,
|
||||||
return canvasY(node.node.y);
|
node: LinkNode,
|
||||||
}
|
grid_size: number
|
||||||
if (node.y === node.node.height) {
|
) {
|
||||||
return canvasY(node.node.y) + node.node.height * grid_size;
|
if (node.y === -1) {
|
||||||
}
|
return canvasY(node.node.y);
|
||||||
return canvasY(node.node.y) + node.y * grid_size + grid_size / 2;
|
}
|
||||||
|
if (node.y === node.node.height) {
|
||||||
|
return canvasY(node.node.y) + node.node.height * grid_size;
|
||||||
|
}
|
||||||
|
return canvasY(node.node.y) + node.y * grid_size + grid_size / 2;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
async function submit() {
|
async function submit() {
|
||||||
try {
|
try {
|
||||||
await post('application/text', text);
|
await post('application/text', text);
|
||||||
applicationStore.loadApplications(true);
|
applicationStore.loadAll(true);
|
||||||
goto('/');
|
goto('/');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(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">
|
<script lang="ts">
|
||||||
import { applicationStore } from '$lib/ApplicationsStore.svelte';
|
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 companies = $derived(new Set(applicationStore.all.map((a) => a.company)));
|
||||||
|
|
||||||
let fcomps = $derived(
|
let fcomps = $derived(
|
||||||
company === ''
|
applicationStore.all[index].company === ''
|
||||||
? []
|
? []
|
||||||
: [...companies.values()].filter((a) => {
|
: [...companies.values()].filter((a) => {
|
||||||
// TODO improve this a lot I want to make like matching algo
|
// 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>
|
</script>
|
||||||
|
|
||||||
<fieldset class="grow">
|
<fieldset class="grow">
|
||||||
<label class="flabel" for="title">Company</label>
|
<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">
|
<div class="flex gap-2 flex-wrap">
|
||||||
{#each fcomps as comp}
|
{#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}
|
{comp}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/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();
|
onreload();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Show message to the user
|
// Show message to the user
|
||||||
console.log(e);
|
console.log('TODO inform the user', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ApplicationStatus, ApplicationStatusMaping, type Application } from '$lib/ApplicationsStore.svelte';
|
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
||||||
import { post, put } from '$lib/utils';
|
import { statusStore } from '$lib/Types.svelte';
|
||||||
|
import { post } from '$lib/utils';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
application,
|
application,
|
||||||
@ -13,43 +14,21 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let filter = $state('');
|
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) {
|
async function submit(item: Application) {
|
||||||
try {
|
try {
|
||||||
application.linked_application = item.id;
|
await post(`application/link/application/${application.id}/${item.id}`, {});
|
||||||
await put('application/update', application);
|
applicationStore.removeByID(application.id);
|
||||||
await put('application/status', {
|
|
||||||
id: application.id,
|
|
||||||
status: ApplicationStatus.LinkedApplication
|
|
||||||
});
|
|
||||||
dialog.close();
|
dialog.close();
|
||||||
onreload(item);
|
onreload(item);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Show message to the user
|
// TODO: Show message to the user
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
<dialog class="card max-w-[50vw]" bind:this={dialog}>
|
let internal = $derived(
|
||||||
<div class="flex">
|
applicationStore.all.filter((i) => {
|
||||||
<input placeholder="Filter" class="p-2 flex-grow" bind:value={filter} />
|
|
||||||
<div>
|
|
||||||
{applications.length}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="overflow-y-auto overflow-x-hidden flex-grow p-2">
|
|
||||||
{#each applications.filter((i) => {
|
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -62,7 +41,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return x.match(f);
|
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">
|
<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)}>
|
<button class="text-left max-w-full" type="button" onclick={() => submit(item)}>
|
||||||
<h2 class="text-lg text-blue-500">
|
<h2 class="text-lg text-blue-500">
|
||||||
@ -73,9 +64,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</h2>
|
</h2>
|
||||||
<span>
|
<span>
|
||||||
{ApplicationStatusMaping[item.status]}
|
{statusStore.nodesR[item.status_id].name}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Application } from '$lib/ApplicationsStore.svelte';
|
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
||||||
import { post, preventDefault } from '$lib/utils';
|
import { post, preventDefault } from '$lib/utils';
|
||||||
|
|
||||||
let {
|
const {
|
||||||
onreload
|
onreload
|
||||||
}: {
|
}: {
|
||||||
onreload: (item: Application) => void;
|
onreload: (item: Application) => void;
|
||||||
@ -12,20 +12,50 @@
|
|||||||
|
|
||||||
let link = $state('');
|
let link = $state('');
|
||||||
|
|
||||||
|
let dialogAddMultiple = $state(false);
|
||||||
|
|
||||||
|
let inputField: HTMLTextAreaElement;
|
||||||
|
|
||||||
async function createApplication() {
|
async function createApplication() {
|
||||||
try {
|
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', {
|
const r: Application = await post('application/link', {
|
||||||
text: link
|
text: link
|
||||||
});
|
});
|
||||||
|
applicationStore.all.push(r);
|
||||||
onreload(r);
|
onreload(r);
|
||||||
dialogElement.close()
|
dialogElement.close();
|
||||||
|
link = '';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Inform the user', e);
|
console.log('TODO: Inform the user', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function docKey(e: KeyboardEvent) {
|
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();
|
dialogElement.showModal();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -41,11 +71,34 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</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)}>
|
<form onsubmit={preventDefault(createApplication)}>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label class="flabel" for="title">Link</label>
|
<label class="flabel" for="title"
|
||||||
<input class="finput" id="title" bind:value={link} required />
|
>{#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>
|
</fieldset>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
{#if dialogAddMultiple}
|
||||||
|
<button type="submit" class="btn-primary">Submit</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Application } from '$lib/ApplicationsStore.svelte';
|
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
||||||
import { post, preventDefault } from '$lib/utils';
|
import { post, preventDefault } from '$lib/utils';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
dialog = $bindable(),
|
dialog = $bindable(),
|
||||||
onreload,
|
onreload,
|
||||||
id
|
id,
|
||||||
|
index
|
||||||
}: {
|
}: {
|
||||||
dialog: HTMLDialogElement;
|
dialog: HTMLDialogElement;
|
||||||
onreload: (item: Application) => void;
|
onreload: (item: Application) => void;
|
||||||
id: string;
|
id: string;
|
||||||
openWindow?: Window | null;
|
openWindow?: Window | null;
|
||||||
|
index?: number;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const data = $state({
|
const data = $state({
|
||||||
@ -27,6 +29,9 @@
|
|||||||
});
|
});
|
||||||
data.url = '';
|
data.url = '';
|
||||||
dialog.close();
|
dialog.close();
|
||||||
|
if (newItem.id !== applicationStore.all[index ?? -1].id) {
|
||||||
|
applicationStore.removeByID(applicationStore.all[index ?? -1].id);
|
||||||
|
}
|
||||||
onreload(newItem);
|
onreload(newItem);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Show message to the user
|
// Show message to the user
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ApplicationStatusMaping, type Application } from '$lib/ApplicationsStore.svelte';
|
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
||||||
import { post } from '$lib/utils';
|
import { statusStore } from '$lib/Types.svelte';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
application,
|
application,
|
||||||
@ -11,18 +11,11 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let filter = $state('');
|
let filter = $state('');
|
||||||
let applications: Application[] = $state([]);
|
|
||||||
|
|
||||||
let dialogElement: HTMLDialogElement;
|
let dialogElement: HTMLDialogElement;
|
||||||
|
|
||||||
async function getApplicationList() {
|
|
||||||
applications = await post('application/list', {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function docKey(e: KeyboardEvent) {
|
function docKey(e: KeyboardEvent) {
|
||||||
if (e.ctrlKey && e.code === 'KeyK') {
|
if (e.ctrlKey && e.code === 'KeyK') {
|
||||||
applications = [];
|
|
||||||
getApplicationList();
|
|
||||||
dialogElement.showModal();
|
dialogElement.showModal();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -38,7 +31,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
let internal = $derived(
|
let internal = $derived(
|
||||||
applications.filter((i) => {
|
applicationStore.all.filter((i) => {
|
||||||
|
if (i.linked_application) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (application && i.id == application.id) {
|
if (application && i.id == application.id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -96,7 +92,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
<span>
|
<span>
|
||||||
{ApplicationStatusMaping[item.status]}
|
{statusStore.nodesR[item.status_id]?.name ?? 'NOT FOUND'}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,13 +2,11 @@
|
|||||||
import {
|
import {
|
||||||
type Application,
|
type Application,
|
||||||
type ApplicationEvent,
|
type ApplicationEvent,
|
||||||
ApplicationStatus,
|
EventType
|
||||||
EventType,
|
|
||||||
ApplicationStatusIconMaping,
|
|
||||||
ApplicationStatusMaping
|
|
||||||
} from '$lib/ApplicationsStore.svelte';
|
} 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([]);
|
let events: (ApplicationEvent & { timeDiff: string })[] = $state([]);
|
||||||
|
|
||||||
@ -36,17 +34,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
let status: number | undefined = undefined;
|
|
||||||
|
|
||||||
let lastEvent: ApplicationEvent | undefined = undefined;
|
let lastEvent: ApplicationEvent | undefined = undefined;
|
||||||
|
|
||||||
let _events: typeof events = [];
|
let _events: typeof events = [];
|
||||||
|
|
||||||
let checkArray: (number | undefined)[] = [
|
|
||||||
ApplicationStatus.WorkingOnIt,
|
|
||||||
ApplicationStatus.ToApply
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let event of application.events) {
|
for (let event of application.events) {
|
||||||
let time = '';
|
let time = '';
|
||||||
if (lastEvent) {
|
if (lastEvent) {
|
||||||
@ -55,25 +46,6 @@
|
|||||||
time = calcDiff(d2 - d1);
|
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) {
|
if (lastEvent) {
|
||||||
let d1 = new Date(lastEvent.time).getTime();
|
let d1 = new Date(lastEvent.time).getTime();
|
||||||
let d2 = new Date(event.time).getTime();
|
let d2 = new Date(event.time).getTime();
|
||||||
@ -84,26 +56,21 @@
|
|||||||
_events[_events.length - 1].timeDiff = time;
|
_events[_events.length - 1].timeDiff = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = event.new_status;
|
|
||||||
_events.push({ ...event, timeDiff: '' });
|
_events.push({ ...event, timeDiff: '' });
|
||||||
lastEvent = event;
|
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 d1 = new Date(_events[_events.length - 1].time).getTime();
|
||||||
let d2 = new Date().getTime();
|
let d2 = new Date().getTime();
|
||||||
_events[_events.length - 1].timeDiff = calcDiff(d2 - d1);
|
_events[_events.length - 1].timeDiff = calcDiff(d2 - d1);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
events = _events;
|
events = _events;
|
||||||
});
|
});
|
||||||
|
|
||||||
let endable: number[] = [
|
|
||||||
ApplicationStatus.Expired,
|
|
||||||
ApplicationStatus.Ignore,
|
|
||||||
ApplicationStatus.ApplyedButSaidNo,
|
|
||||||
ApplicationStatus.LinkedApplication
|
|
||||||
];
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if events.length > 0}
|
{#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"
|
class="shadow-sm shadow-violet-500 border rounded-full min-w-[50px] min-h-[50px] grid place-items-center bg-white z-10"
|
||||||
>
|
>
|
||||||
{#if event.event_type == EventType.Creation}
|
{#if event.event_type == EventType.Creation}
|
||||||
<span
|
<span title={`Created @\n ${new Date(event.time).toLocaleString()}`} class="bi bi-plus"
|
||||||
title={`Created @\n ${new Date(event.time).toLocaleString()}`}
|
|
||||||
class="bi bi-plus"
|
|
||||||
></span>
|
></span>
|
||||||
{:else if event.event_type == EventType.View}
|
{:else if event.event_type == EventType.View}
|
||||||
<span
|
<span title={`Viewed @\n ${new Date(event.time).toLocaleString()}`} class="bi bi-eye"
|
||||||
title={`Viewed @\n ${new Date(event.time).toLocaleString()}`}
|
|
||||||
class="bi bi-eye"
|
|
||||||
></span>
|
></span>
|
||||||
{:else}
|
{:else}
|
||||||
<span
|
<span
|
||||||
title={`${ApplicationStatusMaping[event.new_status]}\n${new Date(
|
title={`${statusStore.nodesR[event.new_status_id].name}\n${new Date(
|
||||||
event.time
|
event.time
|
||||||
).toLocaleString()}`}
|
).toLocaleString()}`}
|
||||||
class="bi bi-{ApplicationStatusIconMaping[event.new_status]}"
|
class="bi bi-{statusStore.nodesR[event.new_status_id].icon}"
|
||||||
></span>
|
></span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if i != events.length - 1 || !endable.includes(event.new_status)}
|
<!-- TODO -->
|
||||||
<div
|
<!-- || !endable.includes(event.new_status) -->
|
||||||
class="min-w-[70px] h-[18px] bg-blue-500 -mx-[10px] px-[20px] flex-grow text-center"
|
{#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}
|
{#if event.timeDiff}
|
||||||
<div class="-mt-[3px] text-white">
|
<div class="-mt-[3px] text-white">
|
||||||
<span class="bi bi-clock"></span>
|
<span class="bi bi-clock"></span>
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
||||||
ApplicationStatus,
|
|
||||||
ApplicationStatusMaping,
|
|
||||||
applicationStore,
|
|
||||||
type Application
|
|
||||||
} from '$lib/ApplicationsStore.svelte';
|
|
||||||
|
|
||||||
import { put, preventDefault, post, get } from '$lib/utils';
|
import { put, preventDefault, post, get } from '$lib/utils';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@ -16,10 +11,15 @@
|
|||||||
import SearchApplication from './SearchApplication.svelte';
|
import SearchApplication from './SearchApplication.svelte';
|
||||||
import NewApplication from './NewApplication.svelte';
|
import NewApplication from './NewApplication.svelte';
|
||||||
import Timeline from './Timeline.svelte';
|
import Timeline from './Timeline.svelte';
|
||||||
import DropingZone from './DropingZone.svelte';
|
|
||||||
import CompanyField from './CompanyField.svelte';
|
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 extractTokens: HTMLDialogElement;
|
||||||
let changeUrl: HTMLDialogElement;
|
let changeUrl: HTMLDialogElement;
|
||||||
@ -32,45 +32,30 @@
|
|||||||
|
|
||||||
let drag = $state(true);
|
let drag = $state(true);
|
||||||
|
|
||||||
async function activate(item?: Application) {
|
async function activate(item?: Application, ow = true) {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
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);
|
openWindow(item.url);
|
||||||
openCV(item.id);
|
|
||||||
}
|
}
|
||||||
|
openCV(item.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await put('application/status', {
|
const toLoadItem: Application = await get(`application/${item.id}`);
|
||||||
id: item.id,
|
if (!toLoadItem?.simple_url) {
|
||||||
status: ApplicationStatus.WorkingOnIt
|
|
||||||
});
|
|
||||||
activeItem = await get('application/active');
|
|
||||||
if (!activeItem?.unique_url) {
|
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
changeUrl.showModal();
|
changeUrl.showModal();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
activeItem = applicationStore.getIndexById(toLoadItem.id);
|
||||||
|
applicationStore.all[activeItem ?? -1] = toLoadItem;
|
||||||
|
applicationStore.reset();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Show User
|
// Show User
|
||||||
console.log('info data', e);
|
console.log('info data', e);
|
||||||
}
|
}
|
||||||
applicationStore.loadApplications(true);
|
|
||||||
|
|
||||||
autoExtData = false;
|
autoExtData = false;
|
||||||
lastExtData = undefined;
|
lastExtData = undefined;
|
||||||
@ -90,7 +75,7 @@
|
|||||||
|
|
||||||
function openCV(id?: string) {
|
function openCV(id?: string) {
|
||||||
window.open(
|
window.open(
|
||||||
`/cv?id=${id ?? activeItem!.id}`,
|
`/cv?id=${id ?? derivedItem!.id}`,
|
||||||
'cv viwer',
|
'cv viwer',
|
||||||
`location,height=${window.screen.height},width=${window.screen.width},scrollbars,status,toolbar,menubar,popup`
|
`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
|
// Make the CV open on a new page
|
||||||
//
|
//
|
||||||
function docKey(e: KeyboardEvent) {
|
function docKey(e: KeyboardEvent) {
|
||||||
if (activeItem && e.ctrlKey && e.code === 'KeyO') {
|
if (derivedItem && e.ctrlKey && e.code === 'KeyO') {
|
||||||
openCV(activeItem.id);
|
openCV(derivedItem.id);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
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
|
// Load Ext
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
function onMessage(e: MessageEvent) {
|
function onMessage(e: MessageEvent) {
|
||||||
@ -150,21 +123,22 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function setExtData() {
|
function setExtData() {
|
||||||
if (!lastExtData || !activeItem) return;
|
if (!lastExtData || activeItem === undefined || !derivedItem) return;
|
||||||
activeItem.title = lastExtData.jobTitle;
|
applicationStore.all[activeItem].title = lastExtData.jobTitle;
|
||||||
activeItem.company = lastExtData.company;
|
applicationStore.all[activeItem].company = lastExtData.company;
|
||||||
activeItem.payrange = lastExtData.money;
|
applicationStore.all[activeItem].payrange = lastExtData.money;
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
save().then(async () => {
|
save().then(async () => {
|
||||||
|
if (activeItem === undefined) return;
|
||||||
if (!lastExtData.description) {
|
if (!lastExtData.description) {
|
||||||
lastExtData = undefined;
|
lastExtData = undefined;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await post('application/text/flair', {
|
await post('application/text/flair', {
|
||||||
id: activeItem?.id ?? '',
|
id: derivedItem.id ?? '',
|
||||||
text: lastExtData.description
|
text: lastExtData.description
|
||||||
});
|
});
|
||||||
loadActive();
|
activate(derivedItem, false);
|
||||||
lastExtData = undefined;
|
lastExtData = undefined;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -172,20 +146,26 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
userStore.checkLogin();
|
userStore.checkLogin();
|
||||||
loadActive();
|
statusStore.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!applicationStore.loadItem) {
|
if (!applicationStore.loadItem) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
activeItem = applicationStore.loadItem;
|
(async () => {
|
||||||
applicationStore.loadItem = undefined;
|
await activate(applicationStore.loadItem, applicationStore.loadItemOpen);
|
||||||
|
applicationStore.loadItem = undefined;
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
applicationStore.loadAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
try {
|
try {
|
||||||
await put('application/update', activeItem);
|
await put('application/update', applicationStore.all[activeItem ?? -1]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Show User
|
// Show User
|
||||||
console.log('info data', e);
|
console.log('info data', e);
|
||||||
@ -194,7 +174,7 @@
|
|||||||
|
|
||||||
async function resetUrl() {
|
async function resetUrl() {
|
||||||
try {
|
try {
|
||||||
activeItem = await post(`application/reset/url/${activeItem?.id}`, {});
|
activeItem = await post(`application/reset/url/${derivedItem?.id}`, {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Show User
|
// Show User
|
||||||
console.log('info data', e);
|
console.log('info data', e);
|
||||||
@ -203,11 +183,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col w-full gap-2 min-w-0 relative" role="none">
|
<div class="flex flex-col w-full gap-2 min-w-0 relative" role="none">
|
||||||
{#if activeItem && (!applicationStore.dragging || applicationStore.dragging?.id === activeItem.id)}
|
{#if activeItem !== undefined && (!applicationStore.dragging || applicationStore.dragging?.id === derivedItem.id)}
|
||||||
<div
|
<div
|
||||||
draggable={drag}
|
draggable={drag}
|
||||||
ondrag={() => {
|
ondrag={() => {
|
||||||
applicationStore.dragStart(activeItem);
|
applicationStore.dragStart(derivedItem);
|
||||||
}}
|
}}
|
||||||
ondragend={() => {
|
ondragend={() => {
|
||||||
window.requestAnimationFrame(() => {
|
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]"
|
class="flex flex-col p-2 h-full gap-2 card min-w-0 flex-grow min-h-[50vh]"
|
||||||
>
|
>
|
||||||
<div class="w-full">
|
<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">
|
<div class="bg-danger text-white p-2 rounded-lg text-lg font-bold">
|
||||||
{(ApplicationStatusMaping[
|
{statusStore.nodesR[derivedItem.status_id].name}
|
||||||
activeItem?.status as (typeof ApplicationStatus)[keyof typeof ApplicationStatus]
|
|
||||||
] as string) ?? ''}
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if showExtraData}
|
{#if showExtraData}
|
||||||
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||||
<div class="flabel">Id</div>
|
<div class="flabel">Id</div>
|
||||||
<div class="finput bg-white w-full break-keep">
|
<div class="finput bg-white w-full break-keep">
|
||||||
{activeItem.id}
|
{derivedItem.id}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||||
<div class="flabel">Create Time</div>
|
<div class="flabel">Create Time</div>
|
||||||
<div class="finput bg-white w-full break-keep">
|
<div class="finput bg-white w-full break-keep">
|
||||||
{activeItem.create_time}
|
{derivedItem.create_time}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<CompanyField bind:company={activeItem.company} {save} />
|
<CompanyField index={activeItem} {save} />
|
||||||
<fieldset class="grow">
|
<fieldset class="grow">
|
||||||
<label class="flabel" for="title">Recruiter</label>
|
<label class="flabel" for="title">Recruiter</label>
|
||||||
<input
|
<input
|
||||||
class="finput"
|
class="finput"
|
||||||
id="title"
|
id="title"
|
||||||
bind:value={activeItem.recruiter}
|
bind:value={applicationStore.all[activeItem].recruiter}
|
||||||
onchange={save}
|
onchange={save}
|
||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@ -256,7 +235,7 @@
|
|||||||
class="finput"
|
class="finput"
|
||||||
id="title"
|
id="title"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
bind:checked={activeItem.agency}
|
bind:checked={applicationStore.all[activeItem].agency}
|
||||||
onchange={save}
|
onchange={save}
|
||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@ -266,7 +245,7 @@
|
|||||||
<input
|
<input
|
||||||
class="finput"
|
class="finput"
|
||||||
id="title"
|
id="title"
|
||||||
bind:value={activeItem.title}
|
bind:value={applicationStore.all[activeItem].title}
|
||||||
onchange={save}
|
onchange={save}
|
||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@ -275,51 +254,36 @@
|
|||||||
<input
|
<input
|
||||||
class="finput"
|
class="finput"
|
||||||
id="payrange"
|
id="payrange"
|
||||||
bind:value={activeItem.payrange}
|
bind:value={applicationStore.all[activeItem].payrange}
|
||||||
onchange={save}
|
onchange={save}
|
||||||
/>
|
/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{#if !activeItem.unique_url || showExtraData}
|
{#if !derivedItem.simple_url || showExtraData}
|
||||||
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||||
<div class="flabel">Url</div>
|
<div class="flabel">Url</div>
|
||||||
<div class="finput bg-white w-full break-keep">
|
<div class="finput bg-white w-full break-keep">
|
||||||
{activeItem.url}
|
{derivedItem.url}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeItem.unique_url}
|
{#if derivedItem.simple_url}
|
||||||
<fieldset>
|
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||||
<div class="flabel">Unique Url</div>
|
<div class="flabel">Simple Url</div>
|
||||||
<div class="finput bg-white">
|
<div class="finput bg-white w-full break-keep">
|
||||||
{activeItem.unique_url}
|
{derivedItem.simple_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}
|
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{/if}
|
{/if}
|
||||||
<div>
|
<div>
|
||||||
<div class="flabel">Tags</div>
|
<div class="flabel">Tags</div>
|
||||||
<div class="flex gap-2 flex-wrap">
|
<div class="flex gap-2 flex-wrap">
|
||||||
{#each activeItem.flairs as item}
|
{#each derivedItem.flairs as item}
|
||||||
<Flair
|
<Flair
|
||||||
{item}
|
{item}
|
||||||
allowDelete
|
allowDelete
|
||||||
application={activeItem}
|
application={derivedItem}
|
||||||
updateApplication={(item) => (activeItem = item)}
|
updateApplication={(item) =>
|
||||||
|
(applicationStore.all[activeItem ?? -1] = item)}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@ -329,7 +293,7 @@
|
|||||||
<textarea
|
<textarea
|
||||||
class="finput"
|
class="finput"
|
||||||
id="extra"
|
id="extra"
|
||||||
bind:value={activeItem.extra_data}
|
bind:value={applicationStore.all[activeItem].extra_data}
|
||||||
onchange={save}
|
onchange={save}
|
||||||
></textarea>
|
></textarea>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@ -338,24 +302,16 @@
|
|||||||
<textarea
|
<textarea
|
||||||
class="finput"
|
class="finput"
|
||||||
id="extra"
|
id="extra"
|
||||||
bind:value={activeItem.message}
|
bind:value={applicationStore.all[activeItem].message}
|
||||||
onchange={save}
|
onchange={save}
|
||||||
></textarea>
|
></textarea>
|
||||||
</fieldset>
|
</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>
|
||||||
<div class="flex btns">
|
<div class="flex btns">
|
||||||
<button
|
<button
|
||||||
class="btn-confirm"
|
class="btn-confirm"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
openWindow(activeItem!.url);
|
openWindow(derivedItem!.url);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Open
|
Open
|
||||||
@ -378,18 +334,16 @@
|
|||||||
Get Ext Data
|
Get Ext Data
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeItem.original_url == null}
|
<button
|
||||||
<button class="btn-primary" onclick={() => changeUrl.showModal()}>
|
class={derivedItem.simple_url ? 'btn-danger' : 'btn-primary'}
|
||||||
Update Url
|
onclick={() => changeUrl.showModal()}
|
||||||
</button>
|
>
|
||||||
{/if}
|
Update Url
|
||||||
|
</button>
|
||||||
<div class="px-10"></div>
|
<div class="px-10"></div>
|
||||||
<button class="btn-primary" onclick={() => linkApplication.showModal()}>
|
<button class="btn-primary" onclick={() => linkApplication.showModal()}>
|
||||||
Link Application
|
Link Application
|
||||||
</button>
|
</button>
|
||||||
{#if activeItem.original_url != null}
|
|
||||||
<button class="btn-danger" onclick={resetUrl}> Reset Url </button>
|
|
||||||
{/if}
|
|
||||||
<button
|
<button
|
||||||
class:btn-primary={drag}
|
class:btn-primary={drag}
|
||||||
class:btn-danger={!drag}
|
class:btn-danger={!drag}
|
||||||
@ -405,9 +359,9 @@
|
|||||||
🔬
|
🔬
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Timeline application={activeItem} showAll={showExtraData} />
|
<Timeline application={derivedItem} showAll={showExtraData} />
|
||||||
</div>
|
</div>
|
||||||
<DropingZone bind:activeItem />
|
<AutoDropZone bind:index={activeItem} />
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="p-2 h-full w-full gap-2 flex-grow card grid place-items-center min-h-[50vh]"
|
class="p-2 h-full w-full gap-2 flex-grow card grid place-items-center min-h-[50vh]"
|
||||||
@ -432,32 +386,35 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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
|
<NewUrlDialog
|
||||||
id={activeItem?.id ?? ''}
|
id={derivedItem?.id ?? ''}
|
||||||
bind:dialog={changeUrl}
|
bind:dialog={changeUrl}
|
||||||
|
index={activeItem}
|
||||||
onreload={async (item) => {
|
onreload={async (item) => {
|
||||||
item.events = await get(`events/${item.id}`);
|
activate(item, false);
|
||||||
activeItem = item;
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if activeItem}
|
{#if derivedItem}
|
||||||
<LinkApplication
|
<LinkApplication
|
||||||
application={activeItem}
|
application={derivedItem}
|
||||||
bind:dialog={linkApplication}
|
bind:dialog={linkApplication}
|
||||||
onreload={(item) => (activeItem = item)}
|
onreload={(item) => activate(item, false)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<SearchApplication
|
<SearchApplication
|
||||||
application={activeItem}
|
application={derivedItem}
|
||||||
onreload={async (item) => {
|
onreload={async (item) => {
|
||||||
item.events = await get(`events/${item.id}`);
|
activate(item);
|
||||||
activeItem = item;
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user