chore: things done a very nice and usable git commit

This commit is contained in:
Andre Henriques 2024-10-15 12:58:02 +01:00
parent 55434c590a
commit c83b2fd541
12 changed files with 518 additions and 390 deletions

View File

@ -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 {

View File

@ -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 (?, ?)",

View File

@ -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;

View File

@ -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;
}, }
}; };
} }

View File

@ -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>

View File

@ -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}

View File

@ -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>

View File

@ -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,

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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);
}}
/>