İçeriğe geç
BigBang

Sık açılanlar

    ↑↓Gezin↵AçEscKapat
    EN

    Şablonlar

    Gösterge paneli

    Tarih aralığı, dört gösterge kartı, eğilim grafiği ve son kayıtlar tablosu.

    Gösterge paneli bloğu AppShell şablonu içinde bir özet sayfası kurar. Tarih aralığı değişince rakamlar yer değiştirir; grafik ve tablo yeniden giriş hareketi yapmaz.

    Tarih aralığını değiştirin. Sayfa başlığındaki marka ve mod seçimi önizlemeyi de değiştirir. İkinci önizleme ilk yükleme durumudur.

    Canlı örnek, StorybookStorybook'ta aç
    Canlı örnek, StorybookStorybook'ta aç
    Canlı örnek, StorybookStorybook'ta aç
    Terminal window
    pnpm add @bigbang/ui
    Veri Tür Not
    Göstergeler { active, open, done, cycle, delta } Uygulama Intl ile biçimler; StatCard sayı biçimlemez.
    Grafik serileri ChartSeries[] En çok 8 seri; renkler chart.categorical-* token’larından.
    Son kayıtlar Row[] DataTable status ile yükleniyor, boş ve hata durumlarını gösterir.
    loading boolean Bütün bölgeleri ilk yükleme durumunda gösterir.
    import { Button, Chart, createDataTableColumnHelper, DataTable, Grid, Icon, SegmentedControl, StatCard, StatusBadge } from '@bigbang/ui';
    import type { ChartSeries, StatusKey } from '@bigbang/ui';
    import { useMemo, useState } from 'react';
    import type { Lang } from '../../patterns/lang';
    import { AppShellTemplate } from '../../templates/app-shell/AppShellTemplate';
    const copy = {
    tr: {
    title: 'Genel bakış',
    description: 'Ekibinizin projeleri ve görevleri.',
    range: 'Tarih aralığı',
    ranges: { '7': '7 gün', '30': '30 gün', '90': '90 gün' },
    report: 'Rapor oluştur',
    active: 'Aktif proje',
    open: 'Açık görev',
    done: 'Tamamlanan görev',
    cycle: 'Ortalama süre',
    days: (value: string) => `${value} gün`,
    period: 'önceki döneme göre',
    chart: 'Açılan ve kapanan görevler',
    chartDescription: 'Haftalık görev sayısı',
    opened: 'Açılan',
    closed: 'Kapanan',
    week: (index: number) => `${index}. hafta`,
    recent: 'Son kayıtlar',
    name: 'Kayıt',
    status: 'Durum',
    owner: 'Ekip',
    updated: 'Güncellendi',
    record: (index: number) => `Kayıt ${index}`,
    team: (index: number) => `Ekip ${index}`,
    },
    en: {
    title: 'Overview',
    description: "Your team's projects and tasks.",
    range: 'Date range',
    ranges: { '7': '7 days', '30': '30 days', '90': '90 days' },
    report: 'Create report',
    active: 'Active projects',
    open: 'Open tasks',
    done: 'Completed tasks',
    cycle: 'Average cycle time',
    days: (value: string) => `${value} days`,
    period: 'against the previous period',
    chart: 'Opened and closed tasks',
    chartDescription: 'Tasks per week',
    opened: 'Opened',
    closed: 'Closed',
    week: (index: number) => `Week ${index}`,
    recent: 'Recent records',
    name: 'Record',
    status: 'Status',
    owner: 'Team',
    updated: 'Updated',
    record: (index: number) => `Record ${index}`,
    team: (index: number) => `Team ${index}`,
    },
    };
    type Range = '7' | '30' | '90';
    // Round demo figures per range; an app passes its own query results.
    const figures: Record<Range, { active: number; open: number; done: number; cycle: number; delta: number }> = {
    '7': { active: 24, open: 120, done: 300, cycle: 3.5, delta: 8 },
    '30': { active: 26, open: 140, done: 1200, cycle: 4, delta: 12 },
    '90': { active: 30, open: 180, done: 3600, cycle: 4.5, delta: 5 },
    };
    const opened = [40, 45, 50, 48, 60, 55, 70, 65, 80, 75, 90, 85];
    const closed = [30, 40, 45, 50, 50, 55, 60, 65, 70, 75, 80, 85];
    const statuses: StatusKey[] = ['completed', 'in-review', 'active', 'pending', 'completed'];
    type Row = { id: string; name: string; status: StatusKey; owner: string; updated: string };
    const helper = createDataTableColumnHelper<Row>();
    export type DashboardShellBlockProps = {
    lang?: Lang;
    /** Shows the loading state of every region at once, as on first load. */
    loading?: boolean;
    defaultRange?: Range;
    };
    /** A dashboard page: range control, four KPIs, a trend chart and the latest records. */
    export function DashboardShellBlock({ lang = 'tr', loading = false, defaultRange = '30' }: DashboardShellBlockProps) {
    const t = copy[lang];
    const [range, setRange] = useState<Range>(defaultRange);
    const number = new Intl.NumberFormat(lang === 'tr' ? 'tr-TR' : 'en-GB');
    const f = figures[range];
    // Turkish puts the percent sign first.
    const percent = (value: number) => (lang === 'tr' ? `%${value}` : `${value}%`);
    const series = useMemo<ChartSeries[]>(
    () => [
    { id: 'opened', label: t.opened, data: opened.map((y, index) => ({ x: t.week(index + 1), y })) },
    { id: 'closed', label: t.closed, data: closed.map((y, index) => ({ x: t.week(index + 1), y })) },
    ],
    [t],
    );
    const rows = useMemo<Row[]>(() => {
    const date = new Intl.DateTimeFormat(lang === 'tr' ? 'tr-TR' : 'en-GB', { day: 'numeric', month: 'short' });
    return statuses.map((status, index) => ({ id: `r${index + 1}`, name: t.record(index + 1), status, owner: t.team((index % 3) + 1), updated: date.format(new Date(2026, 9, 28 - index)) }));
    }, [t, lang]);
    const columns = useMemo(
    () => [
    helper.accessor('name', { header: t.name, meta: { rowHeader: true } }),
    helper.accessor('status', { header: t.status, enableSorting: false, cell: (context) => <StatusBadge status={context.getValue()} size="sm" /> }),
    helper.accessor('owner', { header: t.owner }),
    helper.accessor('updated', { header: t.updated }),
    ],
    [t],
    );
    return (
    <AppShellTemplate
    lang={lang}
    current="home"
    title={t.title}
    description={t.description}
    loading={loading}
    actions={
    <>
    <SegmentedControl label={t.range} size="sm" value={range} onValueChange={(value) => setRange(value as Range)} items={(['7', '30', '90'] as const).map((value) => ({ value, label: t.ranges[value] }))} />
    <Button size="sm" startIcon={<Icon name="file" />}>
    {t.report}
    </Button>
    </>
    }
    >
    {/* Figures change by remount (110 ms cross-fade); amounts never count up. */}
    <Grid columns={{ base: 2, md: 4 }} gap="lg">
    <StatCard key={`active-${range}`} label={t.active} value={number.format(f.active)} loading={loading} />
    <StatCard key={`open-${range}`} label={t.open} value={number.format(f.open)} loading={loading} />
    <StatCard key={`done-${range}`} label={t.done} value={number.format(f.done)} delta={{ value: percent(f.delta), direction: 'up' }} period={t.period} loading={loading} />
    <StatCard
    key={`cycle-${range}`}
    label={t.cycle}
    value={t.days(number.format(f.cycle))}
    delta={{ value: percent(5), direction: 'down', sentiment: 'positive' }}
    period={t.period}
    loading={loading}
    />
    </Grid>
    <Chart kind="area" title={t.chart} description={t.chartDescription} series={series} height="md" loading={loading} formatValue={(value) => number.format(value)} />
    <DataTable caption={t.recent} columns={columns} data={rows} getRowId={(row) => row.id} status={loading ? 'loading' : 'ready'} density="compact" />
    </AppShellTemplate>
    );
    }
    1. AppShell şablonu: gezinme ve üst çubuk.
    2. PageHeader: h1 “Genel bakış”, SegmentedControl tarih aralığı ve “Rapor oluştur”.
    3. Dört StatCard: 1024 px altında iki, üstünde dört sütun.
    4. Chart alan grafiği: açılan ve kapanan görevler, 12 hafta.
    5. DataTable “Son kayıtlar”: durum StatusBadge ile.
    • Sayfada tek h1 vardır; grafik ve tablo kendi başlık ve caption öğeleriyle adlandırılır.
    • Tarih aralığı radiogroup olarak okunur; tek sekme durağı, ok tuşlarıyla seçilir.
    • Grafik role="img" ve özet metniyle okunur; “Veriyi tablo olarak göster” yedeği vardır.
    • Yüklenirken bölgeler aria-busy taşır; sayfa başlığı yerinde kalır.
    • Rakam değişimi 110 ms çapraz solmadır; tutarlar sayarak artmaz.

    StatCard, Chart, DataTable, SegmentedControl, StatusBadge, Grid, PageHeader. Kalıplar: Yükleme durumları, Grafik galerisi.