basic job file function
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
import { createSignal, createResource, Show, For, onMount } from "solid-js";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { store, activeNoteId } from "@/store";
|
||||
import { Search, File as FileIcon, Loader2, FolderOpen } from "lucide-solid";
|
||||
|
||||
interface JobFileSelectorProps {
|
||||
onSelectFile: (jobId: string, filename: string) => void;
|
||||
}
|
||||
|
||||
export const JobFileSelector = (props: JobFileSelectorProps) => {
|
||||
const [query, setQuery] = createSignal("");
|
||||
const [selectedJobId, setSelectedJobId] = createSignal<string | null>(null);
|
||||
|
||||
// Auto-detect job ID from active note tags
|
||||
onMount(() => {
|
||||
const currentNoteId = activeNoteId();
|
||||
if (currentNoteId) {
|
||||
const currentNote = store.notes.find(n => n.id === currentNoteId);
|
||||
if (currentNote) {
|
||||
const jobIdTag = currentNote.tags.find(t => t.startsWith("job_id:"));
|
||||
if (jobIdTag) {
|
||||
const id = jobIdTag.split(":")[1];
|
||||
if (id) {
|
||||
setSelectedJobId(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Query Jobs
|
||||
const [jobs] = createResource(query, async (q) => {
|
||||
if (!q.trim()) return [];
|
||||
try {
|
||||
const res = await pb.collection('Job_Info_Prod').getList(1, 10, {
|
||||
filter: `Job_Number ~ "${q}" || Job_Name ~ "${q}"`,
|
||||
sort: '-created',
|
||||
requestKey: null // Allow concurrent overlapping requests without auto-cancelling if needed, or leave it
|
||||
});
|
||||
return res.items;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch details for the selected job to list files
|
||||
const [selectedJob] = createResource(selectedJobId, async (id) => {
|
||||
if (!id) return null;
|
||||
try {
|
||||
const data = await pb.collection('Job_Info_Prod').getOne(id);
|
||||
console.log("Job File Selector Selected Job Data:", data);
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error("Job File Selector Error Fetching Job:", e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const categories = ["plans", "EST_and_CO", "Addendums", "files"] as const;
|
||||
|
||||
return (
|
||||
<div class="w-full flex flex-col items-center justify-center p-8 gap-6 bg-muted/10 rounded-xl relative">
|
||||
<Show when={!selectedJobId()}>
|
||||
<div class="text-center w-full max-w-lg space-y-4 relative z-10">
|
||||
<div class="flex items-center justify-center w-12 h-12 rounded-full bg-primary/10 text-primary mx-auto mb-4">
|
||||
<FolderOpen size={24} />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<h3 class="font-semibold text-lg">Embed a Job File</h3>
|
||||
<p class="text-sm text-muted-foreground">Search for a Prism job to browse its files.</p>
|
||||
</div>
|
||||
|
||||
<div class="relative w-full">
|
||||
<Search class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-4 h-4" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by Job Name or Number..."
|
||||
class="w-full pl-9 pr-4 py-2 bg-background border border-border rounded-lg outline-none focus:ring-2 focus:ring-primary/50 transition-all text-sm"
|
||||
value={query()}
|
||||
onInput={(e) => setQuery(e.currentTarget.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Show when={jobs.loading}>
|
||||
<div class="flex items-center justify-center py-4 text-muted-foreground">
|
||||
<Loader2 class="w-5 h-5 animate-spin" />
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={jobs() && jobs()!.length > 0}>
|
||||
<div class="mt-4 border border-border bg-background rounded-lg shadow-sm overflow-hidden text-left flex flex-col">
|
||||
<For each={jobs()}>
|
||||
{(job) => (
|
||||
<button
|
||||
class="px-4 py-3 hover:bg-muted text-left flex flex-col gap-1 border-b border-border last:border-0 transition-colors cursor-pointer"
|
||||
onClick={() => setSelectedJobId(job.id)}
|
||||
>
|
||||
<span class="font-medium text-sm text-foreground">{job.Job_Name || "Unnamed Job"}</span>
|
||||
<span class="text-xs text-muted-foreground">Job #{job.Job_Number || job.id}</span>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={selectedJobId()}>
|
||||
<Show when={selectedJob.loading}>
|
||||
<div class="flex flex-col items-center justify-center gap-4 py-12 relative z-10">
|
||||
<Loader2 class="w-8 h-8 animate-spin text-primary" />
|
||||
<span class="text-sm text-muted-foreground font-medium">Loading Job Files...</span>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={selectedJob()}>
|
||||
{(jobRecord) => (
|
||||
<div class="w-full max-w-2xl bg-background rounded-xl border border-border shadow-sm overflow-hidden relative z-10">
|
||||
<div class="p-4 border-b border-border bg-muted/30 flex items-center justify-between">
|
||||
<div class="flex flex-col">
|
||||
<h3 class="font-semibold">{jobRecord().Job_Name || "Unnamed Job"}</h3>
|
||||
<span class="text-xs text-muted-foreground">Job #{jobRecord().Job_Number || jobRecord().id}</span>
|
||||
</div>
|
||||
<button
|
||||
class="text-xs text-blue-500 hover:text-blue-600 font-medium px-2 py-1 hover:bg-blue-50 rounded transition-colors cursor-pointer"
|
||||
onClick={() => setSelectedJobId(null)}
|
||||
>
|
||||
Change Job
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-4 flex flex-col gap-6 max-h-[400px] overflow-y-auto">
|
||||
<For each={categories}>
|
||||
{(category) => {
|
||||
let files = jobRecord()[category];
|
||||
if (files === null || files === undefined || files === "") return null;
|
||||
if (!Array.isArray(files)) files = [files];
|
||||
files = files.filter(Boolean);
|
||||
|
||||
if (files.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div class="space-y-2">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase tracking-wider">{category.replace(/_/g, " ")}</h4>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<For each={files as string[]}>
|
||||
{(filename) => (
|
||||
<button
|
||||
class="flex items-center gap-3 p-2 rounded-md border border-border hover:bg-muted hover:border-primary/50 transition-colors text-left cursor-pointer group"
|
||||
onClick={() => props.onSelectFile(jobRecord().id, filename)}
|
||||
>
|
||||
<div class="p-2 rounded bg-muted text-muted-foreground group-hover:text-primary transition-colors shrink-0">
|
||||
<FileIcon class="w-4 h-4" />
|
||||
</div>
|
||||
<span class="text-sm truncate font-medium text-foreground min-w-0" title={filename}>
|
||||
{filename}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
|
||||
{/* Empty state if no files at all */}
|
||||
<Show when={categories.every(c => {
|
||||
const val = jobRecord()[c];
|
||||
return !val || (Array.isArray(val) && val.length === 0) || (typeof val === 'string' && val.trim() === '');
|
||||
})}>
|
||||
<div class="text-center py-8 text-muted-foreground text-sm">
|
||||
No files found for this job.
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user