feat(households): hane seçici, ayarlar, admin hane yönetimi ve hane-yok sayfası
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,18 +11,23 @@ import { UserMenu } from "./user-menu";
|
||||
export function AppHeader({
|
||||
user,
|
||||
households,
|
||||
activeHouseholdId,
|
||||
canWrite,
|
||||
version,
|
||||
}: {
|
||||
user: SessionUser;
|
||||
households: HouseholdSummary[];
|
||||
activeHouseholdId: string | undefined;
|
||||
/** Aktif hanede en az "member" mi? (İzleyici veri giremez.) */
|
||||
canWrite: boolean;
|
||||
version: string;
|
||||
}) {
|
||||
return (
|
||||
<header className="sticky top-0 z-30 flex h-14 items-center gap-2 border-b bg-background/95 px-3 backdrop-blur supports-backdrop-filter:bg-background/80 md:px-4">
|
||||
<SidebarTrigger className="hidden md:inline-flex" aria-label={tr.nav.toggleSidebar} />
|
||||
<HouseholdSwitcher households={households} />
|
||||
<HouseholdSwitcher households={households} activeId={activeHouseholdId} />
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<QuickAddButton className="hidden md:inline-flex" />
|
||||
{canWrite ? <QuickAddButton className="hidden md:inline-flex" /> : null}
|
||||
<ThemeToggle />
|
||||
<UserMenu user={user} version={version} />
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@ function BottomNavLink({ item, pathname }: { item: NavItem; pathname: string })
|
||||
}
|
||||
|
||||
/** Mobil alt gezinme çubuğu (md altı). */
|
||||
export function BottomNav() {
|
||||
export function BottomNav({ canWrite }: { canWrite: boolean }) {
|
||||
const pathname = usePathname();
|
||||
const { openQuickAdd } = useQuickAdd();
|
||||
const { setOpenMobile } = useSidebar();
|
||||
@@ -57,8 +57,10 @@ export function BottomNav() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={openQuickAdd}
|
||||
disabled={!canWrite}
|
||||
title={canWrite ? undefined : tr.households.viewerReadOnly}
|
||||
aria-label={tr.quickAdd.button}
|
||||
className="-mt-5 flex size-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg outline-none focus-visible:ring-4 focus-visible:ring-ring/50"
|
||||
className="-mt-5 flex size-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg outline-none focus-visible:ring-4 focus-visible:ring-ring/50 disabled:opacity-40"
|
||||
>
|
||||
<Plus className="size-6" />
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { House } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useTransition } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -9,17 +10,38 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { switchHouseholdAction } from "@/features/households/actions";
|
||||
import type { HouseholdSummary } from "@/features/households/types";
|
||||
import { tr } from "@/i18n/tr";
|
||||
|
||||
/** Üst bar aktif hane seçici. FAZ 5: seçim sunucuda (cookie) saklanacak. */
|
||||
export function HouseholdSwitcher({ households }: { households: HouseholdSummary[] }) {
|
||||
const [value, setValue] = useState(households[0]?.id);
|
||||
const current = households.find((h) => h.id === value);
|
||||
/** Üst bar aktif hane seçici. Seçim sunucuda doğrulanır ve cookie'ye yazılır. */
|
||||
export function HouseholdSwitcher({
|
||||
households,
|
||||
activeId,
|
||||
}: {
|
||||
households: HouseholdSummary[];
|
||||
activeId: string | undefined;
|
||||
}) {
|
||||
const [pending, startTransition] = useTransition();
|
||||
const current = households.find((h) => h.id === activeId);
|
||||
if (households.length === 0) return null;
|
||||
|
||||
function onChange(id: string) {
|
||||
if (id === activeId) return;
|
||||
startTransition(async () => {
|
||||
const result = await switchHouseholdAction(id);
|
||||
if (result.ok) toast.success(tr.household.switched(result.data.name));
|
||||
else toast.error(result.error);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Select value={value} onValueChange={setValue}>
|
||||
<SelectTrigger aria-label={tr.household.label} className="w-[min(46vw,13rem)]">
|
||||
<Select value={activeId} onValueChange={onChange} disabled={pending || households.length < 2}>
|
||||
<SelectTrigger
|
||||
aria-label={tr.household.label}
|
||||
className="w-[min(46vw,13rem)]"
|
||||
aria-busy={pending}
|
||||
>
|
||||
<House className="text-muted-foreground" />
|
||||
{/* Etiket children ile verilir: Radix aksi halde hydration'a kadar boş gösterir. */}
|
||||
<SelectValue placeholder={tr.household.select}>{current?.name}</SelectValue>
|
||||
|
||||
@@ -25,7 +25,7 @@ export type NavItem = {
|
||||
};
|
||||
|
||||
/** Şu an tamamlanmış en yüksek faz — sayfalar eklendikçe artar. */
|
||||
export const CURRENT_PHASE = 2;
|
||||
export const CURRENT_PHASE = 5;
|
||||
|
||||
export const isAvailable = (item: NavItem) => item.phase <= CURRENT_PHASE;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user