calendar and contacts panels keyed (569 -> 564)

Everything except the appRegistryMetas names, which are blocked on a design decision:
AppRegistryEntry.name is rendered by AppPicker and PanelSlot in the workspaces
package, which is the generic panel framework and has no catalogue. Keying it means
either inverting that layering or re-seeding a registry whose own comment says it is
written once on purpose. Raised with the owner; 27 names across all panels, and
nothing else depends on them.
This commit is contained in:
2026-08-23 14:06:31 +00:00
parent 0f58331654
commit 17dc9719e9
3 changed files with 17 additions and 11 deletions
@@ -1,6 +1,7 @@
import type { CalendarEvent } from './shared';
import { CalendarDays, MapPin, Repeat } from 'lucide-react';
import { useCalendarEvents, useSelectedCollection } from './useDavData';
import { useTranslation } from '../../locales';
// Right panel of /calendar. Deliberately a grouped agenda list, not a month grid: the sidecar returns
// events as they are stored, with RRULE unexpanded, so a grid would have to invent occurrences the server
@@ -26,12 +27,13 @@ const formatTime = (ev: CalendarEvent) => {
};
export const CalendarView = () => {
const { t } = useTranslation();
const { selected, isLoading: loadingCollections } = useSelectedCollection('calendar');
const { events, isLoading, error } = useCalendarEvents(selected?.path ?? null);
if (loadingCollections) return <Placeholder text="Loading calendars…" />;
if (!selected) return <Placeholder text="No calendars yet. Create one from a CalDAV client." />;
if (error) return <Placeholder text={`Could not read ${selected.displayName}: ${String(error)}`} />;
if (error) return <Placeholder text={t('dav.readFailed', { name: selected.displayName, reason: String(error) })} />;
if (isLoading) return <Placeholder text="Loading events…" />;
if (events.length === 0) return <Placeholder text={`${selected.displayName} has no events.`} />;
@@ -4,6 +4,7 @@ import { Link, useSearchParams } from 'react-router';
import { CalendarDays, Contact as ContactIcon } from 'lucide-react';
import { davCollectionPath, COLLECTION_PARAM } from './shared';
import { useDavCollections } from './useDavData';
import { useTranslation } from '../../locales';
// Left panel for both /calendar and /contacts: the list of collections belonging to this user.
//
@@ -22,6 +23,7 @@ type Props = {
const ROW = 'group relative flex items-center gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors';
const CollectionNav = ({ kind, base, title, icon: Icon }: Props) => {
const { t } = useTranslation();
const [params] = useSearchParams();
const { collections, isLoading } = useDavCollections(kind);
const fromUrl = params.get(COLLECTION_PARAM);
@@ -69,19 +71,19 @@ const CollectionNav = ({ kind, base, title, icon: Icon }: Props) => {
);
})}
{!isLoading && collections.length === 0 && (
<p className="px-3 py-2 text-xs text-muted-foreground">
Nothing here yet. Add one from a phone or desktop client over CalDAV/CardDAV.
</p>
<p className="px-3 py-2 text-xs text-muted-foreground">{t('dav.empty')}</p>
)}
</nav>
</div>
);
};
export const CalendarNav = () => (
<CollectionNav kind="calendar" base="/calendar" title="Calendar" icon={CalendarDays} />
);
export const CalendarNav = () => {
const { t } = useTranslation();
return <CollectionNav kind="calendar" base="/calendar" title={t('dav.calendar')} icon={CalendarDays} />;
};
export const ContactsNav = () => (
<CollectionNav kind="addressbook" base="/contacts" title="Contacts" icon={ContactIcon} />
);
export const ContactsNav = () => {
const { t } = useTranslation();
return <CollectionNav kind="addressbook" base="/contacts" title={t('dav.contacts')} icon={ContactIcon} />;
};
@@ -1,5 +1,6 @@
import { Building2, Mail, Phone, User } from 'lucide-react';
import { useDavContacts, useSelectedCollection } from './useDavData';
import { useTranslation } from '../../locales';
// Right panel of /contacts: the selected address book as cards, sorted by name.
//
@@ -16,12 +17,13 @@ const initials = (name: string) =>
.join('') || '?';
export const ContactsView = () => {
const { t } = useTranslation();
const { selected, isLoading: loadingCollections } = useSelectedCollection('addressbook');
const { contacts, isLoading, error } = useDavContacts(selected?.path ?? null);
if (loadingCollections) return <Placeholder text="Loading address books…" />;
if (!selected) return <Placeholder text="No address books yet. Create one from a CardDAV client." />;
if (error) return <Placeholder text={`Could not read ${selected.displayName}: ${String(error)}`} />;
if (error) return <Placeholder text={t('dav.readFailed', { name: selected.displayName, reason: String(error) })} />;
if (isLoading) return <Placeholder text="Loading contacts…" />;
if (contacts.length === 0) return <Placeholder text={`${selected.displayName} has no contacts.`} />;