feat: a lot

This commit is contained in:
2025-01-21 11:16:32 +00:00
parent 26301d1a13
commit 941875ff21
34 changed files with 956 additions and 1205 deletions

View File

@@ -1,138 +1,67 @@
import type { Flair } from './FlairStore.svelte';
import { post } from './utils';
import { get } from './utils';
export type AsEnum<T> = T[keyof T];
export const ApplicationStatus = Object.freeze({
ToApply: 0,
WorkingOnIt: 1,
Ignore: 2,
ApplyedButSaidNo: 3,
Applyed: 4,
Expired: 5,
TasksToDo: 6,
TasksToDo2: 10,
LinkedApplication: 7,
InterviewStep1: 8,
InterviewStep2: 9,
FinalInterview: 11
});
export const ApplicationStatusIconMaping: Record<AsEnum<typeof ApplicationStatus>, string> = Object.freeze({
0: 'clock',
1: 'search',
2: 'trash3',
3: 'fire',
4: 'send',
5: 'hourglass-bottom',
6: 'list-check',
10: 'list-check',
7: 'link-45deg',
8: 'person',
9: 'people',
11: 'badge-vo-fill'
});
export const ApplicationStatusMaping: Record<AsEnum<typeof ApplicationStatus>, string> = Object.freeze({
0: 'To Apply',
1: 'Working On It',
2: 'Ignore',
3: 'Applyed But Said No',
4: 'Applyed',
5: 'Expired',
6: 'Tasks To Do',
10: 'Tasks To Do 2',
7: 'Linked Application',
8: 'Interview 1',
9: 'Interview 2',
11: 'Final Interview'
});
export type View = {
id: string;
application_id: string;
time: string;
};
export const EventType = Object.freeze({
Creation: 0,
StatusUpdate: 1,
View: 2,
View: 2
});
export type ApplicationEvent = {
id: string,
application_id: string,
event_type: AsEnum<typeof EventType>,
new_status: number,
time: string
}
id: string;
application_id: string;
event_type: AsEnum<typeof EventType>;
new_status_id: string;
time: string;
};
export type Application = {
id: string;
url: string;
original_url: string | null;
unique_url: string | null;
simple_url: string;
title: string;
user_id: string;
extra_data: string;
payrange: string;
status: AsEnum<typeof ApplicationStatus>;
status_id: string;
recruiter: string;
agency: boolean;
company: string;
message: string;
linked_application: string;
application_time: string;
create_time: string;
status_history: string;
flairs: Flair[];
views: View[];
events: ApplicationEvent[];
};
function createApplicationStore() {
let applications: Application[] = $state([]);
let applyed: Application[] = $state([]);
let all: Application[] = $state([]);
let dragApplication: Application | undefined = $state(undefined);
let loadItem: Application | undefined = $state(undefined);
let loadItemOpen = $state(true);
let req = false;
return {
/**
* @throws {Error}
*/
async loadApplications(force = false) {
if (!force && applications.length > 1) {
return;
}
applications = await post('application/list', { status: 0 });
},
/**
* @throws {Error}
*/
async loadAplyed(force = false) {
if (!force && applyed.length > 1) {
return;
}
applyed = await post('application/list', { status: ApplicationStatus.Applyed, views: true });
},
/**
* @throws {Error}
*/
async loadAll(force = false) {
if (req) return;
if (!force && all.length > 1) {
return;
}
all = await post('application/list', {});
},
clear() {
applications = [];
req = true;
try {
all = await get('application/list');
} finally {
req = false;
}
},
dragStart(application: Application | undefined) {
@@ -146,28 +75,53 @@ function createApplicationStore() {
dragApplication = undefined;
},
removeByID(uuid: string) {
all = all.filter((i) => i.id !== uuid);
},
getIndexById(uuid: string): number | undefined {
return all
.map((a, i) => [a, i] as [Application, number])
.filter((i) => i[0].id === uuid)[0]?.[1];
},
get dragging() {
return dragApplication;
},
get applications() {
return applications;
},
get applyed() {
return applyed;
},
get all() {
return all;
},
reset() {
// This just tells svelte that we changed all
all = all;
},
set(item: Application, index?: number) {
if (index === undefined) {
index = this.getIndexById(item.id);
}
if (index === undefined) return;
all[index] = item;
this.reset();
},
get loadItem() {
return loadItem;
},
set loadItem(item: Application | undefined) {
loadItem = item;
loadItemOpen = true;
},
get loadItemOpen() {
return loadItemOpen;
},
set loadItemOpen(_loadItemOpen: boolean) {
loadItemOpen = _loadItemOpen;
}
};
}

View File

@@ -1,29 +1,31 @@
import { get } from './utils';
export type Flair = {
id: string;
user_id: string;
color: string;
name: string;
expr: string;
description: string;
id: string;
user_id: string;
color: string;
name: string;
expr: string;
description: string;
showFullDescription: number;
sort: number;
};
function createFlairStore() {
let flairList: Flair[] = $state([]);
let flairList: Flair[] = $state([]);
return {
get flairs() {
return flairList;
},
return {
get flairs() {
return flairList;
},
async loadItems(force?: boolean) {
if (flairList.length > 0 && !force) {
return;
}
flairList = await get('flair/');
}
};
async loadItems(force?: boolean) {
if (flairList.length > 0 && !force) {
return;
}
flairList = await get('flair/');
}
};
}
export const flairStore = createFlairStore();

View File

@@ -0,0 +1,80 @@
import { get } from './utils';
import type { Node, FullLinkApi } from '../routes/flow/types';
function createStatusStore() {
let nodes: Node[] = $state([]);
let links: FullLinkApi[] = $state([]);
let nodesR: Record<string, Node> = $state({});
let dirLinks: Record<string, Node[]> = $state({});
let request = false;
return {
/**
* @throws {Error}
**/
async load(clear = false) {
if (!clear && nodes.length !== 0) {
return;
}
if (request) return;
request = true;
try {
nodes = await get('user/status/node');
links = await get('user/status/link');
} catch (e) {
request = false;
throw e;
}
dirLinks = {};
nodesR = {};
nodes.sort((a, b) => {
if (a.y !== b.y) return a.y - b.y;
return b.x - a.x;
});
for (const node of nodes) {
nodesR[node.id] = node;
}
const nodesId: string[] = [null as any, ...Object.keys(nodesR)];
nodesR[null as any] = {
icon: 'plus',
id: null as any,
name: 'Created'
} as any;
for (const nodeId of nodesId) {
const targets = [];
for (const link of links) {
if (link.source_node === nodeId) {
targets.push(nodesR[link.target_node as any]);
}
}
dirLinks[nodeId] = targets;
}
request = false;
},
get nodes() {
return nodes;
},
get nodesR() {
return nodesR;
},
get dirLinks() {
return dirLinks;
}
};
}
export const statusStore = createStatusStore();

View File

@@ -3,9 +3,7 @@
import HasUser from '$lib/HasUser.svelte';
import ApplicationsList from './ApplicationsList.svelte';
import WorkArea from './work-area/WorkArea.svelte';
import AppliyedList from './AppliyedList.svelte';
import PApplicationList from './PApplicationList.svelte';
import { ApplicationStatus } from '$lib/ApplicationsStore.svelte';
import ApplicationTypesList from './ApplicationTypesList.svelte';
</script>
<HasUser redirect="/cv">
@@ -13,25 +11,11 @@
<NavBar />
<div class="w-full px-4 grow h-full gap-3 flex flex-col">
<div class="flex gap-3 flex-grow max-h-[75%]">
<ApplicationsList />
<ApplicationsList />
<WorkArea />
</div>
<PApplicationList status={ApplicationStatus.FinalInterview}>
Interview Final
</PApplicationList >
<PApplicationList status={ApplicationStatus.InterviewStep2}>
Interview II
</PApplicationList >
<PApplicationList status={ApplicationStatus.InterviewStep1}>
Interview I
</PApplicationList >
<PApplicationList status={ApplicationStatus.TasksToDo2}>
Tasks To do 2
</PApplicationList >
<PApplicationList status={ApplicationStatus.TasksToDo}>
Tasks To do
</PApplicationList >
<AppliyedList />
<ApplicationTypesList />
<div class="p-3"></div>
</div>
</div>
</HasUser>

View File

@@ -0,0 +1,17 @@
<script lang="ts">
import { statusStore } from '$lib/Types.svelte';
import { onMount } from 'svelte';
import SmartApplicationList from './SmartApplicationList.svelte';
onMount(async () => {
await statusStore.load();
});
</script>
{#if statusStore.nodes.length > 0}
{#each statusStore.nodes as node}
{#if node.visible}
<SmartApplicationList title={node.name} status_id={node.id} />
{/if}
{/each}
{/if}

View File

@@ -5,11 +5,15 @@
let filter = $state('');
onMount(() => {
applicationStore.loadApplications();
applicationStore.loadAll();
});
let apps = $derived(
applicationStore.all.filter((i) => i.status_id === null && !i.linked_application)
);
let internal = $derived(
applicationStore.applications.filter((i) => {
apps.filter((i) => {
if (!filter) {
return true;
}
@@ -47,10 +51,7 @@
}}
role="none"
>
<div
class="max-w-full"
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 flex gap-2 max-w-full overflow-hidden">
<div class="flex-grow max-w-[90%]">
<div class="whitespace-nowrap overflow-hidden">

View File

@@ -1,59 +0,0 @@
<script lang="ts">
import { applicationStore } from '$lib/ApplicationsStore.svelte';
import { get } from '$lib/utils';
import { onMount } from 'svelte';
onMount(() => {
applicationStore.loadAplyed();
});
let filter = $state('');
</script>
<div class="card p-3 rounded-lg">
<h1 class="flex gap-2">
Applied <input bind:value={filter} placeholder="search" class="flex-grow text-blue-500" />
</h1>
<div class="flex flex-wrap gap-4 justify-between">
{#each applicationStore.applyed.filter((i) => {
if (!filter) {
return true;
}
const f = new RegExp(filter, 'ig');
let x = i.title;
if (i.company) {
x = `${x} @ ${i.company}`;
}
return x.match(f);
}) as item}
<button
class="card p-2 my-2 bg-slate-100 text-left"
onclick={async () => {
item.views = await get(`view/${item.id}`);
item.events = await get(`events/${item.id}`);
applicationStore.loadItem = item;
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}}
>
<div>
<h2 class="text-lg text-blue-500">
{item.title}
{#if item.company}
<div class="text-violet-800">
@ {item.company}
</div>
{/if}
</h2>
</div>
</button>
{/each}
</div>
</div>
<div class="min-h-[40px]">
</div>

View File

@@ -5,9 +5,8 @@
<div class="p-7 pb-1">
<div class="card p-2 rounded-xl flex">
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/')}> Home </button>
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/submit')}>
Submit text
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/')}>
Home
</button>
<button class="text-secudanry hover:text-violet-500 px-2" onclick={() => goto('/flair')}>
Flair

View File

@@ -1,52 +0,0 @@
<script lang="ts">
import {
ApplicationStatus,
applicationStore,
type AsEnum
} from '$lib/ApplicationsStore.svelte';
import { get } from '$lib/utils';
import { onMount, type Snippet } from 'svelte';
let { children, status }: { children: Snippet; status: AsEnum<typeof ApplicationStatus> } =
$props();
let applications = $derived(applicationStore.all.filter((item) => item.status == status))
onMount(() => {
applicationStore.loadAll();
});
</script>
{#if applications.length > 0}
<div class="card p-3 rounded-lg flex flex-col">
<h1>{@render children()}</h1>
<div class="overflow-auto flex-grow">
{#each applications as item}
<button
class="card p-2 my-2 bg-slate-100 w-full text-left"
onclick={async () => {
item.views = await get(`view/${item.id}`);
item.events = await get(`events/${item.id}`);
applicationStore.loadItem = item;
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}}
>
<div>
<h2 class="text-lg text-blue-500">
{item.title}
{#if item.company}
<div class="text-violet-800">
@ {item.company}
</div>
{/if}
</h2>
</div>
</button>
{/each}
</div>
</div>
{/if}

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import { applicationStore } from '$lib/ApplicationsStore.svelte';
import { onMount } from 'svelte';
const { title, status_id }: { title: string; status_id: string } = $props();
onMount(() => {
applicationStore.loadAll();
});
let filter = $state('');
let sorted = $derived(
applicationStore.all.filter((i) => {
return i.status_id === status_id;
})
);
</script>
{#if sorted.length > 0}
<div class="card p-3 rounded-lg">
<h1 class="flex gap-2">
{title} <input bind:value={filter} placeholder="search" class="flex-grow text-blue-500" />
</h1>
<div class="flex flex-wrap gap-4">
{#each sorted.filter((i) => {
if (!filter) {
return true;
}
const f = new RegExp(filter, 'ig');
let x = i.title;
if (i.company) {
x = `${x} @ ${i.company}`;
}
return x.match(f);
}) as item}
<button
class="card p-2 my-2 bg-slate-100 text-left"
onclick={async () => {
applicationStore.loadItem = item;
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}}
>
<div>
<h2 class="text-lg text-blue-500">
{item.title}
{#if item.company}
<div class="text-violet-800">
@ {item.company}
</div>
{/if}
</h2>
</div>
</button>
{/each}
</div>
</div>
{/if}

View File

@@ -2,7 +2,7 @@
import { userStore } from '$lib/UserStore.svelte';
import { get } from '$lib/utils';
import { onMount } from 'svelte';
import ApplicationsList from '../ApplicationsList.svelte';
import Pill from './pill.svelte';
let id: string | undefined | null = $state(undefined);
@@ -18,13 +18,15 @@
name: string;
description: string;
color: string;
sort: number;
showFullDescription: number;
};
type Application = {
recruiter: string;
message: string;
company: string;
agency: boolean,
agency: boolean;
flairs: SimpleFlair[];
};
@@ -40,16 +42,19 @@
}
application.flairs.sort((a, b) => {
if (a.description && b.description) {
return 0;
const aDesc = a.description && a.showFullDescription === 1;
const bDesc = b.description && b.showFullDescription === 1;
if (aDesc && bDesc) {
return b.sort - a.sort;
}
if (a.description) {
if (aDesc) {
return -1;
}
if (b.description) {
if (bDesc) {
return 1;
}
return 0;
return b.sort - a.sort;
});
loadFlairs();
@@ -82,7 +87,7 @@
</svelte:head>
<div class="flex items-center w-full flex-col">
<div class="py-10 w-[190mm]">
<div class="py-5 pb-0 w-[190mm] print:py-0 print:pt-4">
<div class="bg-white rounded-lg p-3">
<div class="w-full flex">
<h1 class="text-black text-5xl">Andre Henriques</h1>
@@ -113,7 +118,7 @@
</ul>
</div>
</div>
<div class="w-full flex py-9">
<div class="w-full flex py-3 print:pb-0">
<div>
I am a dedicated and versatile programmer with four years of professional
experience. <br />
@@ -128,18 +133,21 @@
</div>
</div>
</div>
{#if application}
{#if !application.agency}
<h2 class="text-white p-3 text-4xl">
👋 Hello
{#if application.recruiter}
<span class="font-bold">{application.recruiter}</span> @
<span class="font-bold">{application.company}</span>
{:else if application.company}
recruiter @ <span class="font-bold">{application.company}</span>
{/if}
</h2>
{/if}
{#if !application.agency}
<h2 class="text-white p-6 print:p-0 text-4xl print:text-3xl">
👋 Hello
{#if application.recruiter}
<span class="font-bold">{application.recruiter}</span> @
<span class="font-bold">{application.company}</span>
{:else if application.company}
recruiter @ <span class="font-bold">{application.company}</span>
{/if}
</h2>
{:else}
<div class="p-5 print:hidden"></div>
{/if}
{#if application.message}
<div class="p-3 bg-white w-[190mm] rounded-lg">
@@ -153,41 +161,44 @@
{#if application.flairs.length > 0}
<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="Search other skills!"
Skills {#if flairs.length > 0}<input
placeholder="Click here to search skills!"
class="flex-grow text-blue-500 print:hidden"
bind:value={otherSearch}
/>
<span class="hidden print:inline text-slate-600 text-sm"><a href="https://www.andr3h3nriqu3s.com/cv?id={id}">Search other skills!</a></span>
<span class="hidden print:inline text-slate-600 text-sm"
><a href="https://www.andr3h3nriqu3s.com/cv?id={id}"
>Click here to search other skills!</a
></span
>
{/if}
</h1>
<div class="flex flex-wrap gap-2 py-2">
<div class="flex flex-wrap gap-2 py-2 print:py-0">
{#if otherSearch === ''}
{#each application.flairs as flair}
<div class="min-w-0 {flair.description ? 'flex-grow w-full' : ''}">
{#if flair.description}
{@const hasDesc = flair.description && flair.showFullDescription === 1}
<div class="min-w-0 {hasDesc ? 'flex-grow w-full' : ''}">
{#if hasDesc}
<div
class="p-2 rounded-lg forced-color-adjust-none"
class="p-2 rounded-lg forced-color-adjust-none relative print:py-0"
style="background: {flair.color};"
>
<div
class="rounded-lg print:p-2 print:inline-block"
style="background: {flair.color}; print-color-adjust: exact !important;"
<Pill
text-size="text-base print:text-sm"
class="print:inline-block text-2xl py-0 mb-2"
color={flair.color}
>
{flair.name}
</div>
</Pill>
<span class="hidden print:inline">:</span>
<div class="bg-white my-1 print:inline p-1 rounded-md">
{flair.description}
</div>
</div>
{:else}
<div
class="p-2 rounded-lg forced-color-adjust-none"
style="background: {flair.color}; print-color-adjust: exact !important;"
>
<Pill text-size="text-base print:text-sm" color={flair.color}>
{flair.name}
</div>
</Pill>
{/if}
</div>
{/each}
@@ -196,10 +207,10 @@
a.name.match(new RegExp(otherSearch, 'i'))
)}
{#if filtered_list.length == 0}
<div class="w-full text-center text-blue-500 font-bold text-2xl py-10">
Could not find the skill you are looking for.
</div>
{:else}
<div class="w-full text-center text-blue-500 font-bold text-2xl py-10">
Could not find the skill you are looking for.
</div>
{:else}
{#each filtered_list as flair}
<div class="min-w-0 {flair.description ? 'flex-grow w-full' : ''}">
{#if flair.description}
@@ -234,20 +245,18 @@
</div>
{/if}
{/if}
<div class="p-2"></div>
<div class="w-[190mm]">
<h2 class="pb-2 px-2 print:px-5 text-4xl print:text-3xl font-bold text-white">
<h2 class="p-3 print:p-0 text-4xl print:text-2xl font-bold print:font-normal text-white">
Work Expericence
</h2>
</div>
<div class="p-2"></div>
<div class="w-[100vw] flex items-center flex-col">
<div class="p-3 bg-white w-[190mm] rounded-lg">
<div class="p-3 print:p-0 bg-white w-[190mm] rounded-lg">
<h1>Senior Software Developer @ Planum Solucoes</h1>
<h2>4 years - May 2020 - Present</h2>
<div class="ml-5">
<h2>4 years - May 2020 - Present</h2>
<h3>Developed various projects:</h3>
<ul class="pl-5 list-disc">
<li>Developing various websites using React and Svelte.</li>
@@ -255,30 +264,20 @@
<li>Implemented an ORM system using Java Reflection</li>
<li>Implemented automatic deployment with GitLab CI/CD tools.</li>
<li>Linux Server Administration</li>
<li>Technologies used: React, WebRTC, WebSockets, Rest, Google Maps AP</li>
</ul>
</div>
</div>
</div>
<div class="p-2"></div>
<div class="p-2 print:p-1"></div>
<div class="w-[100vw] flex items-center flex-col">
<div class="text-black w-[190mm] bg-white p-4 rounded-lg">
<div class="text-black w-[190mm] bg-white p-4 print:p-0 rounded-lg">
<div>
<div>
<h1>Associate Devops Engineer @ Sky UK</h1>
<div class="ml-5">
<h2>1 year - July 2022 - June 2023</h2>
<h3>Working with:</h3>
<ul class="pl-5 list-disc">
<li>Python</li>
<li>Jenkins</li>
<li>GitLab CI</li>
<li>Ansible</li>
<li>Docker</li>
</ul>
<h3>Associated Software Developer / DevOps Engineer:</h3>
<h2>1 year - July 2022 - June 2023</h2>
<div class="ml-2">
<ul class="pl-5 list-disc">
<li>Developed web-based tools for the DevOps team to use</li>
<li>
@@ -298,13 +297,15 @@
</div>
</div>
<div class="p-5"></div>
<div class="bg-white p-3 text-black rounded-lg w-[190mm]">
<h2 class="pb-2 text-3xl">Education</h2>
<div class="w-[190mm]">
<h2 class="p-3 print:p-0 text-4xl print:text-xl font-bold print:font-normal text-white">
Education
</h2>
</div>
<div class="bg-white p-2 text-black rounded-lg w-[190mm] print:text-sm">
<div>
<div>
<h1>Bachelors of science in Computer Science @ University of Surrey</h1>
<h1>BCompSc with First Class Honours @ University of Surrey</h1>
<div class="ml-5">
<h2>July 2020 - June 2024</h2>
</div>
@@ -312,6 +313,8 @@
</div>
</div>
<div class="p-3 print:hidden"></div>
<!--div class="p-5"></div>
<div>TODO: Previous projetcs</div -->
<!-- div class="p-5"></div>

View File

@@ -0,0 +1,17 @@
<script lang="ts">
import type { Snippet } from 'svelte';
const {
children,
color = 'rgb(134 239 172)',
class: className = '',
'text-size': textSize = 'text-xs'
}: { children: Snippet; color?: string; class?: string; 'text-size'?: string } = $props();
</script>
<span
class="px-2 py-1 rounded-lg forced-color-adjust-none bg-green-300 {className} {textSize}"
style="print-color-adjust: exact !important; background-color: {color};"
>
{@render children()}
</span>

View File

@@ -95,6 +95,18 @@
<label class="flabel" for="color">Color</label>
<input required type="color" id="color" bind:value={edit.color} />
</fieldset>
<fieldset>
<label class="flabel" for="color">Show description</label>
<input
type="checkbox"
id="showDescription"
checked={edit.showFullDescription === 1}
onchange={() => {
if (!edit) return;
edit.showFullDescription = (edit.showFullDescription + 1) % 2;
}}
/>
</fieldset>
<fieldset>
<label class="flabel" for="name">Name</label>
<input required id="name" class="finput w-full" bind:value={edit.name} />
@@ -106,12 +118,15 @@
<fieldset>
<label class="flabel" for="description">Description</label>
<textarea
required
id="description"
class="finput w-full"
bind:value={edit.description}
></textarea>
</fieldset>
<fieldset>
<label class="flabel" for="sort">Sort</label>
<input type="number" class="finput w-full" bind:value={edit.sort} />
</fieldset>
<div class="btns w-full">
<button class="btn-confirm">Save</button>
</div>

View File

@@ -8,11 +8,6 @@
import { extractLinkNodePosX, extractLinkNodePosY } from './types';
import { deleteR, get, post, put } from '$lib/utils';
import icon_list from './icons-list.json';
import IconPicker from './IconPicker.svelte';
// TODOS: well a lot of stuff but
// - API
// - Automaticaly move to select mode if one of the targets fals inside the bounds
//
// constatns
@@ -47,7 +42,8 @@
height: 4,
name: 'Created',
icon: 'plus',
permission: 1
permission: 1,
visible: false
} as Node,
...nodesRequest
];
@@ -72,7 +68,7 @@
y: a.target_y
}
}));
console.log("test", linkRequests)
console.log('test', linkRequests);
} catch (e) {
console.log('TODO inform user', e);
}
@@ -191,7 +187,8 @@
width,
icon: icon_list[Math.floor(Math.random() * icon_list.length)],
name: 'New Status',
permission: 0
permission: 0,
visible: true
} as Node);
nodes.push(result);
// Tell svelte that nodes is updated
@@ -419,16 +416,19 @@
{linkMode}
linkSource={nodes[i] === linkSource?.node ? linkSource : undefined}
onremove={async () => {
try {
await deleteR(`user/status/node/${nodes[i].id}`);
try {
await deleteR(`user/status/node/${nodes[i].id}`);
links = links.filter(link => link.sourceNode.node.id !== nodes[i].id && link.targetNode.node.id !== nodes[i].id)
links = links.filter(
(link) =>
link.sourceNode.node.id !== nodes[i].id && link.targetNode.node.id !== nodes[i].id
);
nodes.splice(i, 1);
nodes = nodes;
} catch (e) {
console.log("TODO inform the user", e)
}
nodes.splice(i, 1);
nodes = nodes;
} catch (e) {
console.log('TODO inform the user', e);
}
}}
onNodePointClick={async (inNodeX, inNodeY) => {
if (mouseAction === undefined) {
@@ -471,7 +471,7 @@
// Tell svelte that the links changed
links = links;
linkSource = undefined;
mouseAction = undefined;
mouseAction = undefined;
} catch (e) {
console.log('inform the user', e);
}

View File

@@ -94,6 +94,43 @@
>
<div class="bi bi-x mt-[2px]"></div>
</button>
<!-- Controll items -->
<div class="absolute flex gap-1 top-1 left-1">
<button
class="text-purple-400 hover:text-purple-800 rounded-full h-[20px] w-[20px] text-center leading-none"
onclick={async () => {
node.visible = !node.visible;
try {
await put('user/status/node', node);
} catch (e) {
console.log('TODO inform the user', e);
}
}}
>
{#if node.visible}
<div class="bi bi-eye-fill mt-[2px]"></div>
{:else}
<div class="bi bi-eye-slash-fill mt-[2px]"></div>
{/if}
</button>
<button
class="text-purple-400 hover:text-purple-800 rounded-full h-[20px] w-[20px] text-center leading-none"
onclick={async () => {
node.endable = !node.endable;
try {
await put('user/status/node', node);
} catch (e) {
console.log('TODO inform the user', e);
}
}}
>
{#if node.endable}
<div class="bi bi-align-end mt-[2px]"></div>
{:else}
<div class="bi bi-align-start mt-[2px]"></div>
{/if}
</button>
</div>
{/if}
</div>

View File

@@ -1,60 +1,69 @@
export type Node = {
id: string,
user_id: string,
x: number;
y: number;
width: number;
height: number;
name: string;
icon: string;
// 1 disables editing
permission: number;
id: string;
user_id: string;
x: number;
y: number;
width: number;
height: number;
name: string;
icon: string;
// 1 disables editing
permission: number;
visible: boolean;
endable: boolean;
};
export type LinkNode = {
node: Node;
x: number;
y: number;
}
node: Node;
x: number;
y: number;
};
export type FullLink = {
sourceNode: LinkNode;
targetNode: LinkNode;
bi?: boolean;
}
sourceNode: LinkNode;
targetNode: LinkNode;
bi?: boolean;
};
export type FullLinkApi = {
id: string,
user_id: string,
target_node: string | null,
source_node: string | null,
bi: boolean,
source_x: number,
source_y: number,
target_x: number,
target_y: number
}
id: string;
user_id: string;
target_node: string | null;
source_node: string | null;
bi: boolean;
source_x: number;
source_y: number;
target_x: number;
target_y: number;
};
export type ActionType = undefined | 'drag' | 'create' | 'move' | 'link';
export function extractLinkNodePosX(canvasX: (x: number) => number, node: LinkNode, grid_size: number) {
if (node.x === -1) {
return canvasX(node.node.x);
}
if (node.x === node.node.width) {
return canvasX(node.node.x) + node.node.width * grid_size;
}
return canvasX(node.node.x) + node.x * grid_size + grid_size / 2;
export function extractLinkNodePosX(
canvasX: (x: number) => number,
node: LinkNode,
grid_size: number
) {
if (node.x === -1) {
return canvasX(node.node.x);
}
if (node.x === node.node.width) {
return canvasX(node.node.x) + node.node.width * grid_size;
}
return canvasX(node.node.x) + node.x * grid_size + grid_size / 2;
}
export function extractLinkNodePosY(canvasY: (x: number) => number, node: LinkNode, grid_size: number) {
if (node.y === -1) {
return canvasY(node.node.y);
}
if (node.y === node.node.height) {
return canvasY(node.node.y) + node.node.height * grid_size;
}
return canvasY(node.node.y) + node.y * grid_size + grid_size / 2;
export function extractLinkNodePosY(
canvasY: (x: number) => number,
node: LinkNode,
grid_size: number
) {
if (node.y === -1) {
return canvasY(node.node.y);
}
if (node.y === node.node.height) {
return canvasY(node.node.y) + node.node.height * grid_size;
}
return canvasY(node.node.y) + node.y * grid_size + grid_size / 2;
}

View File

@@ -11,7 +11,7 @@
async function submit() {
try {
await post('application/text', text);
applicationStore.loadApplications(true);
applicationStore.loadAll(true);
goto('/');
} catch (e) {
console.log(e);

View File

@@ -0,0 +1,63 @@
<script lang="ts">
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
import { statusStore } from '$lib/Types.svelte';
import { onMount } from 'svelte';
import DropZone from './DropZone.svelte';
import { deleteR, put } from '$lib/utils';
let { index = $bindable() }: { index: number | undefined } = $props();
const derivedItem: Application | undefined = $derived(applicationStore.all[index ?? -1]);
async function moveStatus(status_id: string, endable?: boolean) {
// Deactivate active item
try {
await put('application/status', {
id: derivedItem?.id,
status_id: status_id
});
} catch (e) {
// TODO: Show User
console.log('info data', e);
return;
}
applicationStore.all[index ?? -1].status_id = status_id;
applicationStore.reset();
if (endable) {
index = undefined;
return;
}
applicationStore.loadItem = derivedItem;
applicationStore.loadItemOpen = false;
}
async function remove() {
if (!derivedItem) return;
// Deactivate active item
try {
await deleteR(`application/${derivedItem.id}`);
} catch (e) {
// TODO: Show User
console.log('info data', e);
return;
}
applicationStore.removeByID(derivedItem.id);
index = undefined;
}
onMount(() => {
statusStore.load();
});
</script>
{#if applicationStore.dragging && derivedItem}
<div class="flex w-full flex-grow rounded-lg p-3 gap-2 absolute bottom-0 left-0 right-0 bg-white">
{#each statusStore.dirLinks[derivedItem.status_id] as node}
<DropZone icon={node.icon} ondrop={() => moveStatus(node.id, node.endable)}
>{node.name}</DropZone
>
{/each}
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}>Delete it</DropZone>
</div>
{/if}

View File

@@ -1,26 +1,37 @@
<script lang="ts">
import { applicationStore } from '$lib/ApplicationsStore.svelte';
let { save, company = $bindable() }: { company: string; save: () => void } = $props();
let { save, index }: { index: number; save: () => void } = $props();
let companies = $derived(new Set(applicationStore.all.map((a) => a.company)));
let fcomps = $derived(
company === ''
applicationStore.all[index].company === ''
? []
: [...companies.values()].filter((a) => {
// TODO improve this a lot I want to make like matching algo
return a.match(company) && a !== company;
return (
a.match(applicationStore.all[index].company) &&
a !== applicationStore.all[index].company
);
})
);
</script>
<fieldset class="grow">
<label class="flabel" for="title">Company</label>
<input class="finput" id="title" bind:value={company} onchange={save} />
<input
class="finput"
id="title"
bind:value={applicationStore.all[index].company}
onchange={save}
/>
<div class="flex gap-2 flex-wrap">
{#each fcomps as comp}
<button class="bg-blue-200 px-3 p-1 min-h-0 rounded-lg" onclick={() => company = comp}>
<button
class="bg-blue-200 px-3 p-1 min-h-0 rounded-lg"
onclick={() => (applicationStore.all[index].company = comp)}
>
{comp}
</button>
{/each}

View File

@@ -1,198 +0,0 @@
<script lang="ts">
import {
ApplicationStatus,
applicationStore,
type Application,
type AsEnum
} from '$lib/ApplicationsStore.svelte';
import { deleteR, put } from '$lib/utils';
import DropZone from './DropZone.svelte';
let { activeItem = $bindable() }: { activeItem: Application | undefined } = $props();
async function moveStatus(status: AsEnum<typeof ApplicationStatus>, moveOut = true) {
if (!activeItem) return;
// Deactivate active item
try {
await put('application/status', {
id: activeItem.id,
status: status
});
} catch (e) {
// Show User
console.log('info data', e);
return;
}
applicationStore.loadApplications(true);
applicationStore.loadAplyed(true);
if (moveOut) {
activeItem = undefined;
} else {
activeItem.status = status;
}
//openedWindow?.close();
//openedWindow = undefined;
}
async function remove() {
if (!activeItem) return;
// Deactivate active item
try {
await deleteR(`application/${activeItem.id}`);
} catch (e) {
// Show User
console.log('info data', e);
return;
}
applicationStore.loadApplications(true);
activeItem = undefined;
//openedWindow?.close();
//openedWindow = undefined;
}
</script>
{#if applicationStore.dragging && activeItem}
<div
class="flex w-full flex-grow rounded-lg p-3 gap-2 absolute bottom-0 left-0 right-0 bg-white"
>
<!-- Do nothing -->
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
<DropZone
icon="box-arrow-down"
ondrop={async () => {
await moveStatus(ApplicationStatus.ToApply);
applicationStore.loadAplyed(true);
applicationStore.loadAll(true);
}}
>
To apply
</DropZone>
{/if}
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
<!-- Ignore -->
<DropZone icon="trash-fill" ondrop={() => moveStatus(ApplicationStatus.Ignore)}>
Ignore it
</DropZone>
{/if}
{#if ([ApplicationStatus.WorkingOnIt, ApplicationStatus.Expired] as number[]).includes(activeItem.status)}
<!-- Expired -->
<DropZone
icon="clock-fill text-orange-500"
ondrop={() => {
if (activeItem && activeItem.status === ApplicationStatus.Expired) {
moveStatus(ApplicationStatus.ToApply, false);
} else {
moveStatus(ApplicationStatus.Expired);
}
}}
>
Mark as expired
</DropZone>
{/if}
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
<!-- Repeated -->
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}>Delete it</DropZone>
{/if}
{#if ([ApplicationStatus.WorkingOnIt, ApplicationStatus.TasksToDo] as number[]).includes(activeItem.status)}
<!-- Applyed -->
<DropZone
icon="server text-confirm"
ondrop={async () => {
await moveStatus(ApplicationStatus.Applyed);
applicationStore.loadAll(true);
applicationStore.loadAplyed(true);
}}
>
Apply
</DropZone>
{/if}
{#if activeItem.status === ApplicationStatus.Applyed}
<!-- Tasks to do -->
<DropZone
icon="server text-confirm"
ondrop={async () => {
await moveStatus(ApplicationStatus.TasksToDo);
applicationStore.loadAll(true);
applicationStore.loadAplyed(true);
}}
>
Tasks To Do
</DropZone>
{/if}
{#if activeItem.status === ApplicationStatus.TasksToDo}
<!-- Tasks to do -->
<DropZone
icon="server text-confirm"
ondrop={async () => {
await moveStatus(ApplicationStatus.TasksToDo2);
applicationStore.loadAll(true);
applicationStore.loadAplyed(true);
}}
>
Tasks To Do 2
</DropZone>
{/if}
{#if ([ApplicationStatus.TasksToDo, ApplicationStatus.Applyed] as number[]).includes(activeItem.status)}
<!-- Tasks to do -->
<DropZone
icon="server text-confirm"
ondrop={async () => {
await moveStatus(ApplicationStatus.InterviewStep1);
applicationStore.loadAll(true);
applicationStore.loadAplyed(true);
}}
>
Interview 1
</DropZone>
{/if}
{#if ([ApplicationStatus.InterviewStep1] as number[]).includes(activeItem.status)}
<!-- Tasks to do -->
<DropZone
icon="server text-confirm"
ondrop={async () => {
await moveStatus(ApplicationStatus.InterviewStep2);
applicationStore.loadAll(true);
applicationStore.loadAplyed(true);
}}
>
Interview 2
</DropZone>
{/if}
{#if ([ApplicationStatus.InterviewStep1, ApplicationStatus.InterviewStep2] as number[]).includes(activeItem.status)}
<!-- Tasks to do -->
<DropZone
icon="badge-vo-fill text-confirm"
ondrop={async () => {
await moveStatus(ApplicationStatus.FinalInterview);
applicationStore.loadAll(true);
applicationStore.loadAplyed(true);
}}
>
Interview Final
</DropZone>
{/if}
{#if !([ApplicationStatus.ApplyedButSaidNo] as number[]).includes(activeItem.status)}
<!-- Rejected -->
<DropZone
icon="fire text-danger"
ondrop={() => {
moveStatus(ApplicationStatus.ApplyedButSaidNo);
applicationStore.loadAll(true);
applicationStore.loadAplyed(true);
}}
>
I was rejeted :(
</DropZone>
{/if}
</div>
{/if}

View File

@@ -22,7 +22,7 @@
onreload();
} catch (e) {
// Show message to the user
console.log(e);
console.log('TODO inform the user', e);
}
}
</script>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { ApplicationStatus, ApplicationStatusMaping, type Application } from '$lib/ApplicationsStore.svelte';
import { post, put } from '$lib/utils';
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
import { statusStore } from '$lib/Types.svelte';
import { post } from '$lib/utils';
let {
application,
@@ -13,43 +14,21 @@
} = $props();
let filter = $state('');
let applications: Application[] = $state([]);
async function getApplicationList() {
const app: Application[] = await post('application/list', {});
applications = app.filter((a) => a.id != application.id);
}
$effect(() => {
getApplicationList();
});
async function submit(item: Application) {
try {
application.linked_application = item.id;
await put('application/update', application);
await put('application/status', {
id: application.id,
status: ApplicationStatus.LinkedApplication
});
await post(`application/link/application/${application.id}/${item.id}`, {});
applicationStore.removeByID(application.id);
dialog.close();
onreload(item);
} catch (e) {
// Show message to the user
// TODO: Show message to the user
console.log(e);
}
}
</script>
<dialog class="card max-w-[50vw]" bind:this={dialog}>
<div class="flex">
<input placeholder="Filter" class="p-2 flex-grow" bind:value={filter} />
<div>
{applications.length}
</div>
</div>
<div class="overflow-y-auto overflow-x-hidden flex-grow p-2">
{#each applications.filter((i) => {
let internal = $derived(
applicationStore.all.filter((i) => {
if (!filter) {
return true;
}
@@ -62,7 +41,19 @@
}
return x.match(f);
}) as item}
})
);
</script>
<dialog class="card max-w-[50vw]" bind:this={dialog}>
<div class="flex">
<input placeholder="Filter" class="p-2 flex-grow" bind:value={filter} />
<div>
{internal.length}
</div>
</div>
<div class="overflow-y-auto overflow-x-hidden flex-grow p-2">
{#each internal as item}
<div class="card p-2 my-2 bg-slate-100 max-w-full" role="none">
<button class="text-left max-w-full" type="button" onclick={() => submit(item)}>
<h2 class="text-lg text-blue-500">
@@ -73,9 +64,9 @@
</div>
{/if}
</h2>
<span>
{ApplicationStatusMaping[item.status]}
</span>
<span>
{statusStore.nodesR[item.status_id].name}
</span>
</button>
</div>
{/each}

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import type { Application } from '$lib/ApplicationsStore.svelte';
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
import { post, preventDefault } from '$lib/utils';
let {
const {
onreload
}: {
onreload: (item: Application) => void;
@@ -12,20 +12,50 @@
let link = $state('');
let dialogAddMultiple = $state(false);
let inputField: HTMLTextAreaElement;
async function createApplication() {
try {
if (dialogAddMultiple) {
const r: Application[] = await post('application/text', {
text: link
});
for (const app of r) {
applicationStore.all.push(app);
}
if (r.length > 0) {
onreload(r[0]);
}
dialogElement.close();
link = '';
return;
}
const r: Application = await post('application/link', {
text: link
});
applicationStore.all.push(r);
onreload(r);
dialogElement.close()
dialogElement.close();
link = '';
} catch (e) {
console.log('Inform the user', e);
console.log('TODO: Inform the user', e);
}
}
function docKey(e: KeyboardEvent) {
if (e.ctrlKey && e.code === 'KeyN') {
if (e.ctrlKey && e.shiftKey && e.code === 'KeyN') {
dialogAddMultiple = true;
dialogElement.showModal();
e.stopPropagation();
e.preventDefault();
window.requestAnimationFrame(() => {
inputField?.focus();
});
return;
} else if (e.ctrlKey && e.code === 'KeyN') {
dialogAddMultiple = false;
dialogElement.showModal();
e.stopPropagation();
e.preventDefault();
@@ -41,11 +71,34 @@
});
</script>
<dialog class="card max-w-[50vw]" bind:this={dialogElement}>
<dialog
class="card max-w-[50vw]"
bind:this={dialogElement}
onclose={() => {
dialogAddMultiple = false;
}}
>
<form onsubmit={preventDefault(createApplication)}>
<fieldset>
<label class="flabel" for="title">Link</label>
<input class="finput" id="title" bind:value={link} required />
<label class="flabel" for="title"
>{#if dialogAddMultiple}Email text{:else}Link{/if}</label
>
{#if dialogAddMultiple}
<textarea
class="finput"
id="title"
bind:value={link}
required
bind:this={inputField}
></textarea>
{:else}
<input class="finput" id="title" bind:value={link} required />
{/if}
</fieldset>
<div class="flex justify-end">
{#if dialogAddMultiple}
<button type="submit" class="btn-primary">Submit</button>
{/if}
</div>
</form>
</dialog>

View File

@@ -1,16 +1,18 @@
<script lang="ts">
import type { Application } from '$lib/ApplicationsStore.svelte';
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
import { post, preventDefault } from '$lib/utils';
let {
dialog = $bindable(),
onreload,
id
id,
index
}: {
dialog: HTMLDialogElement;
onreload: (item: Application) => void;
id: string;
openWindow?: Window | null;
index?: number;
} = $props();
const data = $state({
@@ -27,6 +29,9 @@
});
data.url = '';
dialog.close();
if (newItem.id !== applicationStore.all[index ?? -1].id) {
applicationStore.removeByID(applicationStore.all[index ?? -1].id);
}
onreload(newItem);
} catch (e) {
// Show message to the user

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { ApplicationStatusMaping, type Application } from '$lib/ApplicationsStore.svelte';
import { post } from '$lib/utils';
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
import { statusStore } from '$lib/Types.svelte';
let {
application,
@@ -11,18 +11,11 @@
} = $props();
let filter = $state('');
let applications: Application[] = $state([]);
let dialogElement: HTMLDialogElement;
async function getApplicationList() {
applications = await post('application/list', {});
}
function docKey(e: KeyboardEvent) {
if (e.ctrlKey && e.code === 'KeyK') {
applications = [];
getApplicationList();
dialogElement.showModal();
e.stopPropagation();
e.preventDefault();
@@ -38,7 +31,10 @@
});
let internal = $derived(
applications.filter((i) => {
applicationStore.all.filter((i) => {
if (i.linked_application) {
return false;
}
if (application && i.id == application.id) {
return false;
}
@@ -96,7 +92,7 @@
</div>
</h2>
<span>
{ApplicationStatusMaping[item.status]}
{statusStore.nodesR[item.status_id]?.name ?? 'NOT FOUND'}
</span>
</button>
</div>

View File

@@ -2,13 +2,11 @@
import {
type Application,
type ApplicationEvent,
ApplicationStatus,
EventType,
ApplicationStatusIconMaping,
ApplicationStatusMaping
EventType
} from '$lib/ApplicationsStore.svelte';
import { statusStore } from '$lib/Types.svelte';
let { application, showAll }: { application: Application, showAll: boolean } = $props();
let { application, showAll }: { application: Application; showAll: boolean } = $props();
let events: (ApplicationEvent & { timeDiff: string })[] = $state([]);
@@ -36,17 +34,10 @@
}
$effect(() => {
let status: number | undefined = undefined;
let lastEvent: ApplicationEvent | undefined = undefined;
let _events: typeof events = [];
let checkArray: (number | undefined)[] = [
ApplicationStatus.WorkingOnIt,
ApplicationStatus.ToApply
];
for (let event of application.events) {
let time = '';
if (lastEvent) {
@@ -55,25 +46,6 @@
time = calcDiff(d2 - d1);
}
if (event.event_type === EventType.Creation) {
status = ApplicationStatus.ToApply;
}
if (event.event_type !== EventType.StatusUpdate || showAll) {
if (time) {
_events[_events.length - 1].timeDiff = time;
}
_events.push({ ...event, timeDiff: '' });
lastEvent = event;
continue;
}
if (
checkArray.includes(status) &&
checkArray.includes(event.new_status) &&
lastEvent?.event_type !== EventType.Creation
) {
continue;
}
if (lastEvent) {
let d1 = new Date(lastEvent.time).getTime();
let d2 = new Date(event.time).getTime();
@@ -84,26 +56,21 @@
_events[_events.length - 1].timeDiff = time;
}
status = event.new_status;
_events.push({ ...event, timeDiff: '' });
lastEvent = event;
}
if (_events.length > 0 && !endable.includes(_events[_events.length - 1].new_status)) {
// Todo endable
/*
if (_events.length > 0 && !endable.includes(_events[_events.length - 1].new_status_id)) {
let d1 = new Date(_events[_events.length - 1].time).getTime();
let d2 = new Date().getTime();
_events[_events.length - 1].timeDiff = calcDiff(d2 - d1);
}
*/
events = _events;
});
let endable: number[] = [
ApplicationStatus.Expired,
ApplicationStatus.Ignore,
ApplicationStatus.ApplyedButSaidNo,
ApplicationStatus.LinkedApplication
];
</script>
{#if events.length > 0}
@@ -118,28 +85,24 @@
class="shadow-sm shadow-violet-500 border rounded-full min-w-[50px] min-h-[50px] grid place-items-center bg-white z-10"
>
{#if event.event_type == EventType.Creation}
<span
title={`Created @\n ${new Date(event.time).toLocaleString()}`}
class="bi bi-plus"
<span title={`Created @\n ${new Date(event.time).toLocaleString()}`} class="bi bi-plus"
></span>
{:else if event.event_type == EventType.View}
<span
title={`Viewed @\n ${new Date(event.time).toLocaleString()}`}
class="bi bi-eye"
<span title={`Viewed @\n ${new Date(event.time).toLocaleString()}`} class="bi bi-eye"
></span>
{:else}
<span
title={`${ApplicationStatusMaping[event.new_status]}\n${new Date(
title={`${statusStore.nodesR[event.new_status_id].name}\n${new Date(
event.time
).toLocaleString()}`}
class="bi bi-{ApplicationStatusIconMaping[event.new_status]}"
class="bi bi-{statusStore.nodesR[event.new_status_id].icon}"
></span>
{/if}
</div>
{#if i != events.length - 1 || !endable.includes(event.new_status)}
<div
class="min-w-[70px] h-[18px] bg-blue-500 -mx-[10px] px-[20px] flex-grow text-center"
>
<!-- TODO -->
<!-- || !endable.includes(event.new_status) -->
{#if i != events.length - 1 || !statusStore.nodesR[event.new_status_id].endable}
<div class="min-w-[70px] h-[18px] bg-blue-500 -mx-[10px] px-[20px] flex-grow text-center">
{#if event.timeDiff}
<div class="-mt-[3px] text-white">
<span class="bi bi-clock"></span>

View File

@@ -1,10 +1,5 @@
<script lang="ts">
import {
ApplicationStatus,
ApplicationStatusMaping,
applicationStore,
type Application
} from '$lib/ApplicationsStore.svelte';
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
import { put, preventDefault, post, get } from '$lib/utils';
import { onMount } from 'svelte';
@@ -16,10 +11,15 @@
import SearchApplication from './SearchApplication.svelte';
import NewApplication from './NewApplication.svelte';
import Timeline from './Timeline.svelte';
import DropingZone from './DropingZone.svelte';
import CompanyField from './CompanyField.svelte';
import AutoDropZone from './AutoDropZone.svelte';
import { statusStore } from '$lib/Types.svelte';
import { thresholdFreedmanDiaconis } from 'd3';
let activeItem: Application | undefined = $state();
// Not this represents the index in the store array
let activeItem: number | undefined = $state();
let derivedItem: Application | undefined = $derived(applicationStore.all[activeItem ?? -1]);
let extractTokens: HTMLDialogElement;
let changeUrl: HTMLDialogElement;
@@ -32,45 +32,30 @@
let drag = $state(true);
async function activate(item?: Application) {
async function activate(item?: Application, ow = true) {
if (!item) {
return;
}
if (activeItem && activeItem.id !== item.id && activeItem.status === 1) {
// Deactivate active item
try {
await put('application/status', {
id: activeItem.id,
status: 0
});
} catch (e) {
// Show User
console.log('info data', e);
return;
}
}
if (item.status === 0) {
if (ow && item.id !== derivedItem?.id) {
openWindow(item.url);
openCV(item.id);
}
openCV(item.id);
try {
await put('application/status', {
id: item.id,
status: ApplicationStatus.WorkingOnIt
});
activeItem = await get('application/active');
if (!activeItem?.unique_url) {
const toLoadItem: Application = await get(`application/${item.id}`);
if (!toLoadItem?.simple_url) {
window.requestAnimationFrame(() => {
changeUrl.showModal();
});
}
activeItem = applicationStore.getIndexById(toLoadItem.id);
applicationStore.all[activeItem ?? -1] = toLoadItem;
applicationStore.reset();
} catch (e) {
// Show User
console.log('info data', e);
}
applicationStore.loadApplications(true);
autoExtData = false;
lastExtData = undefined;
@@ -90,7 +75,7 @@
function openCV(id?: string) {
window.open(
`/cv?id=${id ?? activeItem!.id}`,
`/cv?id=${id ?? derivedItem!.id}`,
'cv viwer',
`location,height=${window.screen.height},width=${window.screen.width},scrollbars,status,toolbar,menubar,popup`
);
@@ -100,8 +85,8 @@
// Make the CV open on a new page
//
function docKey(e: KeyboardEvent) {
if (activeItem && e.ctrlKey && e.code === 'KeyO') {
openCV(activeItem.id);
if (derivedItem && e.ctrlKey && e.code === 'KeyO') {
openCV(derivedItem.id);
e.stopPropagation();
e.preventDefault();
return;
@@ -117,18 +102,6 @@
//
//
async function loadActive() {
try {
activeItem = await get('application/active');
if (activeItem?.unique_url === null) {
changeUrl.showModal();
}
} catch (e) {
// Show User
console.log('info data', e);
}
}
// Load Ext
$effect(() => {
function onMessage(e: MessageEvent) {
@@ -150,21 +123,22 @@
});
function setExtData() {
if (!lastExtData || !activeItem) return;
activeItem.title = lastExtData.jobTitle;
activeItem.company = lastExtData.company;
activeItem.payrange = lastExtData.money;
if (!lastExtData || activeItem === undefined || !derivedItem) return;
applicationStore.all[activeItem].title = lastExtData.jobTitle;
applicationStore.all[activeItem].company = lastExtData.company;
applicationStore.all[activeItem].payrange = lastExtData.money;
window.requestAnimationFrame(() => {
save().then(async () => {
if (activeItem === undefined) return;
if (!lastExtData.description) {
lastExtData = undefined;
return;
}
await post('application/text/flair', {
id: activeItem?.id ?? '',
id: derivedItem.id ?? '',
text: lastExtData.description
});
loadActive();
activate(derivedItem, false);
lastExtData = undefined;
});
});
@@ -172,20 +146,26 @@
onMount(() => {
userStore.checkLogin();
loadActive();
statusStore.load();
});
$effect(() => {
if (!applicationStore.loadItem) {
return;
}
activeItem = applicationStore.loadItem;
applicationStore.loadItem = undefined;
(async () => {
await activate(applicationStore.loadItem, applicationStore.loadItemOpen);
applicationStore.loadItem = undefined;
})();
});
$effect(() => {
applicationStore.loadAll();
});
async function save() {
try {
await put('application/update', activeItem);
await put('application/update', applicationStore.all[activeItem ?? -1]);
} catch (e) {
// Show User
console.log('info data', e);
@@ -194,7 +174,7 @@
async function resetUrl() {
try {
activeItem = await post(`application/reset/url/${activeItem?.id}`, {});
activeItem = await post(`application/reset/url/${derivedItem?.id}`, {});
} catch (e) {
// Show User
console.log('info data', e);
@@ -203,11 +183,11 @@
</script>
<div class="flex flex-col w-full gap-2 min-w-0 relative" role="none">
{#if activeItem && (!applicationStore.dragging || applicationStore.dragging?.id === activeItem.id)}
{#if activeItem !== undefined && (!applicationStore.dragging || applicationStore.dragging?.id === derivedItem.id)}
<div
draggable={drag}
ondrag={() => {
applicationStore.dragStart(activeItem);
applicationStore.dragStart(derivedItem);
}}
ondragend={() => {
window.requestAnimationFrame(() => {
@@ -218,35 +198,34 @@
class="flex flex-col p-2 h-full gap-2 card min-w-0 flex-grow min-h-[50vh]"
>
<div class="w-full">
{#if activeItem.status != 1}
<!-- TODO add item -->
{#if derivedItem.status_id !== null}
<div class="bg-danger text-white p-2 rounded-lg text-lg font-bold">
{(ApplicationStatusMaping[
activeItem?.status as (typeof ApplicationStatus)[keyof typeof ApplicationStatus]
] as string) ?? ''}
{statusStore.nodesR[derivedItem.status_id].name}
</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}
{derivedItem.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}
{derivedItem.create_time}
</div>
</fieldset>
{/if}
<div class="flex gap-2">
<CompanyField bind:company={activeItem.company} {save} />
<CompanyField index={activeItem} {save} />
<fieldset class="grow">
<label class="flabel" for="title">Recruiter</label>
<input
class="finput"
id="title"
bind:value={activeItem.recruiter}
bind:value={applicationStore.all[activeItem].recruiter}
onchange={save}
/>
</fieldset>
@@ -256,7 +235,7 @@
class="finput"
id="title"
type="checkbox"
bind:checked={activeItem.agency}
bind:checked={applicationStore.all[activeItem].agency}
onchange={save}
/>
</fieldset>
@@ -266,7 +245,7 @@
<input
class="finput"
id="title"
bind:value={activeItem.title}
bind:value={applicationStore.all[activeItem].title}
onchange={save}
/>
</fieldset>
@@ -275,51 +254,36 @@
<input
class="finput"
id="payrange"
bind:value={activeItem.payrange}
bind:value={applicationStore.all[activeItem].payrange}
onchange={save}
/>
</fieldset>
{#if !activeItem.unique_url || showExtraData}
{#if !derivedItem.simple_url || showExtraData}
<fieldset class="max-w-full min-w-0 overflow-hidden">
<div class="flabel">Url</div>
<div class="finput bg-white w-full break-keep">
{activeItem.url}
{derivedItem.url}
</div>
</fieldset>
{/if}
{#if activeItem.unique_url}
<fieldset>
<div class="flabel">Unique Url</div>
<div class="finput bg-white">
{activeItem.unique_url}
</div>
</fieldset>
{/if}
{#if activeItem.linked_application}
<fieldset>
<div class="flabel">Linked Application</div>
<div class="finput bg-white">
{activeItem.linked_application}
</div>
</fieldset>
{/if}
{#if activeItem.application_time}
<fieldset>
<div class="flabel">Application Time</div>
<div class="finput bg-white">
{activeItem.application_time}
{#if derivedItem.simple_url}
<fieldset class="max-w-full min-w-0 overflow-hidden">
<div class="flabel">Simple Url</div>
<div class="finput bg-white w-full break-keep">
{derivedItem.simple_url}
</div>
</fieldset>
{/if}
<div>
<div class="flabel">Tags</div>
<div class="flex gap-2 flex-wrap">
{#each activeItem.flairs as item}
{#each derivedItem.flairs as item}
<Flair
{item}
allowDelete
application={activeItem}
updateApplication={(item) => (activeItem = item)}
application={derivedItem}
updateApplication={(item) =>
(applicationStore.all[activeItem ?? -1] = item)}
/>
{/each}
</div>
@@ -329,7 +293,7 @@
<textarea
class="finput"
id="extra"
bind:value={activeItem.extra_data}
bind:value={applicationStore.all[activeItem].extra_data}
onchange={save}
></textarea>
</fieldset>
@@ -338,24 +302,16 @@
<textarea
class="finput"
id="extra"
bind:value={activeItem.message}
bind:value={applicationStore.all[activeItem].message}
onchange={save}
></textarea>
</fieldset>
{#if activeItem.views.length > 0}
<h1 class="text-sm">Non Loggedin Views Time ({activeItem.views.length})</h1>
{#each activeItem.views as view}
<div>
{view.time}
</div>
{/each}
{/if}
</div>
<div class="flex btns">
<button
class="btn-confirm"
onclick={() => {
openWindow(activeItem!.url);
openWindow(derivedItem!.url);
}}
>
Open
@@ -378,18 +334,16 @@
Get Ext Data
</button>
{/if}
{#if activeItem.original_url == null}
<button class="btn-primary" onclick={() => changeUrl.showModal()}>
Update Url
</button>
{/if}
<button
class={derivedItem.simple_url ? 'btn-danger' : 'btn-primary'}
onclick={() => changeUrl.showModal()}
>
Update Url
</button>
<div class="px-10"></div>
<button class="btn-primary" onclick={() => linkApplication.showModal()}>
Link Application
</button>
{#if activeItem.original_url != null}
<button class="btn-danger" onclick={resetUrl}> Reset Url </button>
{/if}
<button
class:btn-primary={drag}
class:btn-danger={!drag}
@@ -405,9 +359,9 @@
🔬
</button>
</div>
<Timeline application={activeItem} showAll={showExtraData} />
<Timeline application={derivedItem} showAll={showExtraData} />
</div>
<DropingZone bind:activeItem />
<AutoDropZone bind:index={activeItem} />
{:else}
<div
class="p-2 h-full w-full gap-2 flex-grow card grid place-items-center min-h-[50vh]"
@@ -432,32 +386,35 @@
{/if}
</div>
<ExtractTextDialog id={activeItem?.id ?? ''} bind:dialog={extractTokens} onreload={loadActive} />
<ExtractTextDialog
id={derivedItem?.id ?? ''}
bind:dialog={extractTokens}
onreload={() => activate(derivedItem, false)}
/>
{#if !activeItem?.original_url}
{#if derivedItem}
<NewUrlDialog
id={activeItem?.id ?? ''}
id={derivedItem?.id ?? ''}
bind:dialog={changeUrl}
index={activeItem}
onreload={async (item) => {
item.events = await get(`events/${item.id}`);
activeItem = item;
activate(item, false);
}}
/>
{/if}
{#if activeItem}
{#if derivedItem}
<LinkApplication
application={activeItem}
application={derivedItem}
bind:dialog={linkApplication}
onreload={(item) => (activeItem = item)}
onreload={(item) => activate(item, false)}
/>
{/if}
<SearchApplication
application={activeItem}
application={derivedItem}
onreload={async (item) => {
item.events = await get(`events/${item.id}`);
activeItem = item;
activate(item);
}}
/>