Integrate local business workflow and SaaS redesign
This commit is contained in:
@@ -4,7 +4,16 @@ import { useMemo, useState } from "react";
|
||||
|
||||
import { useAction, useMutation, useQuery } from "convex/react";
|
||||
import type { FunctionReturnType } from "convex/server";
|
||||
import { ChevronDown, ChevronRight, ExternalLink, MailCheck, Save } from "lucide-react";
|
||||
import {
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
ExternalLink,
|
||||
FileSearch,
|
||||
MailCheck,
|
||||
Save,
|
||||
ShieldCheck,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { api } from "@/convex/_generated/api";
|
||||
@@ -176,9 +185,9 @@ function FieldPair({ label, value }: { label: string; value?: string | null }) {
|
||||
function WorkspaceLoading() {
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<header className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">Interne Outreach-Prüfung</p>
|
||||
<h1 className="text-2xl font-semibold tracking-normal">Review Workspace</h1>
|
||||
<header className="agency-panel space-y-2 p-4">
|
||||
<p className="agency-kicker">Approval Bench</p>
|
||||
<h1 className="font-heading text-2xl font-semibold tracking-normal">Review Workspace</h1>
|
||||
</header>
|
||||
<div className="space-y-3">
|
||||
{Array.from({ length: 3 }, (_, index) => (
|
||||
@@ -244,11 +253,11 @@ export function OutreachReviewWorkspace() {
|
||||
if (rows.length === 0) {
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<header className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">Interne Outreach-Prüfung</p>
|
||||
<h1 className="text-2xl font-semibold tracking-normal">Review Workspace</h1>
|
||||
<header className="agency-panel space-y-2 p-4">
|
||||
<p className="agency-kicker">Approval Bench</p>
|
||||
<h1 className="font-heading text-2xl font-semibold tracking-normal">Review Workspace</h1>
|
||||
</header>
|
||||
<Card>
|
||||
<Card className="agency-panel">
|
||||
<CardContent className="p-4">
|
||||
<p className="text-sm font-medium">Keine offenen Reviews</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
@@ -482,9 +491,9 @@ export function OutreachReviewWorkspace() {
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<header className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">Interne Outreach-Prüfung</p>
|
||||
<h1 className="text-2xl font-semibold tracking-normal">Review Workspace</h1>
|
||||
<header className="agency-panel space-y-2 p-4">
|
||||
<p className="agency-kicker">Approval Bench</p>
|
||||
<h1 className="font-heading text-2xl font-semibold tracking-normal">Review Workspace</h1>
|
||||
<p className="max-w-3xl text-sm text-muted-foreground">
|
||||
Audits, E-Mail-Empfehlung und Telefonnotizen prüfen, bevor etwas öffentlich
|
||||
wird oder eine Freigabe erhält.
|
||||
@@ -492,7 +501,7 @@ export function OutreachReviewWorkspace() {
|
||||
</header>
|
||||
|
||||
{notice ? (
|
||||
<p className="rounded-md border bg-muted/30 px-3 py-2 text-sm" role="status">{notice}</p>
|
||||
<p className="rounded-md border border-border/75 bg-muted/30 px-3 py-2 text-sm" role="status">{notice}</p>
|
||||
) : null}
|
||||
|
||||
<Dialog
|
||||
@@ -560,7 +569,7 @@ export function OutreachReviewWorkspace() {
|
||||
size="sm"
|
||||
type="button"
|
||||
>
|
||||
Senden
|
||||
Final senden
|
||||
</Button>
|
||||
<Button onClick={closeEmailConfirmation} size="sm" type="button" variant="outline">
|
||||
Abbrechen
|
||||
@@ -570,14 +579,15 @@ export function OutreachReviewWorkspace() {
|
||||
) : null}
|
||||
</Dialog>
|
||||
|
||||
<section className="space-y-3" aria-label="Review-Queue">
|
||||
<div className="grid gap-4 xl:grid-cols-[minmax(18rem,0.78fr)_minmax(0,1.22fr)]">
|
||||
<section className="agency-panel space-y-3 p-3" aria-label="Review-Queue">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<h2 className="text-sm font-semibold">Review-Queue</h2>
|
||||
<div className="flex flex-wrap gap-2" aria-label="Review-Filter">
|
||||
{reviewStatusFilters.map((filter) => (
|
||||
<button
|
||||
aria-pressed={activeFilter === filter.value}
|
||||
className="inline-flex min-h-8 items-center gap-2 rounded-md border px-3 py-1 text-sm text-muted-foreground transition-colors hover:bg-muted aria-pressed:border-foreground aria-pressed:text-foreground"
|
||||
className="agency-tab"
|
||||
key={filter.value}
|
||||
onClick={() => setActiveFilter(filter.value)}
|
||||
type="button"
|
||||
@@ -589,7 +599,7 @@ export function OutreachReviewWorkspace() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 lg:grid-cols-2 xl:grid-cols-3">
|
||||
<div className="grid gap-3">
|
||||
{filteredRows.map((record) => {
|
||||
const lead = record.lead;
|
||||
const audit = record.audit;
|
||||
@@ -602,8 +612,10 @@ export function OutreachReviewWorkspace() {
|
||||
<Card
|
||||
aria-labelledby={queueTitleId}
|
||||
className={cn(
|
||||
"flex min-w-0 flex-col",
|
||||
selectedRecord?.id === record.id ? "border-foreground" : "",
|
||||
"flex min-w-0 flex-col overflow-hidden",
|
||||
selectedRecord?.id === record.id
|
||||
? "border-primary bg-[var(--surface-evidence)]"
|
||||
: "bg-background/60",
|
||||
)}
|
||||
key={record.id}
|
||||
>
|
||||
@@ -656,7 +668,7 @@ export function OutreachReviewWorkspace() {
|
||||
);
|
||||
})}
|
||||
{filteredRows.length === 0 ? (
|
||||
<Card className="lg:col-span-2 xl:col-span-3">
|
||||
<Card className="agency-panel">
|
||||
<CardHeader>
|
||||
<CardTitle>Keine Treffer</CardTitle>
|
||||
<CardDescription>
|
||||
@@ -695,8 +707,8 @@ export function OutreachReviewWorkspace() {
|
||||
const publicAuditHref = audit?.slug ? `/audit/${audit.slug}` : null;
|
||||
|
||||
return (
|
||||
<Card className="overflow-hidden" key={record.id}>
|
||||
<CardHeader className="gap-3 border-b bg-muted/20 p-4">
|
||||
<Card className="agency-panel overflow-hidden" key={record.id}>
|
||||
<CardHeader className="gap-4 border-b bg-muted/20 p-4">
|
||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div className="min-w-0 space-y-1">
|
||||
<CardTitle className="break-words text-lg">
|
||||
@@ -719,11 +731,49 @@ export function OutreachReviewWorkspace() {
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2 sm:grid-cols-4">
|
||||
<div className="evidence-surface rounded-md px-3 py-2">
|
||||
<span className="inline-flex items-center gap-2 text-xs font-bold uppercase text-muted-foreground">
|
||||
<FileSearch className="size-3.5" />
|
||||
Evidence
|
||||
</span>
|
||||
<p className="mt-1 text-sm font-semibold">
|
||||
{audit ? "Audit vorhanden" : "Audit offen"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="review-surface rounded-md px-3 py-2">
|
||||
<span className="inline-flex items-center gap-2 text-xs font-bold uppercase text-muted-foreground">
|
||||
<ShieldCheck className="size-3.5" />
|
||||
Public Audit
|
||||
</span>
|
||||
<p className="mt-1 text-sm font-semibold">
|
||||
{audit?.status === "published" ? "Veröffentlicht" : "Prüfung offen"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="safe-surface rounded-md px-3 py-2">
|
||||
<span className="inline-flex items-center gap-2 text-xs font-bold uppercase text-muted-foreground">
|
||||
<MailCheck className="size-3.5" />
|
||||
E-Mail
|
||||
</span>
|
||||
<p className="mt-1 text-sm font-semibold">
|
||||
{isEmailDraftReady(record) ? "Bereit" : "Entwurf offen"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-md border border-border/75 bg-background/70 px-3 py-2">
|
||||
<span className="inline-flex items-center gap-2 text-xs font-bold uppercase text-muted-foreground">
|
||||
<CheckCircle2 className="size-3.5 text-primary" />
|
||||
Final Send
|
||||
</span>
|
||||
<p className="mt-1 text-sm font-semibold">
|
||||
{isQueuedSend ? "Wird gesendet" : "Bestätigung nötig"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-5 p-4">
|
||||
<section className="grid gap-4 lg:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-3 rounded-md border border-border/75 bg-background/60 p-3">
|
||||
<h2 className="text-sm font-semibold">Lead-Details</h2>
|
||||
<dl className="grid gap-3 sm:grid-cols-2">
|
||||
<FieldPair label="Nische" value={lead?.niche} />
|
||||
@@ -752,7 +802,7 @@ export function OutreachReviewWorkspace() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-3 rounded-md border border-border/75 bg-background/60 p-3">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 className="text-sm font-semibold">Audit-Zusammenfassung</h2>
|
||||
{publicAuditHref ? (
|
||||
@@ -828,7 +878,7 @@ export function OutreachReviewWorkspace() {
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-3 rounded-md border border-border/75 bg-background/60 p-3">
|
||||
<h2 className="text-sm font-semibold">Empfohlene E-Mail</h2>
|
||||
<label className="block space-y-1">
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
@@ -877,12 +927,12 @@ export function OutreachReviewWorkspace() {
|
||||
type="button"
|
||||
>
|
||||
<MailCheck className="size-3.5" />
|
||||
E-Mail freigeben und senden
|
||||
E-Mail freigeben
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-3 rounded-md border border-border/75 bg-background/60 p-3">
|
||||
<h2 className="text-sm font-semibold">Telefon & Follow-up</h2>
|
||||
{hasCallablePhone ? (
|
||||
<label className="block space-y-1">
|
||||
@@ -997,6 +1047,7 @@ export function OutreachReviewWorkspace() {
|
||||
);
|
||||
})() : null}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user