chore: update to so many things
This commit is contained in:
@@ -40,3 +40,5 @@ You can preview the production build with `npm run preview`.
|
||||
## TODO
|
||||
|
||||
https://www.glassdoor.co.uk/job-listing/junior-software-developer-full-stack-onnec-group-JV_IC2671300_KO0,36_KE37,48.htm?jl=1009478590946&utm_source=jobsForYou&utm_medium=email&utm_content=jobs-for-you-jobsForYou-jobpos5-1009478590946&utm_campaign=jobsForYou&src=GD_JOB_AD&uido=5EF1E454F911F36A51BB1DD9CB97C8DB&ao=1136043&jrtk=6-y100011i9mkhgisgqqb801ab7157ddf833b2e1c&cs=1_57b69a24&s=362&t=REC_JOBS&pos=105&guid=00000192656d7c18b164f224f0a88e83&jobListingId=1009478590946&ea=1&vt=e&cb=1728410338190&ctt=1728466768707
|
||||
|
||||
https://careers.veeva.com/job/866d4776-9d23-4311-ab16-4ebff725984d/frontend-engineer-react-remote-london-united-kingdom/
|
||||
|
||||
@@ -49,6 +49,8 @@ export type Application = {
|
||||
message: string;
|
||||
linked_application: string;
|
||||
application_time: string;
|
||||
create_time: string;
|
||||
status_history: string;
|
||||
flairs: Flair[];
|
||||
views: View[];
|
||||
};
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
<div class="p-3 bg-white w-[190mm] rounded-lg">
|
||||
<h1 class="flex gap-5 items-end">
|
||||
Your Ad & My skills {#if flairs.length > 0}<input
|
||||
placeholder="Loking for other skills search?"
|
||||
placeholder="Search other skills!"
|
||||
class="flex-grow text-blue-500 print:hidden"
|
||||
bind:value={otherSearch}
|
||||
/>
|
||||
|
||||
@@ -21,119 +21,125 @@
|
||||
| AsEnum<typeof ApplicationStatus>
|
||||
| 'Linkedin'
|
||||
| 'Glassdoor'
|
||||
| 'Unknown Source'
|
||||
| 'Applications';
|
||||
| 'Direct Source';
|
||||
|
||||
let nodeTypes: Record<NodeType, number> = {
|
||||
[ApplicationStatus.ToApply]: 0,
|
||||
[ApplicationStatus.WorkingOnIt]: 0,
|
||||
[ApplicationStatus.Ignore]: 0,
|
||||
[ApplicationStatus.ApplyedButSaidNo]: 0,
|
||||
[ApplicationStatus.Expired]: 0,
|
||||
[ApplicationStatus.Applyed]: 0,
|
||||
[ApplicationStatus.TasksToDo]: 0,
|
||||
[ApplicationStatus.LinkedApplication]: 0,
|
||||
Linkedin: 0,
|
||||
Glassdoor: 0,
|
||||
'Unknown Source': 0,
|
||||
Applications: applications.length
|
||||
};
|
||||
let graph = {} as Record<string, Record<string, number>>;
|
||||
|
||||
const showPercentage: string[] = [
|
||||
`${ApplicationStatus.ToApply}`,
|
||||
`${ApplicationStatus.Ignore}`,
|
||||
`${ApplicationStatus.Expired}`,
|
||||
`${ApplicationStatus.Applyed}`,
|
||||
`${ApplicationStatus.LinkedApplication}`,
|
||||
`${ApplicationStatus.ApplyedButSaidNo}`,
|
||||
`${ApplicationStatus.TasksToDo}`
|
||||
];
|
||||
|
||||
const baseLinks: { source: NodeType; target: NodeType; value: 0 | 1; end?: boolean }[] =
|
||||
[
|
||||
{ source: 'Linkedin', target: 'Applications', value: 0 },
|
||||
{ source: 'Glassdoor', target: 'Applications', value: 0 },
|
||||
{ source: 'Unknown Source', target: 'Applications', value: 0 },
|
||||
{ source: 'Applications', target: ApplicationStatus.ToApply, value: 1 },
|
||||
{ source: 'Applications', target: ApplicationStatus.Ignore, value: 1 },
|
||||
{ source: 'Applications', target: ApplicationStatus.Expired, value: 1 },
|
||||
{ source: 'Applications', target: ApplicationStatus.Applyed, value: 1 },
|
||||
{
|
||||
source: 'Applications',
|
||||
target: ApplicationStatus.LinkedApplication,
|
||||
value: 1
|
||||
},
|
||||
|
||||
{
|
||||
source: ApplicationStatus.Applyed,
|
||||
target: ApplicationStatus.ApplyedButSaidNo,
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
source: ApplicationStatus.Applyed,
|
||||
target: ApplicationStatus.TasksToDo,
|
||||
value: 1
|
||||
}
|
||||
];
|
||||
function addGraph(inSource: NodeType, inTarget: NodeType) {
|
||||
const source = `${inSource}`;
|
||||
const target = `${inTarget}`;
|
||||
if (graph[source] == undefined) {
|
||||
graph[source] = {} as Record<NodeType, number>;
|
||||
}
|
||||
graph[source][target] = (graph[source][target] ?? 0) + 1;
|
||||
return target as NodeType;
|
||||
}
|
||||
|
||||
applications.forEach((a) => {
|
||||
let source: NodeType;
|
||||
|
||||
if (a.url.includes('linkedin')) {
|
||||
nodeTypes['Linkedin'] += 1;
|
||||
source = 'Linkedin';
|
||||
} else if (a.url.includes('glassdoor')) {
|
||||
nodeTypes['Glassdoor'] += 1;
|
||||
source = 'Glassdoor';
|
||||
} else {
|
||||
nodeTypes['Unknown Source'] += 1;
|
||||
}
|
||||
if (a.status !== ApplicationStatus.WorkingOnIt) {
|
||||
nodeTypes[a.status] += 1;
|
||||
} else {
|
||||
nodeTypes[ApplicationStatus.ToApply] += 1;
|
||||
source = 'Direct Source';
|
||||
}
|
||||
|
||||
if (
|
||||
[ApplicationStatus.ApplyedButSaidNo, ApplicationStatus.TasksToDo].includes(
|
||||
(
|
||||
[
|
||||
ApplicationStatus.Ignore,
|
||||
ApplicationStatus.Expired,
|
||||
ApplicationStatus.LinkedApplication,
|
||||
ApplicationStatus.ToApply,
|
||||
ApplicationStatus.Applyed
|
||||
] as AsEnum<typeof ApplicationStatus>[]
|
||||
).includes(a.status)
|
||||
) {
|
||||
addGraph(source, a.status);
|
||||
return;
|
||||
}
|
||||
|
||||
// Edge case for working on it
|
||||
if (a.status === ApplicationStatus.WorkingOnIt) {
|
||||
addGraph(source, ApplicationStatus.ToApply);
|
||||
return;
|
||||
}
|
||||
|
||||
if (a.status == ApplicationStatus.ApplyedButSaidNo) {
|
||||
const history = a.status_history.split(',');
|
||||
source = addGraph(source, ApplicationStatus.Applyed);
|
||||
if (history.includes(`${ApplicationStatus.TasksToDo}`)) {
|
||||
source = addGraph(source, ApplicationStatus.TasksToDo);
|
||||
}
|
||||
addGraph(source, ApplicationStatus.ApplyedButSaidNo);
|
||||
} else if (
|
||||
([ApplicationStatus.TasksToDo] as AsEnum<typeof ApplicationStatus>[]).includes(
|
||||
a.status
|
||||
)
|
||||
) {
|
||||
nodeTypes[ApplicationStatus.Applyed] += 1;
|
||||
addGraph(source, ApplicationStatus.Applyed);
|
||||
addGraph(ApplicationStatus.Applyed, a.status);
|
||||
}
|
||||
});
|
||||
|
||||
let inNodes: string[] = [];
|
||||
|
||||
let nodes = (Object.keys(nodeTypes) as (keyof typeof nodeTypes)[])
|
||||
.filter((a) => nodeTypes[a] > 0)
|
||||
.map((a, i) => {
|
||||
inNodes.push(`${a}`);
|
||||
const base = {
|
||||
value: nodeTypes[a],
|
||||
originalValue: a,
|
||||
id: '',
|
||||
index: i,
|
||||
percentage: Math.trunc((nodeTypes[a] / applications.length) * 100),
|
||||
end: showPercentage.includes(`${a}`)
|
||||
};
|
||||
if (Number.isNaN(Number(a))) {
|
||||
base.id = a as string;
|
||||
} else {
|
||||
base.id = ApplicationStatusMaping[a as AsEnum<typeof ApplicationStatus>];
|
||||
let inNodes: string[] = Object.keys(graph).reduce((acc, elm) => {
|
||||
const arr = Object.keys(graph[elm]).concat(elm);
|
||||
for (const i of arr) {
|
||||
if (!acc.includes(i)) {
|
||||
acc.push(i);
|
||||
}
|
||||
return base;
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, [] as string[]);
|
||||
|
||||
const links = baseLinks
|
||||
.filter(
|
||||
(link) =>
|
||||
inNodes.includes(`${link.source}`) && inNodes.includes(`${link.target}`)
|
||||
)
|
||||
.map((link) => {
|
||||
const source = inNodes.indexOf(`${link.source}`);
|
||||
const target = inNodes.indexOf(`${link.target}`);
|
||||
return {
|
||||
source: source,
|
||||
target: target,
|
||||
value: [nodes[source], nodes[target]][link.value].value
|
||||
};
|
||||
});
|
||||
function getGraphValueFor(node: string): number {
|
||||
return Object.keys(graph).reduce((acc, i) => {
|
||||
if (i == node) return acc;
|
||||
if (graph[i][node] != undefined) {
|
||||
return acc + graph[i][node];
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
let nodes = inNodes.map((node, i) => {
|
||||
let name = '';
|
||||
if (Number.isNaN(Number(node))) {
|
||||
name = node;
|
||||
} else {
|
||||
name = ApplicationStatusMaping[Number(node) as AsEnum<typeof ApplicationStatus>];
|
||||
}
|
||||
const value = getGraphValueFor(node);
|
||||
const base = {
|
||||
value: value,
|
||||
originalValue: node,
|
||||
id: name,
|
||||
index: i,
|
||||
percentage: Math.trunc((value / applications.length) * 100),
|
||||
end: true
|
||||
};
|
||||
return base;
|
||||
});
|
||||
|
||||
type Link = {
|
||||
source: number;
|
||||
target: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
const links = Object.keys(graph).reduce((acc, source) => {
|
||||
return acc.concat(Object.keys(graph[source]).map((target) => {
|
||||
const ns = inNodes.indexOf(`${source}`);
|
||||
const nt = inNodes.indexOf(`${target}`);
|
||||
return {
|
||||
source: ns,
|
||||
target: nt,
|
||||
value: graph[source][target]
|
||||
};
|
||||
}));
|
||||
}, [] as Link[])
|
||||
|
||||
const bounding = chartDiv.getBoundingClientRect();
|
||||
|
||||
@@ -146,6 +152,8 @@
|
||||
|
||||
sankey.nodes(nodes).links(links).layout(32);
|
||||
|
||||
console.log("here2", nodes, links);
|
||||
|
||||
const svg = d3
|
||||
.select(chartDiv)
|
||||
.append('svg')
|
||||
@@ -171,6 +179,9 @@
|
||||
.append('path')
|
||||
.attr('class', 'link')
|
||||
.attr('d', path)
|
||||
.style('stroke', function(d) {
|
||||
return d3.rgb(color[d.source.index]).toString();
|
||||
})
|
||||
.style('stroke-width', function (d) {
|
||||
return Math.max(1, d.dy);
|
||||
})
|
||||
@@ -269,7 +280,6 @@
|
||||
|
||||
:global(.link) {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
stroke-opacity: 0.2;
|
||||
stroke-opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
51
site/src/routes/work-area/NewApplication.svelte
Normal file
51
site/src/routes/work-area/NewApplication.svelte
Normal file
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import type { Application } from '$lib/ApplicationsStore.svelte';
|
||||
import { post, preventDefault } from '$lib/utils';
|
||||
|
||||
let {
|
||||
onreload
|
||||
}: {
|
||||
onreload: (item: Application) => void;
|
||||
} = $props();
|
||||
|
||||
let dialogElement: HTMLDialogElement;
|
||||
|
||||
let link = $state('');
|
||||
|
||||
async function createApplication() {
|
||||
try {
|
||||
const r: Application = await post('application/link', {
|
||||
text: link
|
||||
});
|
||||
onreload(r);
|
||||
dialogElement.close()
|
||||
} catch (e) {
|
||||
console.log('Inform the user', e);
|
||||
}
|
||||
}
|
||||
|
||||
function docKey(e: KeyboardEvent) {
|
||||
if (e.ctrlKey && e.code === 'KeyN') {
|
||||
dialogElement.showModal();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
document.addEventListener('keydown', docKey, false);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', docKey);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<dialog class="card max-w-[50vw]" bind:this={dialogElement}>
|
||||
<form onsubmit={preventDefault(createApplication)}>
|
||||
<fieldset>
|
||||
<label class="flabel" for="title">Link</label>
|
||||
<input class="finput" id="title" bind:value={link} required />
|
||||
</fieldset>
|
||||
</form>
|
||||
</dialog>
|
||||
109
site/src/routes/work-area/SearchApplication.svelte
Normal file
109
site/src/routes/work-area/SearchApplication.svelte
Normal file
@@ -0,0 +1,109 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
ApplicationStatus,
|
||||
ApplicationStatusMaping,
|
||||
type Application
|
||||
} from '$lib/ApplicationsStore.svelte';
|
||||
import type { AsEnum } from '$lib/ApplicationsStore.svelte';
|
||||
import { post } from '$lib/utils';
|
||||
|
||||
let {
|
||||
application,
|
||||
onreload
|
||||
}: {
|
||||
application?: Application;
|
||||
onreload: (item: Application) => void;
|
||||
} = $props();
|
||||
|
||||
let filter = $state('');
|
||||
let applications: Application[] = $state([]);
|
||||
|
||||
let dialogElement: HTMLDialogElement;
|
||||
|
||||
async function getApplicationList() {
|
||||
applications = await post('application/list', {});
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
getApplicationList();
|
||||
});
|
||||
|
||||
function docKey(e: KeyboardEvent) {
|
||||
if (e.ctrlKey && e.code === 'KeyK') {
|
||||
dialogElement.showModal();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
document.addEventListener('keydown', docKey, false);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', docKey);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<dialog class="card max-w-[50vw]" bind:this={dialogElement}>
|
||||
<div class="flex">
|
||||
<input placeholder="Filter" class="p-2 flex-grow" bind:value={filter} />
|
||||
<div>
|
||||
{applications.length}
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-y-auto overflow-x-hidden flex-grow p-2">
|
||||
{#each applications.filter((i) => {
|
||||
if (application && i.id == application.id) {
|
||||
return false;
|
||||
}
|
||||
if (!filter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (filter.includes('@') && i.company) {
|
||||
const f = new RegExp(filter.split('@')[0].trim(), 'ig');
|
||||
const c = new RegExp(filter.split('@')[1].trim(), 'ig');
|
||||
return i.title.match(f) && i.company.match(c);
|
||||
}
|
||||
|
||||
const f = new RegExp(filter, 'ig');
|
||||
|
||||
let x = i.title;
|
||||
|
||||
if (i.company) {
|
||||
x = `${x} @ ${i.company}`;
|
||||
}
|
||||
|
||||
return x.match(f);
|
||||
}) as item}
|
||||
<div class="card p-2 my-2 bg-slate-100 max-w-full" role="none">
|
||||
<button class="text-left max-w-full" type="button" onclick={() => {
|
||||
dialogElement.close()
|
||||
onreload(item)
|
||||
}}>
|
||||
<h2 class="text-lg text-blue-500 flex justify-between">
|
||||
<div>
|
||||
{item.title}
|
||||
{#if item.company}
|
||||
<div class="text-violet-800">
|
||||
@ {item.company}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
{#if !([ApplicationStatus.ToApply, ApplicationStatus.WorkingOnIt] as AsEnum<ApplicationStatus>[]).includes(item!.status)}
|
||||
{ApplicationStatusMaping[item.status]}
|
||||
{/if}
|
||||
</div>
|
||||
</h2>
|
||||
<span
|
||||
class="text-violet-600 overflow-hidden whitespace-nowrap block max-w-full"
|
||||
>
|
||||
{item.url}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</dialog>
|
||||
@@ -3,7 +3,8 @@
|
||||
ApplicationStatus,
|
||||
ApplicationStatusMaping,
|
||||
applicationStore,
|
||||
type Application
|
||||
type Application,
|
||||
type AsEnum
|
||||
} from '$lib/ApplicationsStore.svelte';
|
||||
|
||||
import { put, preventDefault, post, get, deleteR } from '$lib/utils';
|
||||
@@ -14,6 +15,8 @@
|
||||
import DropZone from './DropZone.svelte';
|
||||
import { userStore } from '$lib/UserStore.svelte';
|
||||
import LinkApplication from './LinkApplication.svelte';
|
||||
import SearchApplication from './SearchApplication.svelte';
|
||||
import NewApplication from './NewApplication.svelte';
|
||||
|
||||
let activeItem: Application | undefined = $state();
|
||||
|
||||
@@ -24,6 +27,8 @@
|
||||
let lastExtData: any = $state(undefined);
|
||||
let autoExtData = false;
|
||||
|
||||
let showExtraData = $state(false);
|
||||
|
||||
async function activate(item?: Application) {
|
||||
if (!item) {
|
||||
return;
|
||||
@@ -113,8 +118,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
console.log('setting up interest');
|
||||
|
||||
window.addEventListener('message', onMessage);
|
||||
window.postMessage({ type: 'REGISTER_INTEREST' });
|
||||
return () => {
|
||||
@@ -156,7 +159,7 @@
|
||||
applicationStore.loadItem = undefined;
|
||||
});
|
||||
|
||||
async function moveStatus(status: number) {
|
||||
async function moveStatus(status: AsEnum<typeof ApplicationStatus>, moveOut = true) {
|
||||
if (!activeItem) return;
|
||||
// Deactivate active item
|
||||
try {
|
||||
@@ -171,7 +174,11 @@
|
||||
}
|
||||
applicationStore.loadApplications(true);
|
||||
applicationStore.loadAplyed(true);
|
||||
activeItem = undefined;
|
||||
if (moveOut) {
|
||||
activeItem = undefined;
|
||||
} else {
|
||||
activeItem.status = status;
|
||||
}
|
||||
//openedWindow?.close();
|
||||
//openedWindow = undefined;
|
||||
}
|
||||
@@ -240,6 +247,20 @@
|
||||
{statusMapping}
|
||||
</div>
|
||||
{/if}
|
||||
{#if showExtraData}
|
||||
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||
<div class="flabel">Id</div>
|
||||
<div class="finput bg-white w-full break-keep">
|
||||
{activeItem.id}
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="max-w-full min-w-0 overflow-hidden">
|
||||
<div class="flabel">Create Time</div>
|
||||
<div class="finput bg-white w-full break-keep">
|
||||
{activeItem.create_time}
|
||||
</div>
|
||||
</fieldset>
|
||||
{/if}
|
||||
<div class="flex gap-2">
|
||||
<fieldset class="grow">
|
||||
<label class="flabel" for="title">Company</label>
|
||||
@@ -395,6 +416,13 @@
|
||||
>
|
||||
👋
|
||||
</button>
|
||||
<button
|
||||
class:btn-primary={!showExtraData}
|
||||
class:btn-confirm={showExtraData}
|
||||
onclick={() => (showExtraData = !showExtraData)}
|
||||
>
|
||||
🔬
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{#if applicationStore.dragging}
|
||||
@@ -423,13 +451,15 @@
|
||||
>
|
||||
Ignore it
|
||||
</DropZone>
|
||||
{/if}
|
||||
|
||||
{#if [ApplicationStatus.WorkingOnIt, ApplicationStatus.Expired].includes(activeItem.status)}
|
||||
<!-- Expired -->
|
||||
<DropZone
|
||||
icon="clock-fill text-orange-500"
|
||||
ondrop={() => {
|
||||
if (activeItem && activeItem.status === ApplicationStatus.Expired) {
|
||||
moveStatus(ApplicationStatus.ToApply);
|
||||
moveStatus(ApplicationStatus.ToApply, false);
|
||||
} else {
|
||||
moveStatus(ApplicationStatus.Expired);
|
||||
}
|
||||
@@ -437,7 +467,9 @@
|
||||
>
|
||||
Mark as expired
|
||||
</DropZone>
|
||||
{/if}
|
||||
|
||||
{#if activeItem.status === ApplicationStatus.WorkingOnIt}
|
||||
<!-- Repeated -->
|
||||
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}
|
||||
>Delete it</DropZone
|
||||
@@ -522,3 +554,11 @@
|
||||
onreload={(item) => (activeItem = item)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<SearchApplication application={activeItem} onreload={(item) => (activeItem = item)} />
|
||||
|
||||
<NewApplication
|
||||
onreload={(item) => {
|
||||
activate(item);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -3,6 +3,9 @@ import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
server: {
|
||||
host: true,
|
||||
},
|
||||
build: {
|
||||
commonjsOptions: {
|
||||
esmExternals: true
|
||||
|
||||
Reference in New Issue
Block a user