e70ae79d-5898-4c55-8ceb-d2c701ce08b4
This commit is contained in:
1 parent
d226dabba3
commit
5855f699f3
53 files changed
+1911
-44
No files matched your search
@@ -9,7 +9,9 @@ import { Button } from '@/components/ui/button'
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { PageHeader } from '../primitives'
|
||||
import { CreateDealDialog } from '../create-deal-dialog'
|
||||
import { formatEuro, formatDate } from '@/lib/format'
|
||||
import { useAos } from '@/store/aos'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface CrmData {
|
||||
@@ -32,7 +34,9 @@ const STAGE_COLORS: Record<string, string> = {
|
||||
}
|
||||
|
||||
export function CrmView() {
|
||||
const { data, loading } = useFetch<CrmData>('/api/aos/crm')
|
||||
const { data, loading, refetch } = useFetch<CrmData>('/api/aos/crm')
|
||||
const setSelectedCompany = useAos((s) => s.setSelectedCompany)
|
||||
const [createOpen, setCreateOpen] = useState(false)
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
@@ -56,9 +60,9 @@ export function CrmView() {
|
||||
title="CRM"
|
||||
description="Firmen, Ansprechpartner und Deal-Pipeline. Konfigurierbare Stages, DSGVO-Konsens-Protokoll pro Kontakt."
|
||||
actions={
|
||||
<Button size="sm">
|
||||
<Button size="sm" onClick={() => setCreateOpen(true)}>
|
||||
<Plus className="size-4" />
|
||||
Firma
|
||||
Deal
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
@@ -94,13 +98,20 @@ export function CrmView() {
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="companies" className="mt-4">
|
||||
<CompaniesTable companies={data.companies} />
|
||||
<CompaniesTable companies={data.companies} onSelectCompany={setSelectedCompany} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="persons" className="mt-4">
|
||||
<PersonsTable persons={data.persons} />
|
||||
<PersonsTable persons={data.persons} onSelectCompany={setSelectedCompany} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<CreateDealDialog
|
||||
open={createOpen}
|
||||
onOpenChange={setCreateOpen}
|
||||
companies={data.companies.map((c) => ({ id: c.id, name: c.name }))}
|
||||
onCreated={refetch}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -158,7 +169,7 @@ function PipelineBoard({ pipeline }: { pipeline: CrmData['pipeline'] }) {
|
||||
)
|
||||
}
|
||||
|
||||
function CompaniesTable({ companies }: { companies: CrmData['companies'] }) {
|
||||
function CompaniesTable({ companies, onSelectCompany }: { companies: CrmData['companies']; onSelectCompany: (id: string) => void }) {
|
||||
const [q, setQ] = useState('')
|
||||
const filtered = companies.filter(
|
||||
(c) => c.name.toLowerCase().includes(q.toLowerCase()) || (c.city ?? '').toLowerCase().includes(q.toLowerCase())
|
||||
@@ -190,14 +201,14 @@ function CompaniesTable({ companies }: { companies: CrmData['companies'] }) {
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{filtered.map((c) => (
|
||||
<tr key={c.id} className="transition hover:bg-muted/40">
|
||||
<tr key={c.id} className="cursor-pointer transition hover:bg-muted/40" onClick={() => onSelectCompany(c.id)}>
|
||||
<td className="px-4 py-2.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="size-3.5 text-muted-foreground" />
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{c.name}</span>
|
||||
{c.website && (
|
||||
<a href={`https://${c.website}`} target="_blank" rel="noopener noreferrer" className="flex items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground">
|
||||
<a href={`https://${c.website}`} target="_blank" rel="noopener noreferrer" onClick={(e) => e.stopPropagation()} className="flex items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground">
|
||||
<Globe className="size-3" />{c.website}
|
||||
</a>
|
||||
)}
|
||||
@@ -238,7 +249,7 @@ function CompaniesTable({ companies }: { companies: CrmData['companies'] }) {
|
||||
)
|
||||
}
|
||||
|
||||
function PersonsTable({ persons }: { persons: CrmData['persons'] }) {
|
||||
function PersonsTable({ persons, onSelectCompany }: { persons: CrmData['persons']; onSelectCompany: (id: string) => void }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
@@ -254,7 +265,7 @@ function PersonsTable({ persons }: { persons: CrmData['persons'] }) {
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{persons.map((p) => (
|
||||
<tr key={p.id} className="transition hover:bg-muted/40">
|
||||
<tr key={p.id} className="cursor-pointer transition hover:bg-muted/40" onClick={() => p.companyId && onSelectCompany(p.companyId)}>
|
||||
<td className="px-4 py-2.5 font-medium">{p.firstName} {p.lastName}</td>
|
||||
<td className="px-4 py-2.5 text-xs text-muted-foreground">{p.company?.name ?? '—'}</td>
|
||||
<td className="px-4 py-2.5 text-xs">{p.role ?? '—'}</td>
|
||||
|
||||
Reference in new issue
Block a user