Zum Inhalt springen
    WingoUI
    KomponentenVorlagenDokuPreiseKI-AgentenBlogChangelog
    1. Startseite
    2. Blog
    3. Praxis: Screens und Apps
    4. React CRM Template: Pipeline, Prognose, Firmenseiten
    Read in English
    1. Blog
    2. Praxis: Screens und Apps
    3. React CRM Template: Pipeline, Prognose, Firmenseiten
    Praxis: Screens und Apps
    Praxis: Screens und Apps

    React CRM Template: Pipeline, Prognose, Firmenseiten

    SR
    Serban Rusu · Gründer von Wingo UI
    16. Okt. 2026 · 11 Min. Lesezeit

    Auf dieser Seite

    0%
    1. Was gehört in ein React CRM Template?
    2. Solltest du ein CRM-Template kaufen oder aus Blöcken zusammensetzen?
    3. Wie strukturierst du ein Next.js CRM Template?
    4. Wie baust du die Seite für die Deal-Pipeline?
      1. Wie funktioniert die gewichtete Prognose?
    5. Wie baust du die Firmenseite?
    6. Wie funktioniert das CRM auf dem Smartphone?
    7. Kannst du das Deal Pipeline Kanban allein nutzen?
    8. Was baust du als Nächstes?
    9. Häufige Fragen
      1. Gibt es ein kostenloses React CRM Template?
      2. Wie berechnet man eine gewichtete Pipeline-Prognose?
      3. Woher sollen die Wahrscheinlichkeiten pro Phase kommen?
      4. Kann ich ein React CRM Template mit meinem eigenen Backend nutzen?
      5. Wie funktioniert ein Board für die Sales-Pipeline auf dem Smartphone?

    Kurz gesagt

    Ein React CRM Template braucht zuerst drei Screens: ein Board für die Deal-Pipeline mit gewichteter Prognose, ein Deal-Sheet zum Bearbeiten eines einzelnen Deals und eine Firmenseite mit Aktivitäten, Deals, Dateien und Notizen. In Next.js setzt du sie aus den Blöcken CRM Pipeline Page und CRM Contact Page von Wingo UI zusammen: Server-Seiten laden die Daten, eine kleine Client-View reicht Server Actions an den Block weiter, und jede Verschiebung wird optimistisch gespeichert, mit Rollback, falls das Speichern fehlschlägt. Auf dem Smartphone zeigt das Board jeweils eine Phase, und Sheets und Menüs öffnen sich von unten.

    Veröffentlicht am 16. Okt. 2026

    Du baust ein CRM-Frontend, für einen Kunden oder für dein eigenes Produkt, und suchst nach einem react crm template. Die Treffer sind Theme-Marktplätze mit Admin-Kits aus Dutzenden Seiten, Open-Source-CRMs mit eigenem Backend und ein paar Starter, die ein Kanban-Board mit Beispielzahlen zeigen. Was du brauchst, ist kleiner und schwieriger: eine Deal-Pipeline, deren Summen etwas bedeuten, eine Möglichkeit, einen Deal zu bearbeiten, ohne das Board zu verlassen, und eine Firmenseite, die die ganze Kundenbeziehung abbildet. Dieses Tutorial baut diese drei Screens in Next.js aus Blöcken von Wingo UI, verbunden mit Server Actions, und zeigt den fertigen Code.

    Was gehört in ein React CRM Template?

    Der größte Teil des Nutzens eines CRM steckt in drei Screens. Alles andere (Aufgaben, E-Mail-Sync, Reports) kann später kommen.

    1. Das Pipeline-Board. Deals als Karten in Spalten pro Phase, jede Spalte mit Anzahl, Summe und gewichteter Summe, darüber eine Zusammenfassung: offene Pipeline, gewichtete Prognose, diesen Monat gewonnen, Abschlussquote. Filter nach Verantwortlichen und Abschlussdatum. Eine Listenansicht für alle, die in Zeilen denken.
    2. Das Deal-Sheet. Ein Klick auf eine Karte öffnet ein Panel mit Wert, Phase, Abschlussdatum, Verantwortlichem, Kontakt, Notizen und dem Aktivitätsverlauf. Wer es mit ungespeicherten Änderungen schließt, wird vorher gefragt.
    3. Der Firmendatensatz. Eine Seite pro Kunde mit den wichtigsten Fakten, den Kontakten, den Deals, einer Aktivitäts-Timeline, Dateien und Notizen.

    Die Details entscheiden, ob das Vertriebsteam dem Tool vertraut. Wer einen Deal auf „Verloren“ ablegt, sollte nach dem Grund gefragt werden, denn ein Report über Verlustgründe ist wertlos, wenn die Hälfte der Gründe fehlt. Ein Deal, der wochenlang in einer Phase liegt, sollte veraltet aussehen. Schlägt das Speichern fehl, sollte die Karte zurückspringen und das auch melden, statt ein Board zu hinterlassen, das lügt.

    Solltest du ein CRM-Template kaufen oder aus Blöcken zusammensetzen?

    Setz es aus Blöcken zusammen, wenn das CRM ein Teil eines größeren Produkts ist, betreib Twenty, wenn du ein ganzes CRM mit Backend willst, und kauf ein Kit, wenn du am ersten Tag viele fertige Screens haben möchtest. Die Fakten stammen von den öffentlichen Seiten der Projekte, Stand Oktober 2026:

    OptionWas es istStackPipeline-Berechnung
    Refine CRM example (opens in a new tab)Referenz-App: Dashboard, Kalender, Kanban, Sales-Pipeline, Firmen, Kontakte, Angebote; Repo unter MITReact, Vite, Ant Design, GraphQL über Nestjs-queryPhasen und Conversions; Gewichtung nach Wahrscheinlichkeit nicht beschrieben
    TheFrontKit CRM Dashboard Kit (opens in a new tab)Kostenpflichtiges UI-Kit mit über 35 Screens, nur UI, $99 (reduziert von $165)Next.js 16, Tailwind CSS v4, shadcn/ui, @hello-pangea/dndSpaltensummen; Prognose als committed, best case und pipeline
    Twenty (opens in a new tab)Ein komplettes Open-Source-CRM, größtenteils AGPLv3React-Frontend, NestJS, PostgreSQL, RedisEin volles Produkt mit eigenem Backend
    Wingo UI CRM-BlöckePro-Blöcke als Quellcode, die du in deine App einbaustNext.js oder Vite, React 19, Tailwind CSS v4, dnd kitGewichtete Summen pro Phase, Prognose, Abschlussquote, veraltete Deals

    Twenty ist die bessere Wahl, wenn du ein CRM betreiben und erweitern willst und kein Frontend bauen möchtest. Ein komplettes Kit ist besser, wenn du Dutzende fertige Screens willst und die Beispieldaten ersetzen wirst. Blöcke passen, wenn das CRM in einem Agentur-Tool, einer Rechnungs-App oder einem vertikalen SaaS steckt und zum Rest passen muss. Ein Hinweis zu Refine: Laut README von examples/app-crm ist das volle Beispiel in die Enterprise Edition gewandert, und das MIT-Repo behält eine kleinere Community-Version in examples/app-crm-minimal.

    Wie strukturierst du ein Next.js CRM Template?

    Ein nextjs crm template braucht zwei Routen in der Admin-Routengruppe aus unserem Next.js Admin-Dashboard-Tutorial. Dort richten wir auch die Shell, den LinkProvider und die Datenzugriffsschicht mit verifySession() ein, die unten genutzt werden.

    text
    app/(admin)/
    layout.tsx AdminShell mit layout="fill"
    deals/
    page.tsx lädt Pipelines, Deals, Verantwortliche, Firmen
    deals-view.tsx Client: CrmPipelinePage + Actions
    actions.ts moveDeal, saveDeal, createDeal, deleteDeal
    clients/[id]/
    page.tsx lädt einen Firmendatensatz
    client-view.tsx Client: CrmContactPage + Actions
    actions.ts saveNote, deleteNote, setTags, addDeal
    server/
    dal.ts verifySession
    crm.ts deine Queries, PIPELINES, toCrmDeal

    Installier die beiden Blöcke und die Shell. Die CLI ergänzt alles, was sie importieren, auch die Deal Pipeline und das Kanban darunter:

    bash
    npx wingo-ui@latest add crm-pipeline-page crm-contact-page admin-shell

    Wie baust du die Seite für die Deal-Pipeline?

    Fang mit den Datenstrukturen an. Die Phasen stehen in der Konfiguration, denn eine geänderte Wahrscheinlichkeit ist eine Produktentscheidung und sollte wie jede andere Codeänderung durchs Review gehen. Die IDs der Phasen müssen über alle Pipelines eindeutig sein: Die Seite ordnet einen Deal der Pipeline zu, zu der seine Phase gehört.

    ts
    // server/crm.ts
    import "server-only";
    import type { CrmDeal, CrmPipeline } from "@/components/blocks/crm-pipeline-page";
    export const PIPELINES: CrmPipeline[] = [
    {
    id: "sales",
    name: "Vertrieb",
    stages: [
    { id: "qualified", name: "Qualifiziert", probability: 0.1 },
    { id: "discovery", name: "Bedarfsanalyse", probability: 0.25 },
    { id: "proposal", name: "Angebot gesendet", probability: 0.5 },
    { id: "negotiation", name: "Verhandlung", probability: 0.75 },
    { id: "won", name: "Gewonnen", probability: 1, kind: "won" },
    { id: "lost", name: "Verloren", probability: 0, kind: "lost" },
    ],
    },
    ];
    export type DealRow = {
    id: string;
    stage_id: string;
    title: string;
    amount_cents: number;
    currency: string;
    probability: number | null;
    company_id: string;
    company_name: string;
    owner_id: string;
    owner_name: string;
    close_date: string | null;
    stage_entered_at: string;
    lost_reason: string | null;
    };
    // Zeilen aus deiner Datenbank (Drizzle, Prisma, reines SQL) in die Form des Blocks
    export function toCrmDeal(row: DealRow): CrmDeal {
    return {
    id: row.id,
    stageId: row.stage_id,
    title: row.title,
    amount: row.amount_cents / 100,
    currency: row.currency,
    probability: row.probability ?? undefined,
    company: { name: row.company_name },
    companyId: row.company_id,
    owner: { id: row.owner_id, name: row.owner_name },
    expectedCloseDate: row.close_date ?? undefined,
    stageEnteredAt: row.stage_entered_at,
    lostReason: row.lost_reason ?? undefined,
    href: `/deals?deal=${row.id}`,
    };
    }

    Die Seite ist eine Server-Komponente. Sie prüft die Session, lädt alles parallel und liest ?deal=, damit ein Link das Sheet eines Deals öffnen kann. Deine Query-Helfer (listDeals, listOwners, listCompanies) geben CrmDeal[], CrmOwner[] und CrmPipelineCompany[] zurück. Gib jeder Firma ein href wie /clients/<id>, dann verlinkt das Deal-Sheet auf ihren Datensatz.

    tsx
    // app/(admin)/deals/page.tsx
    import { verifySession } from "@/server/dal";
    import { PIPELINES, listCompanies, listDeals, listOwners } from "@/server/crm";
    import { DealsView } from "./deals-view";
    export default async function DealsPage({ searchParams }: { searchParams: Promise<{ deal?: string }> }) {
    const { userId } = await verifySession();
    const [{ deal }, deals, owners, companies] = await Promise.all([searchParams, listDeals(), listOwners(), listCompanies()]);
    return (
    <DealsView
    pipelines={PIPELINES}
    deals={deals}
    owners={owners}
    companies={companies}
    currentUserId={userId}
    openDeal={deal}
    />
    );
    }

    Der Block ist eine Client-Komponente, also stecken die Callbacks in einer kleinen View. Jeder Handler gibt das Promise der Server Action zurück: Die Karte zeigt einen Spinner, solange sie läuft, und wird es abgelehnt, springt sie mit einem Toast zurück.

    Zwei Details in den Handlern übersieht man leicht. onDealMove feuert auch, wenn jemand Karten innerhalb einer Spalte umsortiert (move.from.id === move.to.id), deshalb überspringt die View diese Fälle. Sonst würde jedes Umsortieren stage_entered_at zurücksetzen und damit den Hinweis auf veraltete Deals und das Datum des Gewinns löschen. Und onDealSave liefert eine Notiz aus dem Sheet als neuesten activity-Eintrag mit kind: "note". Ihre ID ist stabil, also speicherst du Notizen per Upsert über diese ID: Ein späteres Speichern desselben Deals schickt dieselbe Notiz noch einmal.

    tsx
    // app/(admin)/deals/deals-view.tsx
    "use client";
    import {
    CrmPipelinePage,
    type CrmDeal,
    type CrmOwner,
    type CrmPipeline,
    type CrmPipelineCompany,
    } from "@/components/blocks/crm-pipeline-page";
    import { createDeal, deleteDeal, moveDeal, saveDeal } from "./actions";
    type Props = {
    pipelines: CrmPipeline[];
    deals: CrmDeal[];
    owners: CrmOwner[];
    companies: CrmPipelineCompany[];
    currentUserId: string;
    openDeal?: string;
    };
    export function DealsView({ pipelines, deals, owners, companies, currentUserId, openDeal }: Props) {
    return (
    <CrmPipelinePage
    height="fill"
    pipelines={pipelines}
    defaultPipeline={pipelines[0]?.id}
    defaultDeals={deals}
    owners={owners}
    companies={companies}
    currentUserId={currentUserId}
    defaultOpenDeal={openDeal}
    onDealMove={(move) =>
    // auch ein Umsortieren in einer Spalte landet hier; überspringen, damit stageEnteredAt stimmt
    move.from.id === move.to.id ? undefined : moveDeal(move.deal.id, move.to.id, move.lostReason)
    }
    onDealSave={(deal) => {
    const latest = deal.activity?.[0];
    const note = latest?.kind === "note" ? latest : undefined;
    return saveDeal({
    id: deal.id,
    title: deal.title,
    amount: deal.amount,
    ownerId: deal.owner?.id,
    expectedCloseDate: deal.expectedCloseDate,
    note: note ? { id: note.id, text: note.description ?? "" } : undefined,
    });
    }}
    onDealCreate={(deal) =>
    createDeal({
    title: deal.title,
    stageId: deal.stageId,
    amount: deal.amount,
    companyId: deal.companyId,
    ownerId: deal.owner?.id,
    expectedCloseDate: deal.expectedCloseDate,
    })
    }
    onDealDelete={(deal) => deleteDeal(deal.id)}
    />
    );
    }

    Schick IDs und einfache Felder an die Actions, nie den ganzen Deal: Eine Server Action ist ein öffentlicher Endpunkt, also nimmt sie nur an, was sie validiert. Hier die Verschiebung, die am häufigsten läuft:

    ts
    // app/(admin)/deals/actions.ts
    "use server";
    import { z } from "zod";
    import { verifySession } from "@/server/dal";
    import { PIPELINES, updateDeal } from "@/server/crm";
    const stageIds = PIPELINES.flatMap((pipeline) => pipeline.stages.map((stage) => stage.id));
    const Move = z.object({
    dealId: z.string().min(1),
    stageId: z.string().refine((id) => stageIds.includes(id), "Unbekannte Phase"),
    lostReason: z.string().max(200).optional(),
    });
    export async function moveDeal(dealId: string, stageId: string, lostReason?: string) {
    const { userId } = await verifySession();
    const move = Move.parse({ dealId, stageId, lostReason });
    await updateDeal(userId, move.dealId, {
    stage_id: move.stageId,
    lost_reason: move.lostReason ?? null,
    stage_entered_at: new Date().toISOString(),
    });
    }

    saveDeal, createDeal und deleteDeal folgen demselben Muster (saveDeal validiert die optionale note mit z.object({ id: z.string(), text: z.string().max(5000) })). createDeal gibt toCrmDeal(row) zurück, damit der gespeicherte Deal den Entwurf auf dem Board ersetzt. Das Board bleibt unkontrolliert (defaultDeals), also musst du die Seite nach einer Verschiebung nicht revalidieren: Der optimistische State zeigt das Ergebnis schon.

    Zieh Online bookings in eine andere Phase: Diese Demo lässt das erste Speichern absichtlich scheitern, also springt die Karte mit einem Toast zurück. Zieh danach einen Deal auf Lost und wähl einen Grund, oder klick auf eine Karte, um ihr Sheet zu öffnen
    $ npx wingo-ui@latest add crm-pipeline-page
    ProDoku: CRM Pipeline Page

    Wie funktioniert die gewichtete Prognose?

    Die gewichtete Summe einer Phase ist die Summe aus Betrag mal Wahrscheinlichkeit über ihre Deals. Die Wahrscheinlichkeit kommt von der Phase, außer der Deal setzt eine eigene probability, die sie überschreibt. Gewonnene Deals zählen voll, verlorene null. Mit den Phasen von oben:

    DealPhaseBetragWahrscheinlichkeitGewichtet
    KundenportalQualifiziert$42,00010% (Phase)$4,200
    Website-RedesignAngebot gesendet$18,00050% (Phase)$9,000
    SupportvertragVerhandlung$30,00060% (Deal überschreibt)$18,000
    Offene Pipeline$90,000$31,200

    Die Zusammenfassung hat vier Kacheln: offene Pipeline, gewichtete Prognose, im Zeitraum gewonnen (standardmäßig der aktuelle Monat, datiert über stageEnteredAt) und die Abschlussquote, also gewonnene Deals geteilt durch gewonnene plus verlorene im selben Zeitraum. Bei der Deal Pipeline und bei PipelineSummary ersetzt showWeighted={false} die gewichtete Kachel durch den durchschnittlichen offenen Deal. Deals in einer anderen Währung werden getrennt summiert, mit einem Hinweis, und nie umgerechnet. Ohne einen Wechselkurs, den du selbst kontrollierst, ist das die einzige ehrliche Voreinstellung.

    Die Wahrscheinlichkeiten pro Phase sind die Schwachstelle. Leg sie anhand deiner eigenen abgeschlossenen Deals fest (wie viele der Deals, die „Angebot gesendet“ erreicht haben, wurden gewonnen?) und prüf sie jedes Quartal neu. Behandle die Zahlen oben als Platzhalter, bis deine eigene Historie sie ersetzt.

    Um dieselbe Prognose auf die Startseite des Dashboards zu bringen, renderst du die Zusammenfassung allein. Sie ist eine Client-Komponente, die einfache Daten bekommt, also kann eine Server-Seite sie direkt rendern. Für eigene Kacheln liefert computePipelineStats(deals, stages) aus derselben Datei die Zahlen hinter der Zusammenfassung. Die Datei beginnt mit "use client", also ruf die Funktion aus Client-Code auf: Eine Server-Komponente, die sie importiert, bekommt eine Client-Referenz statt einer Funktion.

    tsx
    // app/(admin)/page.tsx (Auszug)
    import { PipelineSummary } from "@/components/ui/deal-pipeline";
    import { PIPELINES, listDeals } from "@/server/crm";
    export default async function HomePage() {
    const deals = await listDeals();
    return <PipelineSummary deals={deals} stages={PIPELINES[0].stages} currency="USD" locale="en-US" />;
    }

    Wie baust du die Firmenseite?

    Die CRM Contact Page bekommt einen Datensatz: die Firma, ihre Kontakte, Deals, Aktivitäten, Dateien und Notizen. Für alles, was du weglässt, greift sie auf Beispieldaten zurück, also gib jeden Teil mit, auch people und currentUser.

    tsx
    // app/(admin)/clients/[id]/page.tsx
    import { notFound } from "next/navigation";
    import { verifySession } from "@/server/dal";
    import { PIPELINES, getClientRecord, getTeam } from "@/server/crm";
    import { ClientView } from "./client-view";
    export default async function ClientPage({ params }: { params: Promise<{ id: string }> }) {
    const { id } = await params;
    const { userId } = await verifySession();
    const [record, team] = await Promise.all([getClientRecord(id), getTeam()]);
    if (!record) notFound();
    const me = team.find((person) => person.id === userId);
    return <ClientView record={record} stages={PIPELINES[0].stages} team={team} me={me} />;
    }

    getClientRecord gibt ein CrmContactSample ({ company, contacts, deals, activity, files, notes }) zurück, dazu seine Position in der Kundenliste. Gib jedem Deal href: "/deals?deal=<id>", dann öffnet eine Zeile im Tab Deals das Sheet dieses Deals auf dem Board.

    tsx
    // app/(admin)/clients/[id]/client-view.tsx
    "use client";
    import type { CommentAuthor } from "@/components/ui/comment-thread";
    import { CrmContactPage, type CrmContactSample, type CrmDealStage } from "@/components/blocks/crm-contact-page";
    import { addDeal, deleteNote, saveNote, setTags } from "./actions";
    type Props = {
    record: CrmContactSample & { index: number; total: number; previousId?: string; nextId?: string };
    stages: CrmDealStage[];
    team: CommentAuthor[];
    me?: CommentAuthor;
    };
    export function ClientView({ record, stages, team, me }: Props) {
    const { company, index, total, previousId, nextId, ...parts } = record;
    return (
    <CrmContactPage
    {...parts}
    company={company}
    stages={stages}
    people={team}
    currentUser={me}
    pager={{
    index,
    total,
    previousHref: previousId ? `/clients/${previousId}` : undefined,
    nextHref: nextId ? `/clients/${nextId}` : undefined,
    }}
    previousShortcut="alt+arrowleft"
    nextShortcut="alt+arrowright"
    listHref="/clients"
    invoiceHref={`/invoices/new?client=${company.id}`}
    contactHref={`/contacts/new?client=${company.id}`}
    onNoteSave={(note) => saveNote(company.id, { id: note.id, content: note.content, pinned: note.pinned })}
    onNoteDelete={(note) => deleteNote(company.id, note.id)}
    onTagsChange={(tags) => setTags(company.id, tags)}
    onDealCreate={(values) => addDeal(company.id, values)}
    />
    );
    }

    Zwei Dinge erledigst du auf dem Server. Notizen kommen als HTML aus dem Rich-Text-Editor, also bereinige sie, bevor du sie speicherst oder renderst. Und gib onUpload mit, sobald du einen Dateispeicher hast, denn ohne diesen Callback simuliert der Tab Files den Upload nur.

    Öffne den Tab Deals, dann den Tab Notes, und speichere eine Notiz mit Cmd + Enter. Drück Alt + Pfeil rechts, um zum nächsten Kunden zu springen, oder scroll nach unten, bis die kompakte Leiste Name und Hauptaktion sichtbar hält
    $ npx wingo-ui@latest add crm-contact-page
    ProDoku: CRM Contact Page

    Wie funktioniert das CRM auf dem Smartphone?

    Auf dem Smartphone zeigt das Board jeweils eine Phase, das Ziehen startet mit einem langen Druck, und alle Menüs, Sheets und Dialoge öffnen sich von unten. Im Detail:

    • Board. Jeweils eine Phase auf 85% der Breite, die nächste schaut am Rand herein, und über eine Reihe Chips springst du zwischen den Phasen. Ein Deal hebt sich erst nach einem langen Druck von 250ms an, damit ein Wischen weiter scrollt. Das Menü ⋯ (Move to, Mark as won, Mark as lost) öffnet sich als Bottom Sheet.
    • Zusammenfassung. Ein Raster aus 2 x 2 Kacheln, das sich mit einem Tipp auf seinen Chevron zu einer Zeile zusammenklappt.
    • Deal-Sheet und Dialoge. Das Sheet wird zu einem hohen Bottom Sheet, der Pipeline-Wechsler und der Dialog für den Verlustgrund öffnen sich von unten, und aus New deal wird ein +-Button.
    • Firmenseite. Die Aktionen im Header klappen in ⋯ zusammen, das Seitenpanel wird zum Tab Details, und die Deal-Tabelle wird zu Karten.

    Wie das Touch-Ziehen hinter dem Board funktioniert, erklärt unser Tutorial zu dnd kit und Touch-Drag auf dem Smartphone. Das Board selbst, mit WIP-Limits und Verschieben per Tastatur, behandelt der Guide zum React Kanban Board.

    Kannst du das Deal Pipeline Kanban allein nutzen?

    Ja. Die CRM Pipeline Page ist der volle Screen mit Header, Filtern, Deal-Sheet und Dialog für neue Deals. Die Deal Pipeline ist die Sales Pipeline UI, die du in React-Apps überall einbetten kannst: das Board mit der Zusammenfassung, der Listenansicht und den Chips für Verantwortliche, und nichts drumherum. Nimm sie für ein deal pipeline kanban in einem Kundenportal oder auf einer Projektseite, wo ein ganzer CRM-Screen zu viel wäre.

    Die Props, die zählen, wenn du sie allein nutzt:

    • stages plus defaultValue (unkontrolliert) oder value mit onValueChange (kontrolliert). onValueChange bekommt das neue Array und den DealMove, der bei neu angelegten Deals und bei Rollbacks undefined ist.
    • onDealMove mit demselben Vertrag wie auf der Seite: Gib ein Promise zurück, solange die Karte speichern soll, lass es fehlschlagen (reject), damit sie zurückspringt, oder gib false zurück, um die Verschiebung abzulehnen.
    • onDealClick, um dein eigenes Panel zu öffnen. Ohne diesen Callback folgt eine Karte mit href dem Link.
    • persistKey, um Ansicht, Filter nach Verantwortlichen und eingeklappte Spalten pro Person zu merken.
    • getDealActions für zusätzliche Einträge im Menü ⋯ nach Move to, Mark as won und Mark as lost, und renderCard mit den Teilen von DealCard, wenn die Standardkarte die falschen Felder zeigt.
    Wechsel in der Toolbar zu List, um dieselben Deals nach Phase gruppiert zu sehen, dann drück einen Chip für Verantwortliche und schau, wie die Zusammenfassung dem Filter folgt
    $ npx wingo-ui@latest add deal-pipeline
    ProDoku: Deal Pipeline

    Für ein komplettes react crm dashboard template mit KPIs, Tabellen und Einstellungen rund um diese Screens startest du mit dem Next.js Admin-Dashboard-Tutorial. Die Kategorie Build zeigt die anderen Screens Schritt für Schritt.

    Was baust du als Nächstes?

    Installier die Blöcke mit npx wingo-ui@latest add crm-pipeline-page crm-contact-page, schließ listDeals an deine Datenbank an und verdrahte zuerst moveDeal, denn diese Action nutzen die Leute am häufigsten. Die CRM-Blöcke sind Pro (Preise). Wenn du mit Claude Code oder Cursor baust, verbinde den MCP-Server von Wingo UI, damit der Agent die echten Props jedes Blocks liest, statt sie zu raten.

    Komponenten in diesem Artikel

    • CRM Pipeline Page

      A React CRM pipeline page: pipeline switcher, search and filters, a deals board or list with totals, a deal sheet and a new deal dialog.

      Pro
    • CRM Contact Page

      A React CRM contact page: a company record with facts and actions, tabs for overview, activity, deals, files and notes, and a details panel.

      Pro
    • Deal Pipeline

      A React sales pipeline board: deal stages with weighted totals, a forecast summary, drag between stages with a lost reason, a list view.

      Pro
    • Kanban

      A React kanban board for tasks, orders and pipelines: drag cards with mouse, touch or keyboard, WIP limits, inline add and a phone layout.

      Pro

    Häufige Fragen

    Gibt es ein kostenloses React CRM Template?

    Ja. Refine veröffentlicht ein CRM-Beispiel auf Basis von React, Ant Design und einer GraphQL-API (Stand Oktober 2026 ist die volle Version in die Enterprise Edition gewandert, eine minimale Community-Version unter MIT-Lizenz bleibt), und Twenty ist ein komplettes Open-Source-CRM, größtenteils unter AGPLv3, mit eigenem Backend. Die CRM-Blöcke von Wingo UI aus diesem Tutorial sind Pro.

    Wie berechnet man eine gewichtete Pipeline-Prognose?

    Multiplizier den Betrag jedes offenen Deals mit seiner Gewinnwahrscheinlichkeit und addier die Ergebnisse. Die Wahrscheinlichkeit kommt meist von der Phase des Deals, und ein Deal kann sie überschreiben; gewonnene Deals zählen voll, verlorene mit null.

    Woher sollen die Wahrscheinlichkeiten pro Phase kommen?

    Aus deinen eigenen abgeschlossenen Deals: pro Phase der Anteil der Deals, die sie erreicht haben und später gewonnen wurden. Ohne Historie startest du mit groben Schätzungen und kalibrierst neu, sobald ein paar Monate abgeschlossener Deals vorliegen.

    Kann ich ein React CRM Template mit meinem eigenen Backend nutzen?

    Ja, wenn das Template nur die UI liefert. Die Blöcke von Wingo UI bekommen ihre Daten über Props und melden jede Änderung über Callbacks wie onDealMove und onNoteSave, also funktionieren sie mit Server Actions, einer REST-API oder GraphQL.

    Wie funktioniert ein Board für die Sales-Pipeline auf dem Smartphone?

    Zeig jeweils eine Phase und lass die nächste am Rand hereinschauen, ergänz Chips, um zwischen den Phasen zu springen, und heb Karten erst nach einem langen Druck an, damit ein Wischen weiter scrollt. Für alle, die nicht ziehen wollen, gibt es ein Menü „Move to“ im Bottom Sheet.

    • CRM
    • Next.js
    • Sales-Pipeline
    • Kanban
    • Vorlagen

    Teilen

    SR

    Über den Autor

    Serban Rusu

    Gründer von Wingo UI

    Serban Rusu hat Wingo UI gegründet. Er baut die Komponenten, die CLI und den MCP-Server und schreibt über React-UIs für Smartphones und KI-Agents.

    Mehr von Serban
    Weiterlesen

    Ähnliche Artikel

    Alle Artikel
    Komponenten-Guides

    React Kanban Board: Drag, WIP-Limits, Smartphone-Layout

    Bau ein React Kanban Board mit Drag per Maus, Touch und Tastatur, WIP-Limits und Inline-Anlegen von Karten, das am Smartphone immer nur eine Spalte zeigt.

    SRSerban Rusu·16. Okt. 2026·9 Min. Lesezeit
    Komponenten-Guides

    dnd kit Mobile: Touch-Drag, ohne das Scrollen zu blockieren

    dnd kit mobile reparieren: TouchSensor mit Long Press, bei dem die Seite weiter scrollt, touch-action pro Element, Resize-Abbruch in iOS Safari und ein Tap-Weg.

    SRSerban Rusu·9. Okt. 2026·11 Min. Lesezeit
    Praxis: Screens und Apps

    Next.js Blog Template: Markdown, Live-Demos und JSON-LD

    So haben wir ein Next.js Blog Template auf Markdown gebaut: zod-Frontmatter, Live-Demos, Sticky-Inhaltsverzeichnis, JSON-LD, Feeds und ein strenger Validator.

    SRSerban Rusu·16. Okt. 2026·10 Min. Lesezeit
    Newsletter

    Neue Artikel per E-Mail

    Neue Guides, Tutorials und Vergleiche aus dem Wingo UI Blog, sobald sie erscheinen. Die meisten Artikel sind auf Englisch.

    Kein Spam. Du kannst dich jederzeit abmelden.

    WingoUI

    Animierte, konfigurierbare, mobile-first React-Komponenten und Blöcke. Kopier den Quellcode, pass ihn an und lass deinen Coding-Agenten damit bauen.

    Produkt

    • Komponenten
    • Vorlagen
    • Preise
    • KI-Agenten
    • Theme-Generator

    Ressourcen

    • Doku
    • Blog
    • Changelog
    • Changelog-RSS
    • llms.txt

    Rechtliches

    • AGB
    • Datenschutz
    • Rückerstattung
    • Lizenz
    • Impressum

    Komponenten-Kategorien

    • Buttons & Actions
    • Inputs
    • Forms
    • Navigation
    • Overlays
    • Feedback
    • Data Display
    • Tables & Lists
    • Charts & Stats
    • Layout
    • Media
    • AI Kit
    • Text & Effects
    • Mobile
    • Commerce
    • Marketing Sections
    • Blocks
    • Hooks & Utilities

    © 2026 Wingo UI

    Wingo UI