feat: base level auto skip for new on expeired

This commit is contained in:
2025-06-11 22:56:57 +01:00
parent 842fb23275
commit e591af4d73
11 changed files with 572 additions and 182 deletions

View File

@@ -4,6 +4,8 @@
import ApplicationsList from './ApplicationsList.svelte';
import WorkArea from './work-area/WorkArea.svelte';
import ApplicationTypesList from './ApplicationTypesList.svelte';
let appList: ApplicationsList;
</script>
<HasUser redirect="/cv">
@@ -11,8 +13,8 @@
<NavBar />
<div class="w-full px-4 grow h-full gap-3 flex flex-col">
<div class="flex gap-3 flex-grow">
<ApplicationsList />
<WorkArea />
<ApplicationsList bind:this={appList} />
<WorkArea {appList} />
</div>
<ApplicationTypesList />
<div class="p-3"></div>

View File

@@ -49,9 +49,13 @@
}
}
export function requestNext() {
applicationStore.loadItem = internal[0];
}
function docKey(e: KeyboardEvent) {
if (e.ctrlKey && e.code === 'KeyJ' && internal.length > 0) {
applicationStore.loadItem = internal[0];
requestNext();
e.stopPropagation();
e.preventDefault();
return;
@@ -94,10 +98,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

@@ -3,9 +3,10 @@
import { statusStore } from '$lib/Types.svelte';
import { onMount } from 'svelte';
import DropZone from './DropZone.svelte';
import { deleteR, put } from '$lib/utils';
import { put } from '$lib/utils';
let { index = $bindable() }: { index: number | undefined } = $props();
let { index = $bindable(), onremove }: { index: number | undefined; onremove: () => void } =
$props();
const derivedItem: Application | undefined = $derived(applicationStore.all[index ?? -1]);
@@ -31,35 +32,19 @@
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"
>
<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>
<DropZone icon="trash-fill text-danger" ondrop={() => onremove()}>Delete it</DropZone>
</div>
{/if}

View File

@@ -0,0 +1,114 @@
<script lang="ts">
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
import { statusStore } from '$lib/Types.svelte';
import { put } from '$lib/utils';
let {
index = $bindable(),
onnext
}: {
index: number | undefined;
onnext: () => void;
} = $props();
let dialog: HTMLDialogElement;
const derivedItem: Application | undefined = $derived(applicationStore.all[index ?? -1]);
async function moveStatus(status_id: string) {
// 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();
dialog.close();
onnext();
return;
}
let status: string | null = $state(null);
let timeoutCount: number = $state(5);
let timeout: undefined | number = undefined;
$effect(() => {
status = localStorage.getItem('expireStatus');
return () => {
if (timeout) clearTimeout(timeout);
};
});
export function open() {
if (dialog) dialog.showModal();
if (status) {
timeoutCount = 5;
function timeoutF() {
if (timeoutCount === 0) {
moveStatus(status!);
timeout = undefined;
return;
}
timeoutCount -= 1;
timeout = setTimeout(timeoutF, 1000) as unknown as number;
}
timeout = setTimeout(timeoutF, 1000) as unknown as number;
}
}
$effect(() => {
localStorage.setItem('expireStatus', status ?? '');
});
// TODO: Auto delete after of 5 secs of inactivity
</script>
<dialog
class="card"
bind:this={dialog}
onclose={() => {
if (timeout) clearTimeout(timeout);
}}
>
<div>
<div class="text-center py-10">
<span class="bi bi-exclamation-triangle-fill text-8xl text-orange-400"></span>
</div>
<h1 class="text-4xl py-5 text-center">Found Expired</h1>
<div class="flex justify-center gap-10">
<button
class="btn-primary"
onclick={() => {
if (timeout) clearTimeout(timeout);
dialog.close();
}}
>
Ignore
</button>
<select bind:value={status} class="py-5">
{#each statusStore.nodes as node}
<option value={node.id}>
{node.name}
</option>
{/each}
</select>
<button
class="btn-danger"
onclick={() => {
if (status) moveStatus(status);
}}
>
Move ({timeoutCount}) <span class="bi bi-clock-history animate-pulse"></span>
</button>
</div>
</div>
</dialog>

View File

@@ -43,7 +43,7 @@
$effect(() => {
function onMessage(e: MessageEvent) {
if (e.data.type === 'MY_GET_URL_R') {
if (dialog.open && e.data.type === 'MY_GET_URL_R') {
if (e.data.error) {
if (e.data.data.length === 0) {
console.log('TODO inform user to mark page');
@@ -52,6 +52,7 @@
}
return;
}
if (!e.data.url) return;
data.url = e.data.url ?? '';
window.requestAnimationFrame(() => {
if (!form?.reportValidity()) {

View File

@@ -0,0 +1,59 @@
<script lang="ts">
let {
ondelete
}: {
ondelete: () => void;
} = $props();
let timeoutCount = $state(5);
let timeout: undefined | number = undefined;
let dialog: HTMLDialogElement;
export function open() {
if (dialog) dialog.showModal();
timeoutCount = 5;
function timeoutF() {
if (timeoutCount === 0) {
ondelete();
timeout = undefined;
return;
}
timeoutCount -= 1;
timeout = setTimeout(timeoutF, 1000) as unknown as number;
}
timeout = setTimeout(timeoutF, 1000) as unknown as number;
}
// TODO: Auto delete after of 5 secs of inactivity
</script>
<dialog
class="card"
bind:this={dialog}
onclose={() => {
if (timeout) clearTimeout(timeout);
}}
>
<div>
<div class="text-center py-10">
<span class="bi bi-exclamation-triangle-fill text-8xl text-red-500"></span>
</div>
<h1 class="text-4xl py-5">Found Glassdoor Search</h1>
<div class="flex justify-center gap-10">
<button
class="btn-primary"
onclick={() => {
dialog.close();
if (timeout) clearTimeout(timeout);
}}
>
Ignore
</button>
<button class="btn-danger" onclick={() => ondelete()}>
Remove ({timeoutCount}) <span class="bi bi-clock-history animate-pulse"></span>
</button>
</div>
</div>
</dialog>

View File

@@ -6,7 +6,7 @@
} from '$lib/ApplicationsStore.svelte';
import { statusStore } from '$lib/Types.svelte';
let { application, showAll }: { application: Application; showAll: boolean } = $props();
let { application }: { application: Application } = $props();
let events: (ApplicationEvent & { timeDiff: string })[] = $state([]);
@@ -91,14 +91,10 @@
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

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { applicationStore, type Application } from '$lib/ApplicationsStore.svelte';
import { put, preventDefault, post, get } from '$lib/utils';
import { put, preventDefault, post, get, deleteR } from '$lib/utils';
import { onMount } from 'svelte';
import ExtractTextDialog from './ExtractTextDialog.svelte';
import Flair from '../flair/Flair.svelte';
@@ -14,6 +14,11 @@
import CompanyField from './CompanyField.svelte';
import AutoDropZone from './AutoDropZone.svelte';
import { statusStore } from '$lib/Types.svelte';
import SearchFound from './SearchFound.svelte';
import FoundExpire from './FoundExpire.svelte';
import ApplicationsList from '../ApplicationsList.svelte';
let { appList }: { appList: ApplicationsList } = $props();
// Not this represents the index in the store array
let activeItem: number | undefined = $state();
@@ -23,6 +28,8 @@
let extractTokens: HTMLDialogElement;
let changeUrl: HTMLDialogElement;
let linkApplication: HTMLDialogElement;
let searchFound: SearchFound;
let foundExpire: FoundExpire;
let lastExtData: any = $state(undefined);
let autoExtData = false;
@@ -60,12 +67,17 @@
lastExtData = undefined;
}
let win: undefined | Window | null | true = $state(undefined);
function openWindow(url: string) {
if (!url.startsWith('https://')) {
url = `https://${url}`;
}
window.open(
if (win && hasExt) {
window.postMessage({ type: 'CHANGE_PAGE', new_url: url });
return;
}
win = window.open(
url,
'new window',
`location,height=${window.screen.height},width=${window.screen.width},scrollbars,status,toolbar,menubar,popup`
@@ -101,21 +113,44 @@
//
//
let hasExt = $state(false);
// Load Ext
$effect(() => {
function onMessage(e: MessageEvent) {
if (e.data.type === 'GOT_INFO_R') {
lastExtData = e.data;
if (autoExtData) {
if ((autoExtData || derivedItem?.title === 'New Application') && derivedItem?.simple_url) {
if (e.data.status === 'expired' && derivedItem?.status_id === null) {
foundExpire.open();
}
window.requestAnimationFrame(() => {
setExtData();
});
}
}
if (e.data.type === 'AUTOLOAD_FOUND_GLASSDOOR_SEARCH') {
console.log('Found glassdoor search');
if (changeUrl.open) {
changeUrl.close();
searchFound.open();
}
return;
}
if (e.data.type === 'GOT_INTEREST') {
console.log('Has for ext and has id!');
hasExt = true;
win = true;
}
if (e.data.type === 'HAS_EXTENSION') {
console.log('Has for ext!');
hasExt = true;
}
}
window.addEventListener('message', onMessage);
console.log('Sending request for ext!');
window.postMessage({ type: 'REGISTER_INTEREST' });
window.postMessage({ type: 'HAS_EXTENSION_Q' });
return () => {
window.removeEventListener('message', onMessage);
};
@@ -139,7 +174,14 @@
applicationStore.all[activeItem].inperson_type = (
lastExtData.inperson_type as string
).toLowerCase();
applicationStore.all[activeItem].location = (lastExtData.location as string).split(',')[0];
if (lastExtData.location && lastExtData.location.includes(',')) {
applicationStore.all[activeItem].location = (lastExtData.location as string).split(',')[0];
} else if (lastExtData.location && lastExtData.location.includes('·')) {
applicationStore.all[activeItem].location = (lastExtData.location as string)
.split('·')[0]
.trim();
}
console.log(lastExtData);
@@ -213,6 +255,23 @@
console.log('info data', e);
}
}
async function remove(autoNext = false) {
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);
activeItem = undefined;
if (autoNext) {
appList.requestNext();
}
}
</script>
<div class="flex flex-col w-full gap-2 min-w-0 relative" role="none">
@@ -309,8 +368,7 @@
payrange[0] = payrange[0] * 8 * 5 * 4 * 12;
if (payrange[1] != undefined) {
payrange[1] = payrange[1] * 8 * 5 * 4 * 12;
applicationStore.all[activeItem!].payrange =
`${payrange[0]}-${payrange[1]}`;
applicationStore.all[activeItem!].payrange = `${payrange[0]}-${payrange[1]}`;
window.requestAnimationFrame(() => save());
return;
@@ -404,8 +462,7 @@
{item}
allowDelete
application={derivedItem}
updateApplication={(item) =>
(applicationStore.all[activeItem ?? -1] = item)}
updateApplication={(item) => (applicationStore.all[activeItem ?? -1] = item)}
/>
{/each}
</div>
@@ -466,11 +523,7 @@
<button class="btn-primary" onclick={() => linkApplication.showModal()}>
Link Application
</button>
<button
class:btn-primary={drag}
class:btn-danger={!drag}
onclick={() => (drag = !drag)}
>
<button class:btn-primary={drag} class:btn-danger={!drag} onclick={() => (drag = !drag)}>
👋
</button>
<button
@@ -483,7 +536,7 @@
</div>
<Timeline application={derivedItem} showAll={showExtraData} />
</div>
<AutoDropZone bind:index={activeItem} />
<AutoDropZone bind:index={activeItem} onremove={remove} />
{:else}
<div
class="p-2 h-full w-full gap-2 flex-grow card grid place-items-center min-h-[50vh]"
@@ -523,14 +576,19 @@
activate(item, false);
}}
/>
{/if}
{#if derivedItem}
<LinkApplication
application={derivedItem}
bind:dialog={linkApplication}
onreload={(item) => activate(item, false)}
/>
<FoundExpire
bind:this={foundExpire}
bind:index={activeItem}
onnext={() => {
appList.requestNext();
}}
/>
<SearchFound bind:this={searchFound} ondelete={() => remove(true)} />
{/if}
<SearchApplication