chore: update to so many things
This commit is contained in:
parent
5253204e17
commit
5380eaffeb
@ -1,11 +1,12 @@
|
||||
package com.andr3h3nriqu3s.applications
|
||||
|
||||
import java.sql.ResultSet
|
||||
import java.util.UUID
|
||||
import java.util.Date
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.UUID
|
||||
import kotlin.collections.emptyList
|
||||
import kotlin.collections.setOf
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.jdbc.core.JdbcTemplate
|
||||
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.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
|
||||
data class Application(
|
||||
var id: String,
|
||||
@ -37,6 +39,7 @@ data class Application(
|
||||
var linked_application: String,
|
||||
var status_history: String,
|
||||
var application_time: String,
|
||||
var create_time: String,
|
||||
var flairs: List<Flair>,
|
||||
var views: List<View>,
|
||||
) {
|
||||
@ -58,6 +61,7 @@ data class Application(
|
||||
rs.getString("linked_application"),
|
||||
rs.getString("status_history"),
|
||||
rs.getString("application_time"),
|
||||
rs.getString("create_time"),
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
)
|
||||
@ -109,6 +113,77 @@ class ApplicationsController(
|
||||
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])
|
||||
public fun submitText(
|
||||
@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 null,
|
||||
if (elm.contains("linkedin")) elm.split("?")[0] else null,
|
||||
"New Aplication",
|
||||
"New Application",
|
||||
user.id,
|
||||
"",
|
||||
"",
|
||||
@ -190,6 +265,7 @@ class ApplicationsController(
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
)
|
||||
@ -205,37 +281,6 @@ class ApplicationsController(
|
||||
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])
|
||||
public fun list(
|
||||
@RequestBody info: ListRequest,
|
||||
@ -267,10 +312,10 @@ class ApplicationsController(
|
||||
}
|
||||
|
||||
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 }
|
||||
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 }
|
||||
@ -308,11 +353,11 @@ class ApplicationsController(
|
||||
var application = applicationService.findApplicationById(user, info.id)
|
||||
|
||||
if (application == null) {
|
||||
throw NotFound()
|
||||
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Application not found", null)
|
||||
}
|
||||
|
||||
if (application.unique_url != null) {
|
||||
throw BadRequest()
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Application already has unique_url", null)
|
||||
}
|
||||
|
||||
application.original_url = application.url
|
||||
@ -341,6 +386,7 @@ class ApplicationsController(
|
||||
applicationService.update(application)
|
||||
|
||||
application.flairs = flairService.listFromLinkApplicationId(application.id)
|
||||
application.views = viewService.listFromApplicationId(application.id)
|
||||
|
||||
return application
|
||||
}
|
||||
@ -485,6 +531,7 @@ class ApplicationService(
|
||||
return false
|
||||
}
|
||||
|
||||
// Create time is auto created by the database
|
||||
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
application.id,
|
||||
@ -527,7 +574,6 @@ class ApplicationService(
|
||||
.toList()
|
||||
}
|
||||
|
||||
|
||||
return db.query(
|
||||
"select * from applications where user_id=? and status=? order by title asc;",
|
||||
arrayOf(user.id, info.status),
|
||||
@ -537,6 +583,7 @@ class ApplicationService(
|
||||
}
|
||||
|
||||
public fun update(application: Application): Application {
|
||||
// I don't want ot update create_time
|
||||
db.update(
|
||||
"update applications set url=?, original_url=?, unique_url=?, title=?, user_id=?, extra_data=?, payrange=?, status=?, company=?, recruiter=?, message=?, linked_application=?, status_history=?, application_time=? where id=?",
|
||||
application.url,
|
||||
|
@ -25,7 +25,8 @@ create table if not exists applications (
|
||||
extra_data text,
|
||||
status integer,
|
||||
linked_application text default '',
|
||||
application_time text default ''
|
||||
application_time text default '',
|
||||
create_time timestamp default now()
|
||||
);
|
||||
|
||||
create table if not exists views (
|
||||
|
@ -40,3 +40,5 @@ ## Building
|
||||
## 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://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;
|
||||
linked_application: string;
|
||||
application_time: string;
|
||||
create_time: string;
|
||||
status_history: string;
|
||||
flairs: Flair[];
|
||||
views: View[];
|
||||
};
|
||||
|
@ -150,7 +150,7 @@
|
||||
<div class="p-3 bg-white w-[190mm] rounded-lg">
|
||||
<h1 class="flex gap-5 items-end">
|
||||
Your Ad & My skills {#if flairs.length > 0}<input
|
||||
placeholder="Loking for other skills search?"
|
||||
placeholder="Search other skills!"
|
||||
class="flex-grow text-blue-500 print:hidden"
|
||||
bind:value={otherSearch}
|
||||
/>
|
||||
|
@ -21,119 +21,125 @@
|
||||
| AsEnum<typeof ApplicationStatus>
|
||||
| 'Linkedin'
|
||||
| 'Glassdoor'
|
||||
| 'Unknown Source'
|
||||
| 'Applications';
|
||||
| 'Direct Source';
|
||||
|
||||
let nodeTypes: Record<NodeType, 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
|
||||
};
|
||||
let graph = {} as Record<string, Record<string, number>>;
|
||||
|
||||
const showPercentage: string[] = [
|
||||
`${ApplicationStatus.ToApply}`,
|
||||
`${ApplicationStatus.Ignore}`,
|
||||
`${ApplicationStatus.Expired}`,
|
||||
`${ApplicationStatus.Applyed}`,
|
||||
`${ApplicationStatus.LinkedApplication}`,
|
||||
`${ApplicationStatus.ApplyedButSaidNo}`,
|
||||
`${ApplicationStatus.TasksToDo}`
|
||||
];
|
||||
|
||||
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
|
||||
}
|
||||
];
|
||||
function addGraph(inSource: NodeType, inTarget: NodeType) {
|
||||
const source = `${inSource}`;
|
||||
const target = `${inTarget}`;
|
||||
if (graph[source] == undefined) {
|
||||
graph[source] = {} as Record<NodeType, number>;
|
||||
}
|
||||
graph[source][target] = (graph[source][target] ?? 0) + 1;
|
||||
return target as NodeType;
|
||||
}
|
||||
|
||||
applications.forEach((a) => {
|
||||
let source: NodeType;
|
||||
|
||||
if (a.url.includes('linkedin')) {
|
||||
nodeTypes['Linkedin'] += 1;
|
||||
source = 'Linkedin';
|
||||
} else if (a.url.includes('glassdoor')) {
|
||||
nodeTypes['Glassdoor'] += 1;
|
||||
source = 'Glassdoor';
|
||||
} else {
|
||||
nodeTypes['Unknown Source'] += 1;
|
||||
}
|
||||
if (a.status !== ApplicationStatus.WorkingOnIt) {
|
||||
nodeTypes[a.status] += 1;
|
||||
} else {
|
||||
nodeTypes[ApplicationStatus.ToApply] += 1;
|
||||
source = 'Direct Source';
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
) {
|
||||
nodeTypes[ApplicationStatus.Applyed] += 1;
|
||||
addGraph(source, ApplicationStatus.Applyed);
|
||||
addGraph(ApplicationStatus.Applyed, a.status);
|
||||
}
|
||||
});
|
||||
|
||||
let inNodes: string[] = [];
|
||||
|
||||
let nodes = (Object.keys(nodeTypes) as (keyof typeof nodeTypes)[])
|
||||
.filter((a) => nodeTypes[a] > 0)
|
||||
.map((a, 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>];
|
||||
let inNodes: string[] = Object.keys(graph).reduce((acc, elm) => {
|
||||
const arr = Object.keys(graph[elm]).concat(elm);
|
||||
for (const i of arr) {
|
||||
if (!acc.includes(i)) {
|
||||
acc.push(i);
|
||||
}
|
||||
return base;
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, [] as string[]);
|
||||
|
||||
const links = baseLinks
|
||||
.filter(
|
||||
(link) =>
|
||||
inNodes.includes(`${link.source}`) && inNodes.includes(`${link.target}`)
|
||||
)
|
||||
.map((link) => {
|
||||
const source = inNodes.indexOf(`${link.source}`);
|
||||
const target = inNodes.indexOf(`${link.target}`);
|
||||
return {
|
||||
source: source,
|
||||
target: target,
|
||||
value: [nodes[source], nodes[target]][link.value].value
|
||||
};
|
||||
});
|
||||
function getGraphValueFor(node: string): number {
|
||||
return Object.keys(graph).reduce((acc, i) => {
|
||||
if (i == node) return acc;
|
||||
if (graph[i][node] != undefined) {
|
||||
return acc + graph[i][node];
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
let nodes = inNodes.map((node, i) => {
|
||||
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();
|
||||
|
||||
@ -146,6 +152,8 @@
|
||||
|
||||
sankey.nodes(nodes).links(links).layout(32);
|
||||
|
||||
console.log("here2", nodes, links);
|
||||
|
||||
const svg = d3
|
||||
.select(chartDiv)
|
||||
.append('svg')
|
||||
@ -171,6 +179,9 @@
|
||||
.append('path')
|
||||
.attr('class', 'link')
|
||||
.attr('d', path)
|
||||
.style('stroke', function(d) {
|
||||
return d3.rgb(color[d.source.index]).toString();
|
||||
})
|
||||
.style('stroke-width', function (d) {
|
||||
return Math.max(1, d.dy);
|
||||
})
|
||||
@ -269,7 +280,6 @@
|
||||
|
||||
:global(.link) {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
stroke-opacity: 0.2;
|
||||
stroke-opacity: 0.5;
|
||||
}
|
||||
</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,
|
||||
ApplicationStatusMaping,
|
||||
applicationStore,
|
||||
type Application
|
||||
type Application,
|
||||
type AsEnum
|
||||
} from '$lib/ApplicationsStore.svelte';
|
||||
|
||||
import { put, preventDefault, post, get, deleteR } from '$lib/utils';
|
||||
@ -14,6 +15,8 @@
|
||||
import DropZone from './DropZone.svelte';
|
||||
import { userStore } from '$lib/UserStore.svelte';
|
||||
import LinkApplication from './LinkApplication.svelte';
|
||||
import SearchApplication from './SearchApplication.svelte';
|
||||
import NewApplication from './NewApplication.svelte';
|
||||
|
||||
let activeItem: Application | undefined = $state();
|
||||
|
||||
@ -24,6 +27,8 @@
|
||||
let lastExtData: any = $state(undefined);
|
||||
let autoExtData = false;
|
||||
|
||||
let showExtraData = $state(false);
|
||||
|
||||
async function activate(item?: Application) {
|
||||
if (!item) {
|
||||
return;
|
||||
@ -113,8 +118,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
console.log('setting up interest');
|
||||
|
||||
window.addEventListener('message', onMessage);
|
||||
window.postMessage({ type: 'REGISTER_INTEREST' });
|
||||
return () => {
|
||||
@ -156,7 +159,7 @@
|
||||
applicationStore.loadItem = undefined;
|
||||
});
|
||||
|
||||
async function moveStatus(status: number) {
|
||||
async function moveStatus(status: AsEnum<typeof ApplicationStatus>, moveOut = true) {
|
||||
if (!activeItem) return;
|
||||
// Deactivate active item
|
||||
try {
|
||||
@ -171,7 +174,11 @@
|
||||
}
|
||||
applicationStore.loadApplications(true);
|
||||
applicationStore.loadAplyed(true);
|
||||
activeItem = undefined;
|
||||
if (moveOut) {
|
||||
activeItem = undefined;
|
||||
} else {
|
||||
activeItem.status = status;
|
||||
}
|
||||
//openedWindow?.close();
|
||||
//openedWindow = undefined;
|
||||
}
|
||||
@ -240,6 +247,20 @@
|
||||
{statusMapping}
|
||||
</div>
|
||||
{/if}
|
||||
{#if showExtraData}
|
||||
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||
<div class="flabel">Id</div>
|
||||
<div class="finput bg-white w-full break-keep">
|
||||
{activeItem.id}
|
||||
</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">
|
||||
<fieldset class="grow">
|
||||
<label class="flabel" for="title">Company</label>
|
||||
@ -395,6 +416,13 @@
|
||||
>
|
||||
👋
|
||||
</button>
|
||||
<button
|
||||
class:btn-primary={!showExtraData}
|
||||
class:btn-confirm={showExtraData}
|
||||
onclick={() => (showExtraData = !showExtraData)}
|
||||
>
|
||||
🔬
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{#if applicationStore.dragging}
|
||||
@ -423,13 +451,15 @@
|
||||
>
|
||||
Ignore it
|
||||
</DropZone>
|
||||
{/if}
|
||||
|
||||
{#if [ApplicationStatus.WorkingOnIt, ApplicationStatus.Expired].includes(activeItem.status)}
|
||||
<!-- Expired -->
|
||||
<DropZone
|
||||
icon="clock-fill text-orange-500"
|
||||
ondrop={() => {
|
||||
if (activeItem && activeItem.status === ApplicationStatus.Expired) {
|
||||
moveStatus(ApplicationStatus.ToApply);
|
||||
moveStatus(ApplicationStatus.ToApply, false);
|
||||
} else {
|
||||
moveStatus(ApplicationStatus.Expired);
|
||||
}
|
||||
@ -437,7 +467,9 @@
|
||||
>
|
||||
Mark as expired
|
||||
</DropZone>
|
||||
{/if}
|
||||
|
||||
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
|
||||
<!-- Repeated -->
|
||||
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}
|
||||
>Delete it</DropZone
|
||||
@ -522,3 +554,11 @@
|
||||
onreload={(item) => (activeItem = item)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<SearchApplication application={activeItem} onreload={(item) => (activeItem = item)} />
|
||||
|
||||
<NewApplication
|
||||
onreload={(item) => {
|
||||
activate(item);
|
||||
}}
|
||||
/>
|
||||
|
@ -3,6 +3,9 @@ import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
server: {
|
||||
host: true,
|
||||
},
|
||||
build: {
|
||||
commonjsOptions: {
|
||||
esmExternals: true
|
||||
|
Loading…
Reference in New Issue
Block a user