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:
@@ -1,6 +1,7 @@
|
|||||||
import type { CalendarEvent } from './shared';
|
import type { CalendarEvent } from './shared';
|
||||||
import { CalendarDays, MapPin, Repeat } from 'lucide-react';
|
import { CalendarDays, MapPin, Repeat } from 'lucide-react';
|
||||||
import { useCalendarEvents, useSelectedCollection } from './useDavData';
|
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
|
// 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
|
// 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 = () => {
|
export const CalendarView = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { selected, isLoading: loadingCollections } = useSelectedCollection('calendar');
|
const { selected, isLoading: loadingCollections } = useSelectedCollection('calendar');
|
||||||
const { events, isLoading, error } = useCalendarEvents(selected?.path ?? null);
|
const { events, isLoading, error } = useCalendarEvents(selected?.path ?? null);
|
||||||
|
|
||||||
if (loadingCollections) return <Placeholder text="Loading calendars…" />;
|
if (loadingCollections) return <Placeholder text="Loading calendars…" />;
|
||||||
if (!selected) return <Placeholder text="No calendars yet. Create one from a CalDAV client." />;
|
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 (isLoading) return <Placeholder text="Loading events…" />;
|
||||||
if (events.length === 0) return <Placeholder text={`${selected.displayName} has no 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 { CalendarDays, Contact as ContactIcon } from 'lucide-react';
|
||||||
import { davCollectionPath, COLLECTION_PARAM } from './shared';
|
import { davCollectionPath, COLLECTION_PARAM } from './shared';
|
||||||
import { useDavCollections } from './useDavData';
|
import { useDavCollections } from './useDavData';
|
||||||
|
import { useTranslation } from '../../locales';
|
||||||
|
|
||||||
// Left panel for both /calendar and /contacts: the list of collections belonging to this user.
|
// 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 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 CollectionNav = ({ kind, base, title, icon: Icon }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [params] = useSearchParams();
|
const [params] = useSearchParams();
|
||||||
const { collections, isLoading } = useDavCollections(kind);
|
const { collections, isLoading } = useDavCollections(kind);
|
||||||
const fromUrl = params.get(COLLECTION_PARAM);
|
const fromUrl = params.get(COLLECTION_PARAM);
|
||||||
@@ -69,19 +71,19 @@ const CollectionNav = ({ kind, base, title, icon: Icon }: Props) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!isLoading && collections.length === 0 && (
|
{!isLoading && collections.length === 0 && (
|
||||||
<p className="px-3 py-2 text-xs text-muted-foreground">
|
<p className="px-3 py-2 text-xs text-muted-foreground">{t('dav.empty')}</p>
|
||||||
Nothing here yet. Add one from a phone or desktop client over CalDAV/CardDAV.
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarNav = () => (
|
export const CalendarNav = () => {
|
||||||
<CollectionNav kind="calendar" base="/calendar" title="Calendar" icon={CalendarDays} />
|
const { t } = useTranslation();
|
||||||
);
|
return <CollectionNav kind="calendar" base="/calendar" title={t('dav.calendar')} icon={CalendarDays} />;
|
||||||
|
};
|
||||||
|
|
||||||
export const ContactsNav = () => (
|
export const ContactsNav = () => {
|
||||||
<CollectionNav kind="addressbook" base="/contacts" title="Contacts" icon={ContactIcon} />
|
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 { Building2, Mail, Phone, User } from 'lucide-react';
|
||||||
import { useDavContacts, useSelectedCollection } from './useDavData';
|
import { useDavContacts, useSelectedCollection } from './useDavData';
|
||||||
|
import { useTranslation } from '../../locales';
|
||||||
|
|
||||||
// Right panel of /contacts: the selected address book as cards, sorted by name.
|
// Right panel of /contacts: the selected address book as cards, sorted by name.
|
||||||
//
|
//
|
||||||
@@ -16,12 +17,13 @@ const initials = (name: string) =>
|
|||||||
.join('') || '?';
|
.join('') || '?';
|
||||||
|
|
||||||
export const ContactsView = () => {
|
export const ContactsView = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { selected, isLoading: loadingCollections } = useSelectedCollection('addressbook');
|
const { selected, isLoading: loadingCollections } = useSelectedCollection('addressbook');
|
||||||
const { contacts, isLoading, error } = useDavContacts(selected?.path ?? null);
|
const { contacts, isLoading, error } = useDavContacts(selected?.path ?? null);
|
||||||
|
|
||||||
if (loadingCollections) return <Placeholder text="Loading address books…" />;
|
if (loadingCollections) return <Placeholder text="Loading address books…" />;
|
||||||
if (!selected) return <Placeholder text="No address books yet. Create one from a CardDAV client." />;
|
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 (isLoading) return <Placeholder text="Loading contacts…" />;
|
||||||
if (contacts.length === 0) return <Placeholder text={`${selected.displayName} has no contacts.`} />;
|
if (contacts.length === 0) return <Placeholder text={`${selected.displayName} has no contacts.`} />;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user