chore: things done a very nice and usable git commit
This commit is contained in:
parent
55434c590a
commit
c83b2fd541
@ -71,7 +71,7 @@ data class Application(
|
|||||||
|
|
||||||
data class SubmitRequest(val text: String)
|
data class SubmitRequest(val text: String)
|
||||||
|
|
||||||
data class ListRequest(val status: Int?)
|
data class ListRequest(val status: Int? = null, val views: Boolean? = null)
|
||||||
|
|
||||||
data class StatusRequest(val id: String, val status: Int)
|
data class StatusRequest(val id: String, val status: Int)
|
||||||
|
|
||||||
@ -357,7 +357,11 @@ class ApplicationsController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (application.unique_url != null) {
|
if (application.unique_url != null) {
|
||||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Application already has unique_url", null)
|
throw ResponseStatusException(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Application already has unique_url",
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
application.original_url = application.url
|
application.original_url = application.url
|
||||||
@ -554,14 +558,13 @@ class ApplicationService(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun findAll(user: UserDb, info: ListRequest): List<Application> {
|
private fun internalFindAll(user: UserDb, info: ListRequest): Iterable<Application> {
|
||||||
if (info.status == null) {
|
if (info.status == null) {
|
||||||
return db.query(
|
return db.query(
|
||||||
"select * from applications where user_id=? order by title asc;",
|
"select * from applications where user_id=? order by title asc;",
|
||||||
arrayOf(user.id),
|
arrayOf(user.id),
|
||||||
Application
|
Application
|
||||||
)
|
)
|
||||||
.toList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's to apply also remove the linked_application to only show the main
|
// If it's to apply also remove the linked_application to only show the main
|
||||||
@ -571,7 +574,6 @@ class ApplicationService(
|
|||||||
arrayOf(user.id),
|
arrayOf(user.id),
|
||||||
Application
|
Application
|
||||||
)
|
)
|
||||||
.toList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.query(
|
return db.query(
|
||||||
@ -579,7 +581,17 @@ class ApplicationService(
|
|||||||
arrayOf(user.id, info.status),
|
arrayOf(user.id, info.status),
|
||||||
Application,
|
Application,
|
||||||
)
|
)
|
||||||
.toList()
|
}
|
||||||
|
|
||||||
|
public fun findAll(user: UserDb, info: ListRequest): List<Application> {
|
||||||
|
var iter = internalFindAll(user, info);
|
||||||
|
if (info.views == true) {
|
||||||
|
iter = iter.map {
|
||||||
|
it.views = viewService.listFromApplicationId(it.id)
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iter.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun update(application: Application): Application {
|
public fun update(application: Application): Application {
|
||||||
|
@ -1,24 +1,56 @@
|
|||||||
package com.andr3h3nriqu3s.applications
|
package com.andr3h3nriqu3s.applications
|
||||||
|
|
||||||
import java.sql.ResultSet
|
import java.sql.ResultSet
|
||||||
|
import java.sql.Timestamp
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
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
|
||||||
|
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: Date) {
|
data class View(var id: String, var application_id: String, var time: Timestamp) {
|
||||||
companion object : RowMapper<View> {
|
companion object : RowMapper<View> {
|
||||||
override public fun mapRow(rs: ResultSet, rowNum: Int): View {
|
override public fun mapRow(rs: ResultSet, rowNum: Int): View {
|
||||||
return View(
|
return View(
|
||||||
rs.getString("id"),
|
rs.getString("id"),
|
||||||
rs.getString("application_id"),
|
rs.getString("application_id"),
|
||||||
rs.getDate("time"),
|
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
|
@Service
|
||||||
public class ViewService(val db: JdbcTemplate) {
|
public class ViewService(val db: JdbcTemplate) {
|
||||||
|
|
||||||
@ -58,7 +90,7 @@ public class ViewService(val db: JdbcTemplate) {
|
|||||||
public fun create(application_id: String): View {
|
public fun create(application_id: String): View {
|
||||||
val id = UUID.randomUUID().toString()
|
val id = UUID.randomUUID().toString()
|
||||||
|
|
||||||
var new_view = View(id, application_id, Date())
|
var new_view = View(id, application_id, Timestamp(Date().getTime()))
|
||||||
|
|
||||||
db.update(
|
db.update(
|
||||||
"insert into views (id, application_id) values (?, ?)",
|
"insert into views (id, application_id) values (?, ?)",
|
||||||
|
@ -45,7 +45,7 @@ @layer components {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.finput {
|
.finput {
|
||||||
@apply rounded-lg w-full p-2 drop-shadow-lg border-gray-300 border;
|
@apply rounded-lg w-full p-2 drop-shadow-lg border-gray-300 border mb-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.finput[type='color'] {
|
.finput[type='color'] {
|
||||||
@ -75,15 +75,14 @@ @layer components {
|
|||||||
|
|
||||||
@print {
|
@print {
|
||||||
@page :footer {
|
@page :footer {
|
||||||
display: none
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@page :header {
|
@page :header {
|
||||||
display: none
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@page {
|
@page {
|
||||||
size: auto;
|
size: auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -11,7 +11,8 @@ export const ApplicationStatus = Object.freeze({
|
|||||||
Applyed: 4,
|
Applyed: 4,
|
||||||
Expired: 5,
|
Expired: 5,
|
||||||
TasksToDo: 6,
|
TasksToDo: 6,
|
||||||
LinkedApplication: 7
|
LinkedApplication: 7,
|
||||||
|
InterviewStep1: 8
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ApplicationStatusMaping: Record<
|
export const ApplicationStatusMaping: Record<
|
||||||
@ -26,13 +27,14 @@ export const ApplicationStatusMaping: Record<
|
|||||||
5: 'Expired',
|
5: 'Expired',
|
||||||
6: 'Tasks To Do',
|
6: 'Tasks To Do',
|
||||||
7: 'Linked Application',
|
7: 'Linked Application',
|
||||||
|
8: 'Interview 1'
|
||||||
});
|
});
|
||||||
|
|
||||||
export type View = {
|
export type View = {
|
||||||
id: string,
|
id: string;
|
||||||
application_id: string,
|
application_id: string;
|
||||||
time: string,
|
time: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Application = {
|
export type Application = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -58,7 +60,7 @@ export type Application = {
|
|||||||
function createApplicationStore() {
|
function createApplicationStore() {
|
||||||
let applications: Application[] = $state([]);
|
let applications: Application[] = $state([]);
|
||||||
let applyed: Application[] = $state([]);
|
let applyed: Application[] = $state([]);
|
||||||
let tasksToDo: Application[] = $state([]);
|
let all: Application[] = $state([]);
|
||||||
|
|
||||||
let dragApplication: Application | undefined = $state(undefined);
|
let dragApplication: Application | undefined = $state(undefined);
|
||||||
|
|
||||||
@ -82,24 +84,27 @@ function createApplicationStore() {
|
|||||||
if (!force && applyed.length > 1) {
|
if (!force && applyed.length > 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
applyed = await post('application/list', { status: ApplicationStatus.Applyed });
|
applyed = await post('application/list', { status: ApplicationStatus.Applyed, views: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws {Error}
|
* @throws {Error}
|
||||||
*/
|
*/
|
||||||
async loadTasksToDo(force = false) {
|
async loadAll(force = false) {
|
||||||
if (!force && tasksToDo.length > 1) {
|
if (!force && all.length > 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tasksToDo = await post('application/list', { status: ApplicationStatus.TasksToDo });
|
all = await post('application/list', {});
|
||||||
},
|
},
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
applications = [];
|
applications = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
dragStart(application: Application) {
|
dragStart(application: Application | undefined) {
|
||||||
|
if (!application) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
dragApplication = application;
|
dragApplication = application;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -119,8 +124,8 @@ function createApplicationStore() {
|
|||||||
return applyed;
|
return applyed;
|
||||||
},
|
},
|
||||||
|
|
||||||
get tasksToDo() {
|
get all() {
|
||||||
return tasksToDo;
|
return all;
|
||||||
},
|
},
|
||||||
|
|
||||||
get loadItem() {
|
get loadItem() {
|
||||||
@ -129,7 +134,7 @@ function createApplicationStore() {
|
|||||||
|
|
||||||
set loadItem(item: Application | undefined) {
|
set loadItem(item: Application | undefined) {
|
||||||
loadItem = item;
|
loadItem = item;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
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 AppliyedList from './AppliyedList.svelte';
|
||||||
import TasksToDoList from './TasksToDoList.svelte';
|
import PApplicationList from './PApplicationList.svelte';
|
||||||
|
import { ApplicationStatus } from '$lib/ApplicationsStore.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<HasUser redirect="/cv">
|
<HasUser redirect="/cv">
|
||||||
@ -15,7 +16,12 @@
|
|||||||
<ApplicationsList />
|
<ApplicationsList />
|
||||||
<WorkArea />
|
<WorkArea />
|
||||||
</div>
|
</div>
|
||||||
<TasksToDoList />
|
<PApplicationList status={ApplicationStatus.InterviewStep1}>
|
||||||
|
Interview I
|
||||||
|
</PApplicationList >
|
||||||
|
<PApplicationList status={ApplicationStatus.TasksToDo}>
|
||||||
|
Tasks To do
|
||||||
|
</PApplicationList >
|
||||||
<AppliyedList />
|
<AppliyedList />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
return x.match(f);
|
return x.match(f);
|
||||||
}) as item}
|
}) as item}
|
||||||
<div
|
<div
|
||||||
class="card p-2 my-2 bg-slate-100"
|
class="card p-2 my-2 bg-slate-100 max-w-full"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
ondragstart={() => applicationStore.dragStart(item)}
|
ondragstart={() => applicationStore.dragStart(item)}
|
||||||
ondragend={() => {
|
ondragend={() => {
|
||||||
@ -43,21 +43,26 @@
|
|||||||
}}
|
}}
|
||||||
role="none"
|
role="none"
|
||||||
>
|
>
|
||||||
<div class:animate-pulse={applicationStore.dragging?.id === item.id}>
|
<div class="max-w-full" class:animate-pulse={applicationStore.dragging?.id === item.id}>
|
||||||
<h2 class="text-lg text-blue-500">
|
<h2 class="text-lg text-blue-500 flex gap-2 max-w-full overflow-hidden">
|
||||||
|
<div class="flex-grow max-w-[90%]">
|
||||||
|
<div class="whitespace-nowrap overflow-hidden">
|
||||||
{item.title}
|
{item.title}
|
||||||
|
</div>
|
||||||
{#if item.company}
|
{#if item.company}
|
||||||
<div class="text-violet-800">
|
<div class="text-violet-800">
|
||||||
@ {item.company}
|
@ {item.company}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{#if item.url.includes('linkedin')}
|
||||||
|
<span class="bi bi-linkedin"></span>
|
||||||
|
{:else if item.url.includes('glassdoor')}
|
||||||
|
<span class="bi bi-eyeglasses"></span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
<a
|
|
||||||
href={item.url}
|
|
||||||
class="text-violet-600 overflow-hidden whitespace-nowrap block"
|
|
||||||
>
|
|
||||||
{item.url}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -5,14 +5,14 @@
|
|||||||
|
|
||||||
<div class="p-7">
|
<div class="p-7">
|
||||||
<div class="card p-2 rounded-xl flex">
|
<div class="card p-2 rounded-xl flex">
|
||||||
<button class="text-secudanry hover:text-primary px-2" onclick={() => goto('/')}> Home </button>
|
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/')}> Home </button>
|
||||||
<button class="text-secudanry hover:text-primary px-2" onclick={() => goto('/submit')}>
|
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/submit')}>
|
||||||
Submit text
|
Submit text
|
||||||
</button>
|
</button>
|
||||||
<button class="text-secudanry hover:text-primary px-2" onclick={() => goto('/flair')}>
|
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/flair')}>
|
||||||
Flair
|
Flair
|
||||||
</button>
|
</button>
|
||||||
<button class="text-secudanry hover:text-primary px-2" onclick={() => goto('/graphs')}>
|
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/graphs')}>
|
||||||
Graphs
|
Graphs
|
||||||
</button>
|
</button>
|
||||||
<div class="flex-grow"></div>
|
<div class="flex-grow"></div>
|
||||||
|
@ -1,20 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { applicationStore } from '$lib/ApplicationsStore.svelte';
|
import {
|
||||||
import { onMount } from 'svelte';
|
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(() => {
|
onMount(() => {
|
||||||
applicationStore.loadTasksToDo();
|
applicationStore.loadAll();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if applicationStore.tasksToDo.length > 0}
|
{#if applications.length > 0}
|
||||||
<div class="card p-3 rounded-lg flex flex-col">
|
<div class="card p-3 rounded-lg flex flex-col">
|
||||||
<h1>Tasks To Do</h1>
|
<h1>{@render children()}</h1>
|
||||||
<div class="overflow-auto flex-grow">
|
<div class="overflow-auto flex-grow">
|
||||||
{#each applicationStore.tasksToDo as item}
|
{#each applications as item}
|
||||||
<button
|
<button
|
||||||
class="card p-2 my-2 bg-slate-100 w-full text-left"
|
class="card p-2 my-2 bg-slate-100 w-full text-left"
|
||||||
onclick={() => {
|
onclick={async () => {
|
||||||
|
item.views = await get(`view/${item.id}`);
|
||||||
|
|
||||||
applicationStore.loadItem = item;
|
applicationStore.loadItem = item;
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
top: 0,
|
top: 0,
|
@ -154,7 +154,7 @@
|
|||||||
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}">Looking for other skills?</a></span>
|
<span class="hidden print:inline text-slate-600 text-sm"><a href="https://www.andr3h3nriqu3s.com/cv?id={id}">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">
|
||||||
|
@ -9,11 +9,16 @@
|
|||||||
|
|
||||||
let applications: Application[] = $state([]);
|
let applications: Application[] = $state([]);
|
||||||
|
|
||||||
let chartDiv: HTMLDivElement;
|
let chartDiv: HTMLDivElement | undefined = $state();
|
||||||
|
|
||||||
async function getData() {
|
let showExpired = $state(false);
|
||||||
try {
|
let showIgnore = $state(false);
|
||||||
applications = await post('application/list', {});
|
let showLinked = $state(false);
|
||||||
|
let showToApply = $state(false);
|
||||||
|
|
||||||
|
// Handle the graph creation
|
||||||
|
$effect(() => {
|
||||||
|
if (!chartDiv || applications.length == 0) return;
|
||||||
|
|
||||||
chartDiv.innerHTML = '';
|
chartDiv.innerHTML = '';
|
||||||
|
|
||||||
@ -35,15 +40,40 @@
|
|||||||
return target as NodeType;
|
return target as NodeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sourceData: Record<string, number> = {
|
||||||
|
'Direct Source': 0,
|
||||||
|
Glassdoor: 0,
|
||||||
|
Linkedin: 0
|
||||||
|
};
|
||||||
|
|
||||||
applications.forEach((a) => {
|
applications.forEach((a) => {
|
||||||
let source: NodeType;
|
let source: NodeType;
|
||||||
|
|
||||||
|
if (!showExpired && a.status == ApplicationStatus.Expired) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!showIgnore && a.status == ApplicationStatus.Ignore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!showLinked && a.status == ApplicationStatus.LinkedApplication) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!showToApply &&
|
||||||
|
(a.status == ApplicationStatus.ToApply || a.status == ApplicationStatus.WorkingOnIt)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (a.url.includes('linkedin')) {
|
if (a.url.includes('linkedin')) {
|
||||||
source = 'Linkedin';
|
source = 'Linkedin';
|
||||||
|
sourceData['Linkedin'] += 1;
|
||||||
} else if (a.url.includes('glassdoor')) {
|
} else if (a.url.includes('glassdoor')) {
|
||||||
source = 'Glassdoor';
|
source = 'Glassdoor';
|
||||||
|
sourceData['Glassdoor'] += 1;
|
||||||
} else {
|
} else {
|
||||||
source = 'Direct Source';
|
source = 'Direct Source';
|
||||||
|
sourceData['Direct Source'] += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -67,21 +97,22 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.status == ApplicationStatus.ApplyedButSaidNo) {
|
|
||||||
const history = a.status_history.split(',');
|
|
||||||
source = addGraph(source, ApplicationStatus.Applyed);
|
source = addGraph(source, ApplicationStatus.Applyed);
|
||||||
|
|
||||||
|
const history = a.status_history.split(',');
|
||||||
if (history.includes(`${ApplicationStatus.TasksToDo}`)) {
|
if (history.includes(`${ApplicationStatus.TasksToDo}`)) {
|
||||||
source = addGraph(source, ApplicationStatus.TasksToDo);
|
source = addGraph(source, ApplicationStatus.TasksToDo);
|
||||||
|
if (a.status == ApplicationStatus.TasksToDo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (history.includes(`${ApplicationStatus.InterviewStep1}`)) {
|
||||||
|
source = addGraph(source, ApplicationStatus.InterviewStep1);
|
||||||
|
if (a.status == ApplicationStatus.InterviewStep1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
addGraph(source, ApplicationStatus.ApplyedButSaidNo);
|
addGraph(source, ApplicationStatus.ApplyedButSaidNo);
|
||||||
} else if (
|
|
||||||
([ApplicationStatus.TasksToDo] as AsEnum<typeof ApplicationStatus>[]).includes(
|
|
||||||
a.status
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
addGraph(source, ApplicationStatus.Applyed);
|
|
||||||
addGraph(ApplicationStatus.Applyed, a.status);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let inNodes: string[] = Object.keys(graph).reduce((acc, elm) => {
|
let inNodes: string[] = Object.keys(graph).reduce((acc, elm) => {
|
||||||
@ -111,14 +142,13 @@
|
|||||||
} else {
|
} else {
|
||||||
name = ApplicationStatusMaping[Number(node) as AsEnum<typeof ApplicationStatus>];
|
name = ApplicationStatusMaping[Number(node) as AsEnum<typeof ApplicationStatus>];
|
||||||
}
|
}
|
||||||
const value = getGraphValueFor(node);
|
const value = sourceData[node] ?? getGraphValueFor(node);
|
||||||
const base = {
|
const base = {
|
||||||
value: value,
|
value: value,
|
||||||
originalValue: node,
|
originalValue: node,
|
||||||
id: name,
|
id: name,
|
||||||
index: i,
|
index: i,
|
||||||
percentage: Math.trunc((value / applications.length) * 100),
|
percentage: Math.trunc((value / applications.length) * 100)
|
||||||
end: true
|
|
||||||
};
|
};
|
||||||
return base;
|
return base;
|
||||||
});
|
});
|
||||||
@ -127,10 +157,11 @@
|
|||||||
source: number;
|
source: number;
|
||||||
target: number;
|
target: number;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
const links = Object.keys(graph).reduce((acc, source) => {
|
const links = Object.keys(graph).reduce((acc, source) => {
|
||||||
return acc.concat(Object.keys(graph[source]).map((target) => {
|
return acc.concat(
|
||||||
|
Object.keys(graph[source]).map((target) => {
|
||||||
const ns = inNodes.indexOf(`${source}`);
|
const ns = inNodes.indexOf(`${source}`);
|
||||||
const nt = inNodes.indexOf(`${target}`);
|
const nt = inNodes.indexOf(`${target}`);
|
||||||
return {
|
return {
|
||||||
@ -138,8 +169,9 @@
|
|||||||
target: nt,
|
target: nt,
|
||||||
value: graph[source][target]
|
value: graph[source][target]
|
||||||
};
|
};
|
||||||
}));
|
})
|
||||||
}, [] as Link[])
|
);
|
||||||
|
}, [] as Link[]);
|
||||||
|
|
||||||
const bounding = chartDiv.getBoundingClientRect();
|
const bounding = chartDiv.getBoundingClientRect();
|
||||||
|
|
||||||
@ -152,8 +184,6 @@
|
|||||||
|
|
||||||
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')
|
||||||
@ -162,7 +192,12 @@
|
|||||||
.attr('viewBox', [0, 0, bounding.width, bounding.height])
|
.attr('viewBox', [0, 0, bounding.width, bounding.height])
|
||||||
.attr('style', 'max-width: 100%; height: auto; height: intrinsic;');
|
.attr('style', 'max-width: 100%; height: auto; height: intrinsic;');
|
||||||
|
|
||||||
let color = d3.schemeSpectral[nodes.length];
|
// let color = d3.schemeSpectral[nodes.length];
|
||||||
|
// let color = d3.interpolateTurbo(nodes.length);
|
||||||
|
|
||||||
|
function getColor(index: number) {
|
||||||
|
return d3.interpolateRainbow(index/nodes.length);
|
||||||
|
}
|
||||||
|
|
||||||
// add in the links
|
// add in the links
|
||||||
var link = svg
|
var link = svg
|
||||||
@ -180,7 +215,10 @@
|
|||||||
.attr('class', 'link')
|
.attr('class', 'link')
|
||||||
.attr('d', path)
|
.attr('d', path)
|
||||||
.style('stroke', function (d) {
|
.style('stroke', function (d) {
|
||||||
return d3.rgb(color[d.source.index]).toString();
|
return d3.rgb(
|
||||||
|
getColor(d.source.index)
|
||||||
|
// 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);
|
||||||
@ -219,10 +257,14 @@
|
|||||||
})
|
})
|
||||||
.attr('width', sankey.getNodeWidth())
|
.attr('width', sankey.getNodeWidth())
|
||||||
.style('fill', function (d) {
|
.style('fill', function (d) {
|
||||||
return color[d.index];
|
return getColor(d.index);
|
||||||
|
//color[d.index];
|
||||||
})
|
})
|
||||||
.style('stroke', (d) => {
|
.style('stroke', (d) => {
|
||||||
return d3.rgb(color[d.index]).darker(2).toString();
|
return d3.rgb(
|
||||||
|
getColor(d.index)
|
||||||
|
//color[d.index]
|
||||||
|
).darker(2).toString();
|
||||||
})
|
})
|
||||||
.append('title')
|
.append('title')
|
||||||
.text(function (d) {
|
.text(function (d) {
|
||||||
@ -238,13 +280,18 @@
|
|||||||
.attr('text-anchor', 'end')
|
.attr('text-anchor', 'end')
|
||||||
.attr('transform', null)
|
.attr('transform', null)
|
||||||
.text(function (d) {
|
.text(function (d) {
|
||||||
return `${d.id} (${d.value} ${d.end ? `${d.percentage}%` : ''})`;
|
return `${d.id} (${d.value} ${d.percentage}%)`;
|
||||||
})
|
})
|
||||||
.filter(function (d) {
|
.filter(function (d) {
|
||||||
return d.x < bounding.width / 2;
|
return d.x < bounding.width / 2;
|
||||||
})
|
})
|
||||||
.attr('x', 6 + sankey.getNodeWidth())
|
.attr('x', 6 + sankey.getNodeWidth())
|
||||||
.attr('text-anchor', 'start');
|
.attr('text-anchor', 'start');
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getData() {
|
||||||
|
try {
|
||||||
|
applications = await post('application/list', {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('TODO, inform the user', e);
|
console.log('TODO, inform the user', e);
|
||||||
}
|
}
|
||||||
@ -258,7 +305,27 @@
|
|||||||
<HasUser redirect="/cv">
|
<HasUser redirect="/cv">
|
||||||
<div class="flex flex-col h-[100vh]">
|
<div class="flex flex-col h-[100vh]">
|
||||||
<NavBar />
|
<NavBar />
|
||||||
<div class="flex flex-grow p-5">
|
<div class="p-1 px-5">
|
||||||
|
<div class="bg-white p-3 rounded-lg gap-5 flex">
|
||||||
|
<fieldset>
|
||||||
|
<label for="showIgnore">Show Ignore</label>
|
||||||
|
<input id="showIgnore" type="checkbox" bind:checked={showIgnore} />
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="showExpired">Show Expired</label>
|
||||||
|
<input id="showExpired" type="checkbox" bind:checked={showExpired} />
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="showLinked">Show Linked Applicaitons</label>
|
||||||
|
<input id="showLinked" type="checkbox" bind:checked={showLinked} />
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="showToApply">Show To Apply</label>
|
||||||
|
<input id="showToApply" type="checkbox" bind:checked={showToApply} />
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-grow flex-col p-5">
|
||||||
<div class="bg-white p-3 rounded-lg" style="width: 100%; height: 100%">
|
<div class="bg-white p-3 rounded-lg" style="width: 100%; height: 100%">
|
||||||
<div bind:this={chartDiv} style="width: 100%; height: 100%;"></div>
|
<div bind:this={chartDiv} style="width: 100%; height: 100%;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import { ApplicationStatusMaping, type Application } from '$lib/ApplicationsStore.svelte';
|
||||||
ApplicationStatus,
|
|
||||||
ApplicationStatusMaping,
|
|
||||||
type Application
|
|
||||||
} from '$lib/ApplicationsStore.svelte';
|
|
||||||
import type { AsEnum } from '$lib/ApplicationsStore.svelte';
|
|
||||||
import { post } from '$lib/utils';
|
import { post } from '$lib/utils';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@ -24,12 +19,10 @@
|
|||||||
applications = await post('application/list', {});
|
applications = await post('application/list', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
getApplicationList();
|
|
||||||
});
|
|
||||||
|
|
||||||
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();
|
||||||
@ -53,6 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-y-auto overflow-x-hidden flex-grow p-2">
|
<div class="overflow-y-auto overflow-x-hidden flex-grow p-2">
|
||||||
|
<!-- TODO loading screen -->
|
||||||
{#each applications.filter((i) => {
|
{#each applications.filter((i) => {
|
||||||
if (application && i.id == application.id) {
|
if (application && i.id == application.id) {
|
||||||
return false;
|
return false;
|
||||||
@ -62,8 +56,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (filter.includes('@') && i.company) {
|
if (filter.includes('@') && i.company) {
|
||||||
const f = new RegExp(filter.split('@')[0].trim(), 'ig');
|
const splits = filter.split('@');
|
||||||
const c = new RegExp(filter.split('@')[1].trim(), 'ig');
|
const f = new RegExp(splits[0].trim(), 'ig');
|
||||||
|
const c = new RegExp(splits[1].trim(), 'ig');
|
||||||
return i.title.match(f) && i.company.match(c);
|
return i.title.match(f) && i.company.match(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,10 +73,14 @@
|
|||||||
return x.match(f);
|
return x.match(f);
|
||||||
}) as item}
|
}) 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={() => {
|
<button
|
||||||
dialogElement.close()
|
class="text-left max-w-full"
|
||||||
onreload(item)
|
type="button"
|
||||||
}}>
|
onclick={() => {
|
||||||
|
dialogElement.close();
|
||||||
|
onreload(item);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<h2 class="text-lg text-blue-500 flex justify-between">
|
<h2 class="text-lg text-blue-500 flex justify-between">
|
||||||
<div>
|
<div>
|
||||||
{item.title}
|
{item.title}
|
||||||
@ -95,9 +94,7 @@
|
|||||||
{ApplicationStatusMaping[item.status]}
|
{ApplicationStatusMaping[item.status]}
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
<span
|
<span class="text-violet-600 overflow-hidden whitespace-nowrap block max-w-full">
|
||||||
class="text-violet-600 overflow-hidden whitespace-nowrap block max-w-full"
|
|
||||||
>
|
|
||||||
{item.url}
|
{item.url}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -93,6 +93,21 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function docKey(e: KeyboardEvent) {
|
||||||
|
if (activeItem && e.ctrlKey && e.code === 'KeyO') {
|
||||||
|
openCV(activeItem.id);
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$effect(() => {
|
||||||
|
document.addEventListener('keydown', docKey, false);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', docKey);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
async function loadActive() {
|
async function loadActive() {
|
||||||
try {
|
try {
|
||||||
activeItem = await get('application/active');
|
activeItem = await get('application/active');
|
||||||
@ -264,48 +279,30 @@
|
|||||||
<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>
|
||||||
<input
|
<input class="finput" id="title" bind:value={activeItem.company} onchange={save} />
|
||||||
class="finput"
|
|
||||||
id="title"
|
|
||||||
bind:value={activeItem.company}
|
|
||||||
onchange={save}
|
|
||||||
/>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="grow">
|
<fieldset class="grow">
|
||||||
<label class="flabel" for="title">Recruiter</label>
|
<label class="flabel" for="title">Recruiter</label>
|
||||||
<input
|
<input class="finput" id="title" bind:value={activeItem.recruiter} onchange={save} />
|
||||||
class="finput"
|
|
||||||
id="title"
|
|
||||||
bind:value={activeItem.recruiter}
|
|
||||||
onchange={save}
|
|
||||||
/>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label class="flabel" for="title">Title</label>
|
<label class="flabel" for="title">Title</label>
|
||||||
<input
|
<input class="finput" id="title" bind:value={activeItem.title} onchange={save} />
|
||||||
class="finput"
|
|
||||||
id="title"
|
|
||||||
bind:value={activeItem.title}
|
|
||||||
onchange={save}
|
|
||||||
/>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label class="flabel" for="payrange">Pay Range</label>
|
<label class="flabel" for="payrange">Pay Range</label>
|
||||||
<input
|
<input class="finput" id="payrange" bind:value={activeItem.payrange} onchange={save} />
|
||||||
class="finput"
|
|
||||||
id="payrange"
|
|
||||||
bind:value={activeItem.payrange}
|
|
||||||
onchange={save}
|
|
||||||
/>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
{#if !activeItem.unique_url || showExtraData}
|
||||||
<fieldset draggable="false" class="max-w-full min-w-0 overflow-hidden">
|
<fieldset draggable="false" class="max-w-full min-w-0 overflow-hidden">
|
||||||
<div class="flabel">Url</div>
|
<div class="flabel">Url</div>
|
||||||
<div class="finput bg-white w-full break-keep">
|
<div class="finput bg-white w-full break-keep">
|
||||||
{activeItem.url}
|
{activeItem.url}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{#if activeItem.unique_url && activeItem.unique_url !== activeItem.url}
|
{/if}
|
||||||
|
{#if activeItem.unique_url}
|
||||||
<fieldset draggable="false">
|
<fieldset draggable="false">
|
||||||
<div class="flabel">Unique Url</div>
|
<div class="flabel">Unique Url</div>
|
||||||
<div class="finput bg-white">
|
<div class="finput bg-white">
|
||||||
@ -344,11 +341,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label class="flabel" for="extra">Extra Info</label>
|
<label class="flabel" for="extra">Extra Info</label>
|
||||||
<textarea
|
<textarea class="finput" id="extra" bind:value={activeItem.extra_data} onchange={save}
|
||||||
class="finput"
|
|
||||||
id="extra"
|
|
||||||
bind:value={activeItem.extra_data}
|
|
||||||
onchange={save}
|
|
||||||
></textarea>
|
></textarea>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@ -398,9 +391,7 @@
|
|||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if activeItem.original_url == null}
|
{#if activeItem.original_url == null}
|
||||||
<button class="btn-primary" onclick={() => changeUrl.showModal()}>
|
<button class="btn-primary" onclick={() => changeUrl.showModal()}> Update Url </button>
|
||||||
Update Url
|
|
||||||
</button>
|
|
||||||
{/if}
|
{/if}
|
||||||
<div class="px-10"></div>
|
<div class="px-10"></div>
|
||||||
<button class="btn-primary" onclick={() => linkApplication.showModal()}>
|
<button class="btn-primary" onclick={() => linkApplication.showModal()}>
|
||||||
@ -409,11 +400,7 @@
|
|||||||
{#if activeItem.original_url != null}
|
{#if activeItem.original_url != null}
|
||||||
<button class="btn-danger" onclick={resetUrl}> Reset Url </button>
|
<button class="btn-danger" onclick={resetUrl}> Reset Url </button>
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
<button class:btn-primary={drag} class:btn-danger={!drag} onclick={() => (drag = !drag)}>
|
||||||
class:btn-primary={drag}
|
|
||||||
class:btn-danger={!drag}
|
|
||||||
onclick={() => (drag = !drag)}
|
|
||||||
>
|
|
||||||
👋
|
👋
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@ -435,7 +422,7 @@
|
|||||||
ondrop={() => {
|
ondrop={() => {
|
||||||
moveStatus(ApplicationStatus.ToApply);
|
moveStatus(ApplicationStatus.ToApply);
|
||||||
applicationStore.loadAplyed(true);
|
applicationStore.loadAplyed(true);
|
||||||
applicationStore.loadTasksToDo(true);
|
applicationStore.loadAll(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
To apply
|
To apply
|
||||||
@ -471,9 +458,7 @@
|
|||||||
|
|
||||||
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
|
{#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
|
|
||||||
>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if [ApplicationStatus.WorkingOnIt, ApplicationStatus.TasksToDo].includes(activeItem.status)}
|
{#if [ApplicationStatus.WorkingOnIt, ApplicationStatus.TasksToDo].includes(activeItem.status)}
|
||||||
@ -482,8 +467,8 @@
|
|||||||
icon="server text-confirm"
|
icon="server text-confirm"
|
||||||
ondrop={async () => {
|
ondrop={async () => {
|
||||||
await moveStatus(ApplicationStatus.Applyed);
|
await moveStatus(ApplicationStatus.Applyed);
|
||||||
|
applicationStore.loadAll(true);
|
||||||
applicationStore.loadAplyed(true);
|
applicationStore.loadAplyed(true);
|
||||||
applicationStore.loadTasksToDo(true);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Apply
|
Apply
|
||||||
@ -496,12 +481,24 @@
|
|||||||
icon="server text-confirm"
|
icon="server text-confirm"
|
||||||
ondrop={async () => {
|
ondrop={async () => {
|
||||||
await moveStatus(ApplicationStatus.TasksToDo);
|
await moveStatus(ApplicationStatus.TasksToDo);
|
||||||
applicationStore.loadTasksToDo(true);
|
applicationStore.loadAll(true);
|
||||||
applicationStore.loadAplyed(true);
|
applicationStore.loadAplyed(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Tasks To Do
|
Tasks To Do
|
||||||
</DropZone>
|
</DropZone>
|
||||||
|
|
||||||
|
<!-- 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}
|
||||||
|
|
||||||
<!-- Rejected -->
|
<!-- Rejected -->
|
||||||
@ -557,8 +554,4 @@
|
|||||||
|
|
||||||
<SearchApplication application={activeItem} onreload={(item) => (activeItem = item)} />
|
<SearchApplication application={activeItem} onreload={(item) => (activeItem = item)} />
|
||||||
|
|
||||||
<NewApplication
|
<NewApplication onreload={activate} />
|
||||||
onreload={(item) => {
|
|
||||||
activate(item);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
Loading…
Reference in New Issue
Block a user