Widoki issues (List / Board / Calendar)
View on GitHubDodaj nowy widok issues
Przećwiczony przykład: dodanie czwartego widoku, Timeline. Sześć miejsc go potrzebuje — pomiń jedno, a nowy widok albo nie jest osiągalny, albo nie ma skrótu, albo nie jest oferowany jako domyślny, albo renderuje zepsutą podgląd, zależnie od tego, które pominiesz.
Krok 1 — Rozszerz typ
Plik: resources/js/types/Issues.ts
export type IssuePageLooks = 'List' | 'Board' | 'Calendar' | 'Timeline';
Krok 2 — Wyrenderuj go
Plik: resources/js/Pages/Projects/Show.tsx
{selectedLook === 'List' ? (
<IssueTable
issues={issues.data}
queryParams={queryParams}
project={project}
pagination={
<Pagination
links={issues.links}
from={issues.from}
to={issues.to}
total={issues.total}
queryParams={queryParams}
/>
}
/>
) : selectedLook === 'Board' ? (
<>
<div className={'flex flex-1 flex-row overflow-hidden'}>
<IssueBoard issues={issues.data} />
</div>
<Pagination
links={issues.links}
from={issues.from}
to={issues.to}
total={issues.total}
queryParams={queryParams}
/>
</>
) : selectedLook === 'Calendar' ? (
<CalendarView issues={issues.data} />
) : (
<TimelineView issues={issues.data} />
)}
Każdy istniejący widok przyjmuje tę samą tablicę issues.data i prezentuje ją inaczej — nowy widok to nowy komponent konsumujący ten sam prop, nie nowa ścieżka pobierania danych. Dodaj import TimelineView from '@/Components/Organisms/TimelineView/TimelineView'; do importów pliku i zbuduj TimelineView na wzór wewnętrznej struktury tego istniejącego widoku, który jest najbliższy (oś pozioma wg daty ma więcej wspólnego z CalendarView niż z IssueBoard).
Zaktualizuj też guard walidacji localStorage tuż nad renderem, w tym samym pliku:
const [selectedLook, setSelectedLook] = useState<IssuePageLooks>(() => {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('selectedLook');
if (
saved === 'List' ||
saved === 'Board' ||
saved === 'Calendar' ||
saved === 'Timeline'
) {
return saved;
}
}
return 'List';
});
Pomiń to, a użytkownik, który wybrał Timeline jako swój domyślny widok (krok 5), zostanie po cichu odbity z powrotem do List przy następnym wczytaniu strony — guard odrzuca każdy string, którego jawnie nie rozpoznaje, włącznie z całkowicie poprawnym nowym.
Krok 3 — Dodaj przycisk nawigacji i jego skrót
Plik: resources/js/Components/Organisms/TopNav/TopNav.tsx
const shortcuts = useMemo(
(): ShortcutDefinition[] => [
// ...existing 'c'/'ctrl+i' shortcuts...
{
key: '1',
description: 'List view',
category: 'View',
action: () => setSelectedLook('List'),
},
{
key: '2',
description: 'Board view',
category: 'View',
action: () => setSelectedLook('Board'),
},
{
key: '3',
description: 'Calendar view',
category: 'View',
action: () => setSelectedLook('Calendar'),
},
{
key: '4',
description: 'Timeline view',
category: 'View',
action: () => setSelectedLook('Timeline'),
},
],
[setSelectedLook],
);
a potem pasujący <button> w bloku <nav>, kopiując dokładny kształt przycisków List/Board/Calendar (buttonVariants({ isActive: selectedLook === 'Timeline' }), Icon, etykieta) — zobacz ../shortcuts/02-register-a-global-shortcut.md po ogólny kształt dodawania takiego skrótu, chociaż ten jest scope'owany do strony projektu, nie faktycznie globalny.
Krok 4 — Dodaj go jako opcję domyślnego widoku w Ustawieniach konta
Plik: resources/js/Components/Organisms/AccountSettingsContent/AccountSettingsPreferencesTab.tsx
const issueViewOptions: Array<{
id: IssuePageLooks;
icon: 'Rows3' | 'Columns3' | 'CalendarDays' | 'ChartGantt';
description: string;
}> = [
{
id: 'List',
icon: 'Rows3',
description: 'A dense, sortable table of every issue.',
},
{
id: 'Board',
icon: 'Columns3',
description: 'Kanban columns grouped by status or priority.',
},
{
id: 'Calendar',
icon: 'CalendarDays',
description: 'Issues plotted against their due dates.',
},
{
id: 'Timeline',
icon: 'ChartGantt',
description: 'Issues laid out on a horizontal timeline.',
},
];
i ten sam guard walidacji localStorage co w kroku 2 istnieje drugi, niezależny raz w inicjalizatorze useState selectedLook tego pliku — zaktualizuj go też:
const [selectedLook, setSelectedLook] = useState<IssuePageLooks>(() => {
const saved = localStorage.getItem('selectedLook');
if (
saved === 'List' ||
saved === 'Board' ||
saved === 'Calendar' ||
saved === 'Timeline'
) {
return saved;
}
return 'List';
});
Kopia w tym pliku to ta, która faktycznie wywołuje localStorage.setItem('selectedLook', option.id), gdy karta zostanie kliknięta — guard w Show.tsx z kroku 2 tylko odczytuje to z powrotem przy następnej wizycie na stronie projektu.
Krok 5 — Dodaj jego podgląd do karty ustawień
Plik: resources/js/Components/Organisms/AccountSettingsContent/AccountSettingsIssueViewCard.tsx
function IssueViewPreview({ view }: { view: IssuePageLooks }) {
if (view === 'Board') {
return <BoardPreview />;
}
if (view === 'Calendar') {
return <CalendarPreview />;
}
if (view === 'Timeline') {
return <TimelinePreview />;
}
return <ListPreview />;
}
Dodaj komponent TimelinePreview na wzór tego samego kształtu co ListPreview/BoardPreview/CalendarPreview w tym pliku (małe, czysto dekoracyjne makiety o stałej treści — nie czytają prawdziwych danych issue, po prostu renderują kilka placeholder-owych pasków/bloków ilustrujących layout). Pominięcie tego kroku niczego nie psuje — fallback return <ListPreview /> na końcu łańcucha if sprawia, że karta ustawień Timeline po prostu po cichu pokazuje podgląd List zamiast własnego.
Testy
resources/js/Pages/Projects/Show.test.tsx(albo gdziekolwiek pokryte jest renderowanie tej strony) — dodaj przypadek asercujący, żeselectedLook === 'Timeline'renderujeTimelineView, oraz przypadek dla guardalocalStorageakceptującego'Timeline'.resources/js/Components/Organisms/TopNav/TopNav.test.tsx— dodaj przypadek dla nowego przycisku nawigacji i jego skrótu'4', na wzór dokładnie istniejących przypadków testowychList/Board/Calendar(zobacz istniejące fixture'y testoweselectedLook: 'Board'/'Calendar'w pliku).resources/js/Components/Organisms/AccountSettingsContent/AccountSettingsPreferencesTab.test.tsx— dodaj przypadek wybierający kartę Timeline i asercujący, żelocalStorage.setItemzostało wywołane z'Timeline'.resources/js/Components/Organisms/AccountSettingsContent/AccountSettingsIssueViewCard.test.tsx— dodaj przypadek asercujący, żeview="Timeline"renderujeTimelinePreview.resources/js/Components/Organisms/TimelineView/TimelineView.test.tsx(nowy plik) — jakiekolwiek pokrycie pasujące do faktycznego zachowania nowego komponentu, na wzór kształtuCalendarView.test.tsxjako najbliższej istniejącej referencji.
