chore: update to so many things
This commit is contained in:
parent
5253204e17
commit
5380eaffeb
@ -1,11 +1,12 @@
|
|||||||
package com.andr3h3nriqu3s.applications
|
package com.andr3h3nriqu3s.applications
|
||||||
|
|
||||||
import java.sql.ResultSet
|
import java.sql.ResultSet
|
||||||
import java.util.UUID
|
|
||||||
import java.util.Date
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.UUID
|
||||||
import kotlin.collections.emptyList
|
import kotlin.collections.emptyList
|
||||||
import kotlin.collections.setOf
|
import kotlin.collections.setOf
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.MediaType
|
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
|
||||||
@ -20,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestBody
|
|||||||
import org.springframework.web.bind.annotation.RequestHeader
|
import org.springframework.web.bind.annotation.RequestHeader
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
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
|
||||||
|
|
||||||
data class Application(
|
data class Application(
|
||||||
var id: String,
|
var id: String,
|
||||||
@ -37,6 +39,7 @@ data class Application(
|
|||||||
var linked_application: String,
|
var linked_application: String,
|
||||||
var status_history: String,
|
var status_history: String,
|
||||||
var application_time: String,
|
var application_time: String,
|
||||||
|
var create_time: String,
|
||||||
var flairs: List<Flair>,
|
var flairs: List<Flair>,
|
||||||
var views: List<View>,
|
var views: List<View>,
|
||||||
) {
|
) {
|
||||||
@ -58,6 +61,7 @@ data class Application(
|
|||||||
rs.getString("linked_application"),
|
rs.getString("linked_application"),
|
||||||
rs.getString("status_history"),
|
rs.getString("status_history"),
|
||||||
rs.getString("application_time"),
|
rs.getString("application_time"),
|
||||||
|
rs.getString("create_time"),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
)
|
)
|
||||||
@ -109,6 +113,77 @@ class ApplicationsController(
|
|||||||
return CVData(application.company, application.recruiter, application.message, flairs)
|
return CVData(application.company, application.recruiter, application.message, flairs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Create a new application from the link */
|
||||||
|
@PostMapping(path = ["/link"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
|
public fun submitLink(
|
||||||
|
@RequestBody submit: SubmitRequest,
|
||||||
|
@RequestHeader("token") token: String
|
||||||
|
): Application {
|
||||||
|
val user = sessionService.verifyTokenThrow(token)
|
||||||
|
|
||||||
|
var application =
|
||||||
|
Application(
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
submit.text,
|
||||||
|
submit.text,
|
||||||
|
submit.text,
|
||||||
|
"New Application",
|
||||||
|
user.id,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
emptyList(),
|
||||||
|
emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!applicationService.createApplication(user, application)) {
|
||||||
|
throw ResponseStatusException(HttpStatus.CONFLICT, "Application already exists", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Created: ")
|
||||||
|
println(application)
|
||||||
|
|
||||||
|
return application
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(path = ["/text/flair"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
|
public fun textFlair(
|
||||||
|
@RequestBody info: FlairRequest,
|
||||||
|
@RequestHeader("token") token: String
|
||||||
|
): Int {
|
||||||
|
val user = sessionService.verifyTokenThrow(token)
|
||||||
|
val application = applicationService.findApplicationById(user, info.id)
|
||||||
|
if (application == null) {
|
||||||
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Application not found", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val flairs = flairService.listUser(user)
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
|
||||||
|
for (flair: Flair in flairs) {
|
||||||
|
val regex =
|
||||||
|
Regex(
|
||||||
|
".*" + flair.expr + ".*",
|
||||||
|
setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (regex.matches(info.text)) {
|
||||||
|
count += 1
|
||||||
|
flairService.linkFlair(application, flair)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(path = ["/text"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
@PostMapping(path = ["/text"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
public fun submitText(
|
public fun submitText(
|
||||||
@RequestBody submit: SubmitRequest,
|
@RequestBody submit: SubmitRequest,
|
||||||
@ -179,7 +254,7 @@ class ApplicationsController(
|
|||||||
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,
|
||||||
if (elm.contains("linkedin")) elm.split("?")[0] else null,
|
if (elm.contains("linkedin")) elm.split("?")[0] else null,
|
||||||
"New Aplication",
|
"New Application",
|
||||||
user.id,
|
user.id,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
@ -190,6 +265,7 @@ class ApplicationsController(
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
emptyList(),
|
emptyList(),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
)
|
)
|
||||||
@ -205,37 +281,6 @@ class ApplicationsController(
|
|||||||
return applications.size
|
return applications.size
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(path = ["/text/flair"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
|
||||||
public fun textFlair(
|
|
||||||
@RequestBody info: FlairRequest,
|
|
||||||
@RequestHeader("token") token: String
|
|
||||||
): Int {
|
|
||||||
val user = sessionService.verifyTokenThrow(token)
|
|
||||||
val application = applicationService.findApplicationById(user, info.id)
|
|
||||||
if (application == null) {
|
|
||||||
throw NotFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
val flairs = flairService.listUser(user)
|
|
||||||
|
|
||||||
var count = 0
|
|
||||||
|
|
||||||
for (flair: Flair in flairs) {
|
|
||||||
val regex =
|
|
||||||
Regex(
|
|
||||||
".*" + flair.expr + ".*",
|
|
||||||
setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (regex.matches(info.text)) {
|
|
||||||
count += 1
|
|
||||||
flairService.linkFlair(application, flair)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(path = ["/list"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
@PostMapping(path = ["/list"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
public fun list(
|
public fun list(
|
||||||
@RequestBody info: ListRequest,
|
@RequestBody info: ListRequest,
|
||||||
@ -267,10 +312,10 @@ class ApplicationsController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
application.status = info.status
|
application.status = info.status
|
||||||
val status_string = "${info.status}";
|
val status_string = "${info.status}"
|
||||||
var status_history = application.status_history.split(",").filter { it.length >= 1 }
|
var status_history = application.status_history.split(",").filter { it.length >= 1 }
|
||||||
if (status_history.indexOf(status_string) == -1) {
|
if (status_history.indexOf(status_string) == -1) {
|
||||||
status_history = status_history.plus("${info.status}");
|
status_history = status_history.plus("${info.status}")
|
||||||
}
|
}
|
||||||
|
|
||||||
application.status_history = status_history.joinToString(",") { it }
|
application.status_history = status_history.joinToString(",") { it }
|
||||||
@ -308,11 +353,11 @@ class ApplicationsController(
|
|||||||
var application = applicationService.findApplicationById(user, info.id)
|
var application = applicationService.findApplicationById(user, info.id)
|
||||||
|
|
||||||
if (application == null) {
|
if (application == null) {
|
||||||
throw NotFound()
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Application not found", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (application.unique_url != null) {
|
if (application.unique_url != null) {
|
||||||
throw BadRequest()
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Application already has unique_url", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
application.original_url = application.url
|
application.original_url = application.url
|
||||||
@ -341,6 +386,7 @@ class ApplicationsController(
|
|||||||
applicationService.update(application)
|
applicationService.update(application)
|
||||||
|
|
||||||
application.flairs = flairService.listFromLinkApplicationId(application.id)
|
application.flairs = flairService.listFromLinkApplicationId(application.id)
|
||||||
|
application.views = viewService.listFromApplicationId(application.id)
|
||||||
|
|
||||||
return application
|
return application
|
||||||
}
|
}
|
||||||
@ -485,6 +531,7 @@ class ApplicationService(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create time is auto created by the database
|
||||||
db.update(
|
db.update(
|
||||||
"insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status, company, recruiter, message, linked_application, status_history, application_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
"insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status, company, recruiter, message, linked_application, status_history, application_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||||
application.id,
|
application.id,
|
||||||
@ -527,7 +574,6 @@ class ApplicationService(
|
|||||||
.toList()
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return db.query(
|
return db.query(
|
||||||
"select * from applications where user_id=? and status=? order by title asc;",
|
"select * from applications where user_id=? and status=? order by title asc;",
|
||||||
arrayOf(user.id, info.status),
|
arrayOf(user.id, info.status),
|
||||||
@ -537,6 +583,7 @@ class ApplicationService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun update(application: Application): Application {
|
public fun update(application: Application): Application {
|
||||||
|
// 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=?, status=?, company=?, recruiter=?, message=?, linked_application=?, status_history=?, application_time=? where id=?",
|
"update applications set url=?, original_url=?, unique_url=?, title=?, user_id=?, extra_data=?, payrange=?, status=?, company=?, recruiter=?, message=?, linked_application=?, status_history=?, application_time=? where id=?",
|
||||||
application.url,
|
application.url,
|
||||||
|
@ -25,7 +25,8 @@ create table if not exists applications (
|
|||||||
extra_data text,
|
extra_data text,
|
||||||
status integer,
|
status integer,
|
||||||
linked_application text default '',
|
linked_application text default '',
|
||||||
application_time text default ''
|
application_time text default '',
|
||||||
|
create_time timestamp default now()
|
||||||
);
|
);
|
||||||
|
|
||||||
create table if not exists views (
|
create table if not exists views (
|
||||||
|
@ -40,3 +40,5 @@ ## Building
|
|||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
https://www.glassdoor.co.uk/job-listing/junior-software-developer-full-stack-onnec-group-JV_IC2671300_KO0,36_KE37,48.htm?jl=1009478590946&utm_source=jobsForYou&utm_medium=email&utm_content=jobs-for-you-jobsForYou-jobpos5-1009478590946&utm_campaign=jobsForYou&src=GD_JOB_AD&uido=5EF1E454F911F36A51BB1DD9CB97C8DB&ao=1136043&jrtk=6-y100011i9mkhgisgqqb801ab7157ddf833b2e1c&cs=1_57b69a24&s=362&t=REC_JOBS&pos=105&guid=00000192656d7c18b164f224f0a88e83&jobListingId=1009478590946&ea=1&vt=e&cb=1728410338190&ctt=1728466768707
|
https://www.glassdoor.co.uk/job-listing/junior-software-developer-full-stack-onnec-group-JV_IC2671300_KO0,36_KE37,48.htm?jl=1009478590946&utm_source=jobsForYou&utm_medium=email&utm_content=jobs-for-you-jobsForYou-jobpos5-1009478590946&utm_campaign=jobsForYou&src=GD_JOB_AD&uido=5EF1E454F911F36A51BB1DD9CB97C8DB&ao=1136043&jrtk=6-y100011i9mkhgisgqqb801ab7157ddf833b2e1c&cs=1_57b69a24&s=362&t=REC_JOBS&pos=105&guid=00000192656d7c18b164f224f0a88e83&jobListingId=1009478590946&ea=1&vt=e&cb=1728410338190&ctt=1728466768707
|
||||||
|
|
||||||
|
https://careers.veeva.com/job/866d4776-9d23-4311-ab16-4ebff725984d/frontend-engineer-react-remote-london-united-kingdom/
|
||||||
|
@ -49,6 +49,8 @@ export type Application = {
|
|||||||
message: string;
|
message: string;
|
||||||
linked_application: string;
|
linked_application: string;
|
||||||
application_time: string;
|
application_time: string;
|
||||||
|
create_time: string;
|
||||||
|
status_history: string;
|
||||||
flairs: Flair[];
|
flairs: Flair[];
|
||||||
views: View[];
|
views: View[];
|
||||||
};
|
};
|
||||||
|
@ -150,7 +150,7 @@
|
|||||||
<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
|
Your Ad & My skills {#if flairs.length > 0}<input
|
||||||
placeholder="Loking for other skills search?"
|
placeholder="Search other skills!"
|
||||||
class="flex-grow text-blue-500 print:hidden"
|
class="flex-grow text-blue-500 print:hidden"
|
||||||
bind:value={otherSearch}
|
bind:value={otherSearch}
|
||||||
/>
|
/>
|
||||||
|
@ -21,119 +21,125 @@
|
|||||||
| AsEnum<typeof ApplicationStatus>
|
| AsEnum<typeof ApplicationStatus>
|
||||||
| 'Linkedin'
|
| 'Linkedin'
|
||||||
| 'Glassdoor'
|
| 'Glassdoor'
|
||||||
| 'Unknown Source'
|
| 'Direct Source';
|
||||||
| 'Applications';
|
|
||||||
|
|
||||||
let nodeTypes: Record<NodeType, number> = {
|
let graph = {} as Record<string, Record<string, number>>;
|
||||||
[ApplicationStatus.ToApply]: 0,
|
|
||||||
[ApplicationStatus.WorkingOnIt]: 0,
|
|
||||||
[ApplicationStatus.Ignore]: 0,
|
|
||||||
[ApplicationStatus.ApplyedButSaidNo]: 0,
|
|
||||||
[ApplicationStatus.Expired]: 0,
|
|
||||||
[ApplicationStatus.Applyed]: 0,
|
|
||||||
[ApplicationStatus.TasksToDo]: 0,
|
|
||||||
[ApplicationStatus.LinkedApplication]: 0,
|
|
||||||
Linkedin: 0,
|
|
||||||
Glassdoor: 0,
|
|
||||||
'Unknown Source': 0,
|
|
||||||
Applications: applications.length
|
|
||||||
};
|
|
||||||
|
|
||||||
const showPercentage: string[] = [
|
function addGraph(inSource: NodeType, inTarget: NodeType) {
|
||||||
`${ApplicationStatus.ToApply}`,
|
const source = `${inSource}`;
|
||||||
`${ApplicationStatus.Ignore}`,
|
const target = `${inTarget}`;
|
||||||
`${ApplicationStatus.Expired}`,
|
if (graph[source] == undefined) {
|
||||||
`${ApplicationStatus.Applyed}`,
|
graph[source] = {} as Record<NodeType, number>;
|
||||||
`${ApplicationStatus.LinkedApplication}`,
|
}
|
||||||
`${ApplicationStatus.ApplyedButSaidNo}`,
|
graph[source][target] = (graph[source][target] ?? 0) + 1;
|
||||||
`${ApplicationStatus.TasksToDo}`
|
return target as NodeType;
|
||||||
];
|
}
|
||||||
|
|
||||||
const baseLinks: { source: NodeType; target: NodeType; value: 0 | 1; end?: boolean }[] =
|
|
||||||
[
|
|
||||||
{ source: 'Linkedin', target: 'Applications', value: 0 },
|
|
||||||
{ source: 'Glassdoor', target: 'Applications', value: 0 },
|
|
||||||
{ source: 'Unknown Source', target: 'Applications', value: 0 },
|
|
||||||
{ source: 'Applications', target: ApplicationStatus.ToApply, value: 1 },
|
|
||||||
{ source: 'Applications', target: ApplicationStatus.Ignore, value: 1 },
|
|
||||||
{ source: 'Applications', target: ApplicationStatus.Expired, value: 1 },
|
|
||||||
{ source: 'Applications', target: ApplicationStatus.Applyed, value: 1 },
|
|
||||||
{
|
|
||||||
source: 'Applications',
|
|
||||||
target: ApplicationStatus.LinkedApplication,
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
source: ApplicationStatus.Applyed,
|
|
||||||
target: ApplicationStatus.ApplyedButSaidNo,
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: ApplicationStatus.Applyed,
|
|
||||||
target: ApplicationStatus.TasksToDo,
|
|
||||||
value: 1
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
applications.forEach((a) => {
|
applications.forEach((a) => {
|
||||||
|
let source: NodeType;
|
||||||
|
|
||||||
if (a.url.includes('linkedin')) {
|
if (a.url.includes('linkedin')) {
|
||||||
nodeTypes['Linkedin'] += 1;
|
source = 'Linkedin';
|
||||||
} else if (a.url.includes('glassdoor')) {
|
} else if (a.url.includes('glassdoor')) {
|
||||||
nodeTypes['Glassdoor'] += 1;
|
source = 'Glassdoor';
|
||||||
} else {
|
} else {
|
||||||
nodeTypes['Unknown Source'] += 1;
|
source = 'Direct Source';
|
||||||
}
|
|
||||||
if (a.status !== ApplicationStatus.WorkingOnIt) {
|
|
||||||
nodeTypes[a.status] += 1;
|
|
||||||
} else {
|
|
||||||
nodeTypes[ApplicationStatus.ToApply] += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
[ApplicationStatus.ApplyedButSaidNo, ApplicationStatus.TasksToDo].includes(
|
(
|
||||||
|
[
|
||||||
|
ApplicationStatus.Ignore,
|
||||||
|
ApplicationStatus.Expired,
|
||||||
|
ApplicationStatus.LinkedApplication,
|
||||||
|
ApplicationStatus.ToApply,
|
||||||
|
ApplicationStatus.Applyed
|
||||||
|
] as AsEnum<typeof ApplicationStatus>[]
|
||||||
|
).includes(a.status)
|
||||||
|
) {
|
||||||
|
addGraph(source, a.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge case for working on it
|
||||||
|
if (a.status === ApplicationStatus.WorkingOnIt) {
|
||||||
|
addGraph(source, ApplicationStatus.ToApply);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.status == ApplicationStatus.ApplyedButSaidNo) {
|
||||||
|
const history = a.status_history.split(',');
|
||||||
|
source = addGraph(source, ApplicationStatus.Applyed);
|
||||||
|
if (history.includes(`${ApplicationStatus.TasksToDo}`)) {
|
||||||
|
source = addGraph(source, ApplicationStatus.TasksToDo);
|
||||||
|
}
|
||||||
|
addGraph(source, ApplicationStatus.ApplyedButSaidNo);
|
||||||
|
} else if (
|
||||||
|
([ApplicationStatus.TasksToDo] as AsEnum<typeof ApplicationStatus>[]).includes(
|
||||||
a.status
|
a.status
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
nodeTypes[ApplicationStatus.Applyed] += 1;
|
addGraph(source, ApplicationStatus.Applyed);
|
||||||
|
addGraph(ApplicationStatus.Applyed, a.status);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let inNodes: string[] = [];
|
let inNodes: string[] = Object.keys(graph).reduce((acc, elm) => {
|
||||||
|
const arr = Object.keys(graph[elm]).concat(elm);
|
||||||
let nodes = (Object.keys(nodeTypes) as (keyof typeof nodeTypes)[])
|
for (const i of arr) {
|
||||||
.filter((a) => nodeTypes[a] > 0)
|
if (!acc.includes(i)) {
|
||||||
.map((a, i) => {
|
acc.push(i);
|
||||||
inNodes.push(`${a}`);
|
|
||||||
const base = {
|
|
||||||
value: nodeTypes[a],
|
|
||||||
originalValue: a,
|
|
||||||
id: '',
|
|
||||||
index: i,
|
|
||||||
percentage: Math.trunc((nodeTypes[a] / applications.length) * 100),
|
|
||||||
end: showPercentage.includes(`${a}`)
|
|
||||||
};
|
|
||||||
if (Number.isNaN(Number(a))) {
|
|
||||||
base.id = a as string;
|
|
||||||
} else {
|
|
||||||
base.id = ApplicationStatusMaping[a as AsEnum<typeof ApplicationStatus>];
|
|
||||||
}
|
}
|
||||||
return base;
|
}
|
||||||
});
|
return acc;
|
||||||
|
}, [] as string[]);
|
||||||
|
|
||||||
const links = baseLinks
|
function getGraphValueFor(node: string): number {
|
||||||
.filter(
|
return Object.keys(graph).reduce((acc, i) => {
|
||||||
(link) =>
|
if (i == node) return acc;
|
||||||
inNodes.includes(`${link.source}`) && inNodes.includes(`${link.target}`)
|
if (graph[i][node] != undefined) {
|
||||||
)
|
return acc + graph[i][node];
|
||||||
.map((link) => {
|
}
|
||||||
const source = inNodes.indexOf(`${link.source}`);
|
return acc;
|
||||||
const target = inNodes.indexOf(`${link.target}`);
|
}, 0);
|
||||||
return {
|
}
|
||||||
source: source,
|
|
||||||
target: target,
|
let nodes = inNodes.map((node, i) => {
|
||||||
value: [nodes[source], nodes[target]][link.value].value
|
let name = '';
|
||||||
};
|
if (Number.isNaN(Number(node))) {
|
||||||
});
|
name = node;
|
||||||
|
} else {
|
||||||
|
name = ApplicationStatusMaping[Number(node) as AsEnum<typeof ApplicationStatus>];
|
||||||
|
}
|
||||||
|
const value = getGraphValueFor(node);
|
||||||
|
const base = {
|
||||||
|
value: value,
|
||||||
|
originalValue: node,
|
||||||
|
id: name,
|
||||||
|
index: i,
|
||||||
|
percentage: Math.trunc((value / applications.length) * 100),
|
||||||
|
end: true
|
||||||
|
};
|
||||||
|
return base;
|
||||||
|
});
|
||||||
|
|
||||||
|
type Link = {
|
||||||
|
source: number;
|
||||||
|
target: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const links = Object.keys(graph).reduce((acc, source) => {
|
||||||
|
return acc.concat(Object.keys(graph[source]).map((target) => {
|
||||||
|
const ns = inNodes.indexOf(`${source}`);
|
||||||
|
const nt = inNodes.indexOf(`${target}`);
|
||||||
|
return {
|
||||||
|
source: ns,
|
||||||
|
target: nt,
|
||||||
|
value: graph[source][target]
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}, [] as Link[])
|
||||||
|
|
||||||
const bounding = chartDiv.getBoundingClientRect();
|
const bounding = chartDiv.getBoundingClientRect();
|
||||||
|
|
||||||
@ -146,6 +152,8 @@
|
|||||||
|
|
||||||
sankey.nodes(nodes).links(links).layout(32);
|
sankey.nodes(nodes).links(links).layout(32);
|
||||||
|
|
||||||
|
console.log("here2", nodes, links);
|
||||||
|
|
||||||
const svg = d3
|
const svg = d3
|
||||||
.select(chartDiv)
|
.select(chartDiv)
|
||||||
.append('svg')
|
.append('svg')
|
||||||
@ -171,6 +179,9 @@
|
|||||||
.append('path')
|
.append('path')
|
||||||
.attr('class', 'link')
|
.attr('class', 'link')
|
||||||
.attr('d', path)
|
.attr('d', path)
|
||||||
|
.style('stroke', function(d) {
|
||||||
|
return d3.rgb(color[d.source.index]).toString();
|
||||||
|
})
|
||||||
.style('stroke-width', function (d) {
|
.style('stroke-width', function (d) {
|
||||||
return Math.max(1, d.dy);
|
return Math.max(1, d.dy);
|
||||||
})
|
})
|
||||||
@ -269,7 +280,6 @@
|
|||||||
|
|
||||||
:global(.link) {
|
:global(.link) {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: #000;
|
stroke-opacity: 0.5;
|
||||||
stroke-opacity: 0.2;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
51
site/src/routes/work-area/NewApplication.svelte
Normal file
51
site/src/routes/work-area/NewApplication.svelte
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Application } from '$lib/ApplicationsStore.svelte';
|
||||||
|
import { post, preventDefault } from '$lib/utils';
|
||||||
|
|
||||||
|
let {
|
||||||
|
onreload
|
||||||
|
}: {
|
||||||
|
onreload: (item: Application) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
let dialogElement: HTMLDialogElement;
|
||||||
|
|
||||||
|
let link = $state('');
|
||||||
|
|
||||||
|
async function createApplication() {
|
||||||
|
try {
|
||||||
|
const r: Application = await post('application/link', {
|
||||||
|
text: link
|
||||||
|
});
|
||||||
|
onreload(r);
|
||||||
|
dialogElement.close()
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Inform the user', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function docKey(e: KeyboardEvent) {
|
||||||
|
if (e.ctrlKey && e.code === 'KeyN') {
|
||||||
|
dialogElement.showModal();
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
document.addEventListener('keydown', docKey, false);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', docKey);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<dialog class="card max-w-[50vw]" bind:this={dialogElement}>
|
||||||
|
<form onsubmit={preventDefault(createApplication)}>
|
||||||
|
<fieldset>
|
||||||
|
<label class="flabel" for="title">Link</label>
|
||||||
|
<input class="finput" id="title" bind:value={link} required />
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
109
site/src/routes/work-area/SearchApplication.svelte
Normal file
109
site/src/routes/work-area/SearchApplication.svelte
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
ApplicationStatus,
|
||||||
|
ApplicationStatusMaping,
|
||||||
|
type Application
|
||||||
|
} from '$lib/ApplicationsStore.svelte';
|
||||||
|
import type { AsEnum } from '$lib/ApplicationsStore.svelte';
|
||||||
|
import { post } from '$lib/utils';
|
||||||
|
|
||||||
|
let {
|
||||||
|
application,
|
||||||
|
onreload
|
||||||
|
}: {
|
||||||
|
application?: Application;
|
||||||
|
onreload: (item: Application) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
let filter = $state('');
|
||||||
|
let applications: Application[] = $state([]);
|
||||||
|
|
||||||
|
let dialogElement: HTMLDialogElement;
|
||||||
|
|
||||||
|
async function getApplicationList() {
|
||||||
|
applications = await post('application/list', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
getApplicationList();
|
||||||
|
});
|
||||||
|
|
||||||
|
function docKey(e: KeyboardEvent) {
|
||||||
|
if (e.ctrlKey && e.code === 'KeyK') {
|
||||||
|
dialogElement.showModal();
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
document.addEventListener('keydown', docKey, false);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', docKey);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<dialog class="card max-w-[50vw]" bind:this={dialogElement}>
|
||||||
|
<div class="flex">
|
||||||
|
<input placeholder="Filter" class="p-2 flex-grow" bind:value={filter} />
|
||||||
|
<div>
|
||||||
|
{applications.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-y-auto overflow-x-hidden flex-grow p-2">
|
||||||
|
{#each applications.filter((i) => {
|
||||||
|
if (application && i.id == application.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!filter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.includes('@') && i.company) {
|
||||||
|
const f = new RegExp(filter.split('@')[0].trim(), 'ig');
|
||||||
|
const c = new RegExp(filter.split('@')[1].trim(), 'ig');
|
||||||
|
return i.title.match(f) && i.company.match(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
const f = new RegExp(filter, 'ig');
|
||||||
|
|
||||||
|
let x = i.title;
|
||||||
|
|
||||||
|
if (i.company) {
|
||||||
|
x = `${x} @ ${i.company}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.match(f);
|
||||||
|
}) as item}
|
||||||
|
<div class="card p-2 my-2 bg-slate-100 max-w-full" role="none">
|
||||||
|
<button class="text-left max-w-full" type="button" onclick={() => {
|
||||||
|
dialogElement.close()
|
||||||
|
onreload(item)
|
||||||
|
}}>
|
||||||
|
<h2 class="text-lg text-blue-500 flex justify-between">
|
||||||
|
<div>
|
||||||
|
{item.title}
|
||||||
|
{#if item.company}
|
||||||
|
<div class="text-violet-800">
|
||||||
|
@ {item.company}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{#if !([ApplicationStatus.ToApply, ApplicationStatus.WorkingOnIt] as AsEnum<ApplicationStatus>[]).includes(item!.status)}
|
||||||
|
{ApplicationStatusMaping[item.status]}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</h2>
|
||||||
|
<span
|
||||||
|
class="text-violet-600 overflow-hidden whitespace-nowrap block max-w-full"
|
||||||
|
>
|
||||||
|
{item.url}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</dialog>
|
@ -3,7 +3,8 @@
|
|||||||
ApplicationStatus,
|
ApplicationStatus,
|
||||||
ApplicationStatusMaping,
|
ApplicationStatusMaping,
|
||||||
applicationStore,
|
applicationStore,
|
||||||
type Application
|
type Application,
|
||||||
|
type AsEnum
|
||||||
} from '$lib/ApplicationsStore.svelte';
|
} from '$lib/ApplicationsStore.svelte';
|
||||||
|
|
||||||
import { put, preventDefault, post, get, deleteR } from '$lib/utils';
|
import { put, preventDefault, post, get, deleteR } from '$lib/utils';
|
||||||
@ -14,6 +15,8 @@
|
|||||||
import DropZone from './DropZone.svelte';
|
import DropZone from './DropZone.svelte';
|
||||||
import { userStore } from '$lib/UserStore.svelte';
|
import { userStore } from '$lib/UserStore.svelte';
|
||||||
import LinkApplication from './LinkApplication.svelte';
|
import LinkApplication from './LinkApplication.svelte';
|
||||||
|
import SearchApplication from './SearchApplication.svelte';
|
||||||
|
import NewApplication from './NewApplication.svelte';
|
||||||
|
|
||||||
let activeItem: Application | undefined = $state();
|
let activeItem: Application | undefined = $state();
|
||||||
|
|
||||||
@ -24,6 +27,8 @@
|
|||||||
let lastExtData: any = $state(undefined);
|
let lastExtData: any = $state(undefined);
|
||||||
let autoExtData = false;
|
let autoExtData = false;
|
||||||
|
|
||||||
|
let showExtraData = $state(false);
|
||||||
|
|
||||||
async function activate(item?: Application) {
|
async function activate(item?: Application) {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
@ -113,8 +118,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('setting up interest');
|
|
||||||
|
|
||||||
window.addEventListener('message', onMessage);
|
window.addEventListener('message', onMessage);
|
||||||
window.postMessage({ type: 'REGISTER_INTEREST' });
|
window.postMessage({ type: 'REGISTER_INTEREST' });
|
||||||
return () => {
|
return () => {
|
||||||
@ -156,7 +159,7 @@
|
|||||||
applicationStore.loadItem = undefined;
|
applicationStore.loadItem = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function moveStatus(status: number) {
|
async function moveStatus(status: AsEnum<typeof ApplicationStatus>, moveOut = true) {
|
||||||
if (!activeItem) return;
|
if (!activeItem) return;
|
||||||
// Deactivate active item
|
// Deactivate active item
|
||||||
try {
|
try {
|
||||||
@ -171,7 +174,11 @@
|
|||||||
}
|
}
|
||||||
applicationStore.loadApplications(true);
|
applicationStore.loadApplications(true);
|
||||||
applicationStore.loadAplyed(true);
|
applicationStore.loadAplyed(true);
|
||||||
activeItem = undefined;
|
if (moveOut) {
|
||||||
|
activeItem = undefined;
|
||||||
|
} else {
|
||||||
|
activeItem.status = status;
|
||||||
|
}
|
||||||
//openedWindow?.close();
|
//openedWindow?.close();
|
||||||
//openedWindow = undefined;
|
//openedWindow = undefined;
|
||||||
}
|
}
|
||||||
@ -240,6 +247,20 @@
|
|||||||
{statusMapping}
|
{statusMapping}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if showExtraData}
|
||||||
|
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||||
|
<div class="flabel">Id</div>
|
||||||
|
<div class="finput bg-white w-full break-keep">
|
||||||
|
{activeItem.id}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||||
|
<div class="flabel">Create Time</div>
|
||||||
|
<div class="finput bg-white w-full break-keep">
|
||||||
|
{activeItem.create_time}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{/if}
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<fieldset class="grow">
|
<fieldset class="grow">
|
||||||
<label class="flabel" for="title">Company</label>
|
<label class="flabel" for="title">Company</label>
|
||||||
@ -395,6 +416,13 @@
|
|||||||
>
|
>
|
||||||
👋
|
👋
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class:btn-primary={!showExtraData}
|
||||||
|
class:btn-confirm={showExtraData}
|
||||||
|
onclick={() => (showExtraData = !showExtraData)}
|
||||||
|
>
|
||||||
|
🔬
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if applicationStore.dragging}
|
{#if applicationStore.dragging}
|
||||||
@ -423,13 +451,15 @@
|
|||||||
>
|
>
|
||||||
Ignore it
|
Ignore it
|
||||||
</DropZone>
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if [ApplicationStatus.WorkingOnIt, ApplicationStatus.Expired].includes(activeItem.status)}
|
||||||
<!-- Expired -->
|
<!-- Expired -->
|
||||||
<DropZone
|
<DropZone
|
||||||
icon="clock-fill text-orange-500"
|
icon="clock-fill text-orange-500"
|
||||||
ondrop={() => {
|
ondrop={() => {
|
||||||
if (activeItem && activeItem.status === ApplicationStatus.Expired) {
|
if (activeItem && activeItem.status === ApplicationStatus.Expired) {
|
||||||
moveStatus(ApplicationStatus.ToApply);
|
moveStatus(ApplicationStatus.ToApply, false);
|
||||||
} else {
|
} else {
|
||||||
moveStatus(ApplicationStatus.Expired);
|
moveStatus(ApplicationStatus.Expired);
|
||||||
}
|
}
|
||||||
@ -437,7 +467,9 @@
|
|||||||
>
|
>
|
||||||
Mark as expired
|
Mark as expired
|
||||||
</DropZone>
|
</DropZone>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
|
||||||
<!-- Repeated -->
|
<!-- Repeated -->
|
||||||
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}
|
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}
|
||||||
>Delete it</DropZone
|
>Delete it</DropZone
|
||||||
@ -522,3 +554,11 @@
|
|||||||
onreload={(item) => (activeItem = item)}
|
onreload={(item) => (activeItem = item)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<SearchApplication application={activeItem} onreload={(item) => (activeItem = item)} />
|
||||||
|
|
||||||
|
<NewApplication
|
||||||
|
onreload={(item) => {
|
||||||
|
activate(item);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
@ -3,6 +3,9 @@ import { defineConfig } from 'vite';
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit()],
|
plugins: [sveltekit()],
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
},
|
||||||
build: {
|
build: {
|
||||||
commonjsOptions: {
|
commonjsOptions: {
|
||||||
esmExternals: true
|
esmExternals: true
|
||||||
|
Loading…
Reference in New Issue
Block a user