259 lines
6.7 KiB
Svelte
259 lines
6.7 KiB
Svelte
<script lang="ts">
|
|
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
|
|
import InplaceDialog from '$lib/InplaceDialog.svelte';
|
|
import { statusStore } from '$lib/Types.svelte';
|
|
|
|
let {
|
|
application,
|
|
onreload
|
|
}: {
|
|
application?: Application;
|
|
onreload: (item: Application) => void;
|
|
} = $props();
|
|
|
|
const JobLevels = ['intern', 'entry', 'junior', 'mid', 'senior', 'staff', 'lead', 'none'];
|
|
|
|
let filter = $state('');
|
|
let filterStatus: string[] = $state([]);
|
|
let jobLevels: string[] = $state([]);
|
|
let advFilters = $state(false);
|
|
|
|
type ExtraFilterType =
|
|
| {
|
|
type: 'name';
|
|
text: string;
|
|
}
|
|
| { type: 'company'; text: string }
|
|
| { type: 'query'; text: string };
|
|
|
|
let extraFiltersToDisplay: ExtraFilterType[] = $state([]);
|
|
|
|
let dialogElement: HTMLDialogElement;
|
|
|
|
$effect(() => {
|
|
if (!filter) {
|
|
extraFiltersToDisplay = [];
|
|
}
|
|
});
|
|
|
|
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);
|
|
};
|
|
});
|
|
|
|
let internal = $derived(
|
|
applicationStore.all.filter((i) => {
|
|
if (i.linked_application) {
|
|
return false;
|
|
}
|
|
if (application && i.id == application.id) {
|
|
return false;
|
|
}
|
|
|
|
if (filterStatus.length !== 0 && !filterStatus.includes(i.status_id ?? '')) {
|
|
return false;
|
|
}
|
|
|
|
const temp = jobLevels.map((a) => (a === 'none' ? '' : a));
|
|
if (temp.length !== 0 && !temp.includes(i.job_level ?? '')) {
|
|
return false;
|
|
}
|
|
|
|
if (!filter) {
|
|
return true;
|
|
}
|
|
|
|
if (filter.includes('@') && i.company) {
|
|
const splits = filter.split('@');
|
|
|
|
const newExtraFilters: ExtraFilterType[] = [];
|
|
const name = splits[0].trim();
|
|
const company = splits[1].trim();
|
|
if (name.length !== 0) {
|
|
newExtraFilters.push({
|
|
type: 'name',
|
|
text: name
|
|
});
|
|
}
|
|
if (company.length !== 0) {
|
|
newExtraFilters.push({
|
|
type: 'company',
|
|
text: company
|
|
});
|
|
}
|
|
extraFiltersToDisplay = newExtraFilters;
|
|
|
|
try {
|
|
const f = new RegExp(name, 'ig');
|
|
const c = new RegExp(company, 'ig');
|
|
return i.title.match(f) && i.company.match(c);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
extraFiltersToDisplay = [{ type: 'query', text: filter }];
|
|
try {
|
|
const f = new RegExp(filter, 'ig');
|
|
let x = i.title;
|
|
|
|
if (i.company) {
|
|
x = `${x} @ ${i.company}`;
|
|
}
|
|
|
|
return x.match(f);
|
|
} catch {
|
|
return false;
|
|
}
|
|
})
|
|
);
|
|
</script>
|
|
|
|
<dialog class="card max-w-[50vw]" bind:this={dialogElement}>
|
|
<div class="flex sticky top-0 bg-white z-50 p-2 shadow-lg rounded-lg gap-2 flex-col">
|
|
<div class="flex items-center gap-2">
|
|
<input placeholder="Filter" class="p-2 flex-grow" bind:value={filter} />
|
|
<button
|
|
onclick={() => {
|
|
advFilters = !advFilters;
|
|
}}
|
|
>
|
|
<span class="bi bi-filter"></span>
|
|
</button>
|
|
<div>
|
|
{internal.length}
|
|
</div>
|
|
</div>
|
|
{#if advFilters}
|
|
<div class="flex gap-2">
|
|
<InplaceDialog
|
|
buttonClass="border-slate-300 border border-solid color-slate-300 p-1 rounded-md bg-slate-100/50"
|
|
dialogClass="w-[200px]"
|
|
>
|
|
{#snippet buttonChildren()}
|
|
<i class="bi bi-plus"></i> Status
|
|
{/snippet}
|
|
<div class="flex flex-wrap gap-2">
|
|
{#each statusStore.nodes.filter((a) => !filterStatus.includes(a.id)) as node}
|
|
<button
|
|
class="border-violet-300 border border-solid color-violet-300 bg-violet-100/50 p-1 rounded-md text-violet-800"
|
|
onclick={() => {
|
|
//filterStatus.push(node.id);
|
|
//filterStatus = filterStatus;
|
|
filterStatus = [...filterStatus, node.id];
|
|
}}
|
|
>
|
|
{node.name}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</InplaceDialog>
|
|
<InplaceDialog
|
|
buttonClass="border-slate-300 border border-solid color-slate-300 p-1 rounded-md bg-slate-100/50"
|
|
dialogClass="w-[200px]"
|
|
>
|
|
{#snippet buttonChildren()}
|
|
<i class="bi bi-plus"></i> Job Level
|
|
{/snippet}
|
|
<div class="flex flex-wrap gap-2">
|
|
{#each JobLevels.filter((a) => !jobLevels.includes(a)) as jobLevel}
|
|
<button
|
|
class="border-red-300 border border-solid color-red-300 bg-red-100/50 p-1 rounded-md text-red-800"
|
|
onclick={() => {
|
|
jobLevels = [...jobLevels, jobLevel];
|
|
}}
|
|
>
|
|
{jobLevel}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</InplaceDialog>
|
|
</div>
|
|
<h2>Filters</h2>
|
|
<div class="flex gap-2">
|
|
{#each statusStore.nodes.filter((a) => filterStatus.includes(a.id)) as node}
|
|
<button
|
|
class="border-violet-300 border border-solid color-violet-300 bg-violet-100/50 text-violet-800 p-1 rounded-md"
|
|
onclick={() => {
|
|
filterStatus = filterStatus.filter((a) => a != node.id);
|
|
}}
|
|
>
|
|
{node.name}
|
|
</button>
|
|
{/each}
|
|
{#each jobLevels.filter((a) => jobLevels.includes(a)) as jobLevel}
|
|
<button
|
|
class="border-red-300 border border-solid color-red-300 bg-red-100/50 p-1 rounded-md text-red-800"
|
|
onclick={() => {
|
|
jobLevels = jobLevels.filter((a) => a != jobLevel);
|
|
}}
|
|
>
|
|
{jobLevel}
|
|
</button>
|
|
{/each}
|
|
{#each extraFiltersToDisplay as filter}
|
|
{#if filter.type === 'name'}
|
|
<span
|
|
class="border-blue-300 border border-solid color-blue-300 bg-blue-100/50 p-1 rounded-md"
|
|
>
|
|
Name ~ /<span class="text-blue-800 text-bold">{filter.text}</span>/
|
|
</span>
|
|
{:else if filter.type === 'company'}
|
|
<span
|
|
class="border-green-300 border border-solid color-green-300 bg-green-100/50 p-1 rounded-md"
|
|
>
|
|
Company ~ /<span class="text-green-800 text-bold">{filter.text}</span>/
|
|
</span>
|
|
{:else if filter.type === 'query'}
|
|
<span
|
|
class="border-orange-300 border border-solid color-orange-300 bg-orange-100/50 p-1 rounded-md"
|
|
>
|
|
Query ~ /<span class="text-orange-800 text-bold">{filter.text}</span>/
|
|
</span>
|
|
{/if}
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
<div class="overflow-y-auto overflow-x-hidden flex-grow p-2">
|
|
<!-- TODO loading screen -->
|
|
{#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={() => {
|
|
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>
|
|
</h2>
|
|
<span>
|
|
{statusStore.nodesR[item.status_id]?.name ?? 'NOT FOUND'}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</dialog>
|