Public Access
1
0

chore: reorganize frontend into app and admin roots

This commit is contained in:
pguerrerox
2026-05-30 00:45:06 +00:00
parent d71f2f1f8a
commit a926d06b54
39 changed files with 76 additions and 49 deletions
+243
View File
@@ -0,0 +1,243 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Crosshair, Loader2, MapPinned, Sparkles } from 'lucide-react';
import type { DeepResearchPreview } from '@/shared/types';
import { createDeepResearchBatch, previewDeepResearch } from '../lib/database';
import { DeepResearchPreviewMap } from './DeepResearchPreviewMap';
import { Alert, Badge, Button, FieldLabel, Input, MetricPill, PageContainer, PageShell, SectionHeader, Surface } from './ui';
interface DeepResearchViewProps {
onShowBatchOnMap: (jobIds: string[]) => void;
topContent?: React.ReactNode;
}
export function DeepResearchView({ onShowBatchOnMap, topContent }: DeepResearchViewProps) {
const [pin, setPin] = useState<google.maps.LatLngLiteral | null>(null);
const [businessType, setBusinessType] = useState('');
const [keywords, setKeywords] = useState('');
const [propagation, setPropagation] = useState(1);
const [preview, setPreview] = useState<DeepResearchPreview | null>(null);
const [previewError, setPreviewError] = useState<string | null>(null);
const [isPreviewing, setIsPreviewing] = useState(false);
const [isRunning, setIsRunning] = useState(false);
useEffect(() => {
setPreview(null);
setPreviewError(null);
}, [businessType, keywords, pin?.lat, pin?.lng, propagation]);
const canPreview = Boolean(pin && businessType.trim());
const previewSummary = useMemo(() => {
if (!preview) {
return null;
}
return `${preview.totalAreas} postal areas across ${preview.countryCode} with ${preview.estimatedChildJobs} child researches.`;
}, [preview]);
const handlePreview = async () => {
if (!pin || !businessType.trim()) {
setPreviewError('Drop a pin and add a business type before previewing deep research.');
return;
}
setIsPreviewing(true);
setPreviewError(null);
try {
const nextPreview = await previewDeepResearch({
lat: pin.lat,
lng: pin.lng,
propagation,
businessType: businessType.trim(),
keywords: keywords.trim() || undefined,
});
setPreview(nextPreview);
} catch (err) {
setPreviewError(err instanceof Error ? err.message : 'Failed to preview deep research.');
} finally {
setIsPreviewing(false);
}
};
const handleRunDeepResearch = async () => {
if (!pin || !businessType.trim()) {
setPreviewError('Drop a pin and add a business type before running deep research.');
return;
}
setIsRunning(true);
setPreviewError(null);
try {
const batch = await createDeepResearchBatch({
lat: pin.lat,
lng: pin.lng,
propagation,
businessType: businessType.trim(),
keywords: keywords.trim() || undefined,
});
if (batch.jobIds.length > 0) {
onShowBatchOnMap(batch.jobIds);
}
} catch (err) {
setPreviewError(err instanceof Error ? err.message : 'Failed to run deep research.');
} finally {
setIsRunning(false);
}
};
return (
<PageShell>
<PageContainer>
{topContent}
<SectionHeader
title="Deep Research"
description="Drop a pin, choose a propagation depth, preview the ZIP or FSA areas that will be covered, and run one bundled research batch across every adjacent postal area."
/>
<section className="grid grid-cols-1 items-stretch gap-6 xl:grid-cols-[400px_minmax(0,1fr)]">
<Surface className="p-5 sm:p-7">
<div className="space-y-5">
<div className="space-y-2">
<FieldLabel>Business Type</FieldLabel>
<Input
type="text"
value={businessType}
onChange={(event) => setBusinessType(event.target.value)}
placeholder="e.g. dentists, HVAC, bakeries"
/>
</div>
<div className="space-y-2">
<FieldLabel>Keywords</FieldLabel>
<Input
type="text"
value={keywords}
onChange={(event) => setKeywords(event.target.value)}
placeholder="Optional comma-separated keywords"
/>
</div>
<div className="space-y-2.5">
<FieldLabel>Location</FieldLabel>
<div className="rounded-2xl border border-stone-200 bg-stone-50 p-4 text-sm text-stone-600">
Click directly on the map to place the deep research center. The preview uses that active pin to find the base ZIP or FSA and expand outward.
</div>
</div>
{pin && (
<Alert variant="success" title="Active research center">
<p>{pin.lat.toFixed(5)}, {pin.lng.toFixed(5)}</p>
</Alert>
)}
<div className="space-y-2">
<FieldLabel>Propagation</FieldLabel>
<input
type="range"
min="0"
max="5"
value={propagation}
onChange={(event) => setPropagation(Number.parseInt(event.target.value, 10) || 0)}
className="w-full accent-emerald-600"
/>
<div className="mt-2 flex items-center justify-between text-xs text-stone-500">
<span>Base postal area only</span>
<Badge variant="primary">{propagation} hop{propagation === 1 ? '' : 's'}</Badge>
<span>Expand outward</span>
</div>
</div>
<div className="rounded-2xl border border-stone-200 bg-stone-50 p-4 text-sm text-stone-600">
<div className="flex items-center gap-2 font-semibold text-stone-900">
<Crosshair className="h-4 w-4 text-emerald-600" />
Pin placement
</div>
<p className="mt-2">Click the map to drop a pin. The preview will find the containing ZIP or FSA and expand to adjacent postal areas based on the propagation depth.</p>
{pin && <p className="mt-3 font-medium text-stone-800">Pin: {pin.lat.toFixed(5)}, {pin.lng.toFixed(5)}</p>}
</div>
{previewError && (
<Alert variant="error">{previewError}</Alert>
)}
{previewSummary && (
<Alert variant="success" title="Preview ready">
<p>{previewSummary}</p>
<p className="mt-2 text-emerald-800">
Base area: <span className="font-semibold">{preview?.baseArea.displayName}</span>
</p>
</Alert>
)}
<div className="flex flex-col gap-3 sm:flex-row">
<Button
type="button"
onClick={() => void handlePreview()}
disabled={!canPreview || isPreviewing || isRunning}
variant="secondary"
className="flex-1"
>
{isPreviewing ? <Loader2 className="h-4 w-4 animate-spin" /> : <MapPinned className="h-4 w-4" />}
Preview areas
</Button>
<Button
type="button"
onClick={() => void handleRunDeepResearch()}
disabled={!canPreview || isPreviewing || isRunning}
className="flex-1"
>
{isRunning ? <Loader2 className="h-4 w-4 animate-spin" /> : <Sparkles className="h-4 w-4" />}
Run deep research
</Button>
</div>
</div>
</Surface>
<div className="flex h-full flex-col gap-4">
<DeepResearchPreviewMap pin={pin} preview={preview} onPinChange={setPin} />
<Surface className="p-5">
<div className="flex items-center gap-2 text-sm font-semibold uppercase tracking-[0.18em] text-stone-500">
<MapPinned className="h-4 w-4" />
Map Controls
</div>
<h3 className="mt-3 text-lg font-semibold text-stone-950">Preview center and coverage</h3>
<p className="mt-2 text-sm text-stone-600">
Drop a pin directly on the map to define the base postal area. Preview colors and rings show how propagation expands the deep research batch into adjacent areas.
</p>
</Surface>
{preview && (
<Surface className="p-6">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h3 className="text-lg font-semibold text-stone-950">Preview coverage</h3>
<p className="mt-1 text-sm text-stone-600">These postal areas will become child researches in the batch.</p>
</div>
<MetricPill className="bg-stone-50 text-stone-700 shadow-none">
{preview.totalAreas} areas
</MetricPill>
</div>
<div className="mt-4 flex flex-wrap gap-2">
{preview.areas.map((area) => (
<Badge
key={area.id}
variant={area.propagationRing === 0 ? 'primary' : 'neutral'}
>
{area.displayName} · ring {area.propagationRing}
</Badge>
))}
</div>
</Surface>
)}
</div>
</section>
</PageContainer>
</PageShell>
);
}