feat(ui): simplify A3 workspace and refresh saved AI availability

This commit is contained in:
cesnimda
2026-11-04 00:21:04 +01:00
parent 7aa54bff1f
commit 2218659ecf
216 changed files with 7935 additions and 115 deletions
+34 -50
View File
@@ -421,17 +421,17 @@ export default function AppShell({
>
<Toolbar
sx={{
gap: 1.25,
px: { xs: 2, md: 3 },
py: { xs: 1.25, md: 0 },
minHeight: { xs: 68, md: 76 },
alignItems: { xs: "stretch", md: "center" },
flexWrap: { xs: "wrap", md: "nowrap" },
gap: { xs: 0.5, md: 1.25 },
px: { xs: 1.5, md: 3 },
py: 0,
minHeight: { xs: 56, md: 64 },
alignItems: "center",
flexWrap: "nowrap",
}}
>
{isMobile ? (
<>
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 1, width: "100%", minWidth: 0 }}>
<Box sx={{ display: "contents" }}>
<Box sx={{ display: "flex", alignItems: "center", gap: 1, minWidth: 0 }}>
<IconButton
edge="start"
@@ -442,34 +442,23 @@ export default function AppShell({
aria-expanded={drawerOpen}
aria-controls={mobileDrawerId}
onClick={() => onToggleDrawer(true)}
sx={{ border: "1px solid", borderColor: "divider", borderRadius: 2.5, width: 42, height: 42 }}
sx={{ width: 44, height: 44 }}
>
<MenuIcon fontSize="small" />
</IconButton>
<Box sx={{ display: "flex", alignItems: "center", gap: 0.9, minWidth: 0, pl: 0.25 }}>
<JobbjaktMark style={{ width: 18, height: 18, flex: "0 0 auto" }} />
<Typography variant="subtitle1" sx={{ fontWeight: 800, letterSpacing: -0.2 }} noWrap>
<Typography variant="subtitle1" sx={{ fontWeight: 800, letterSpacing: -0.2, display: "none", "@media (min-width:430px)": { display: "block" } }} noWrap>
Jobbjakt
</Typography>
</Box>
</Box>
<StackLanguageToggle language={language} setLanguage={setLanguage} />
{user ? (
<IconButton
size="small"
aria-label={t("user")}
onClick={(e) => setUserMenuAnchor(e.currentTarget)}
sx={{ borderRadius: 2.5, border: "1px solid", borderColor: "divider", width: 42, height: 42, flex: "0 0 auto" }}
>
<Avatar src={user.avatarImageDataUrl || undefined} sx={{ width: 28, height: 28, fontWeight: 900, bgcolor: "primary.main", color: "primary.contrastText" }}>{initials}</Avatar>
</IconButton>
) : <Box sx={{ width: 42, height: 42 }} />}
</Box>
<Box sx={{ display: "flex", alignItems: "center", gap: 1, width: "100%" }}>
<Box sx={{ display: "flex", alignItems: "center", gap: 1, flex: "0 0 auto" }}>
<Box sx={{ display: "contents" }}>
<Box sx={{ display: "contents" }}>
<IconButton
color="secondary"
size="small"
@@ -477,30 +466,20 @@ export default function AppShell({
aria-label={t("notifications")}
aria-describedby={notificationCountId}
onClick={(event) => onOpenNotifications?.(event.currentTarget)}
sx={{ border: "1px solid", borderColor: "divider", borderRadius: 2.5, width: 42, height: 42 }}
sx={{ width: 44, height: 44 }}
>
<Badge color="primary" badgeContent={notificationsCount || 0} max={99} slotProps={{ badge: { "aria-hidden": true } }}>
<NotificationsNoneIcon fontSize="small" />
</Badge>
</IconButton>
<IconButton
color="secondary"
size="small"
title={t("settings")}
aria-label={t("settings")}
onClick={() => runShellRouteAction("/settings", onOpenSettings)}
sx={{ border: "1px solid", borderColor: "divider", borderRadius: 2.5, width: 42, height: 42 }}
>
<SettingsOutlinedIcon fontSize="small" />
</IconButton>
</Box>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 1,
flex: 1,
gap: 0.5,
ml: "auto",
minWidth: 0,
'& > *': { minWidth: 0 },
}}
@@ -508,17 +487,20 @@ export default function AppShell({
{rightActions}
</Box>
</Box>
{user ? <IconButton size="small" aria-label={t("user")} onClick={(event) => setUserMenuAnchor(event.currentTarget)} sx={{ width: 44, height: 44, flex: "0 0 auto" }}>
<Avatar src={user.avatarImageDataUrl || undefined} sx={{ width: 28, height: 28, fontWeight: 900, bgcolor: "primary.main", color: "primary.contrastText" }}>{initials}</Avatar>
</IconButton> : null}
</>
) : (
<>
<Box sx={{ display: "flex", alignItems: "center", gap: 1, minWidth: 0, flex: 1 }}>
<Box sx={{ display: "flex", alignItems: "center", gap: 1, minWidth: 44, flex: "1 0 44px" }}>
<IconButton
size="small"
color="secondary"
onClick={() => setDesktopNavCollapsed((value) => !value)}
title={desktopNavCollapsed ? t("expandSidebar") : t("collapseSidebar")}
aria-label={desktopNavCollapsed ? t("expandSidebar") : t("collapseSidebar")}
sx={{ border: "1px solid", borderColor: "divider", borderRadius: 2, width: 40, height: 40 }}
sx={{ width: 44, height: 44 }}
>
<MenuOpenIcon fontSize="small" sx={{ transform: desktopNavCollapsed ? "scaleX(-1)" : "none" }} />
</IconButton>
@@ -530,13 +512,12 @@ export default function AppShell({
alignItems: "center",
justifyContent: "flex-end",
gap: 1,
flexWrap: "wrap",
width: { xs: "100%", md: "auto" },
flex: { xs: "1 1 100%", md: "0 1 auto" },
flexWrap: "nowrap",
flex: "0 0 auto",
}}
>
{buildBadge}
<StackLanguageToggle language={language} setLanguage={setLanguage} />
<Box sx={{ display: "none", "@media (min-width:1280px)": { display: "contents" } }}>{buildBadge}</Box>
<Box sx={{ display: "none", "@media (min-width:1024px)": { display: "block" } }}><StackLanguageToggle language={language} setLanguage={setLanguage} /></Box>
<IconButton
color="secondary"
size="small"
@@ -544,7 +525,7 @@ export default function AppShell({
aria-label={t("notifications")}
aria-describedby={notificationCountId}
onClick={(event) => onOpenNotifications?.(event.currentTarget)}
sx={{ border: "1px solid", borderColor: "divider", borderRadius: 2 }}
sx={{ width: 44, height: 44 }}
>
<Badge color="primary" badgeContent={notificationsCount || 0} max={99} slotProps={{ badge: { "aria-hidden": true } }}>
<NotificationsNoneIcon fontSize="small" />
@@ -556,7 +537,7 @@ export default function AppShell({
title={t("settings")}
aria-label={t("settings")}
onClick={() => runShellRouteAction("/settings", onOpenSettings)}
sx={{ border: "1px solid", borderColor: "divider", borderRadius: 2 }}
sx={{ width: 44, height: 44 }}
>
<SettingsOutlinedIcon fontSize="small" />
</IconButton>
@@ -571,11 +552,11 @@ export default function AppShell({
size="small"
aria-label={t("user")}
onClick={(e) => setUserMenuAnchor(e.currentTarget)}
sx={{ borderRadius: 2, border: "1px solid", borderColor: "divider" }}
sx={{ width: 44, height: 44 }}
>
<Avatar src={user.avatarImageDataUrl || undefined} sx={{ width: 30, height: 30, fontWeight: 900, bgcolor: "primary.main", color: "primary.contrastText" }}>{initials}</Avatar>
</IconButton>
<Box sx={{ display: { xs: "none", sm: "block" }, minWidth: 0 }}>
<Box sx={{ display: "none", "@media (min-width:1280px)": { display: "block" }, minWidth: 0, maxWidth: 180 }}>
<Typography sx={{ fontWeight: 600, lineHeight: 1.2 }} noWrap>
{user.userName || user.displayName || user.email || t("user")}
</Typography>
@@ -595,7 +576,7 @@ export default function AppShell({
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
transformOrigin={{ vertical: "top", horizontal: "right" }}
>
{isMobile && buildVersion ? (
{buildVersion ? (
<ListSubheader disableSticky role="presentation" sx={{ maxWidth: 280, py: 1, lineHeight: 1.5, overflowWrap: "anywhere" }}>
<Typography variant="body2">{buildCommit ? t("applicationVersionCommit", { version: buildVersion, commit: buildCommit }) : t("applicationVersion", { version: buildVersion })}</Typography>
</ListSubheader>
@@ -616,6 +597,9 @@ export default function AppShell({
>
{t("settingsMenu")}
</MenuItem>
{(["en", "nb"] as const).map(value => <MenuItem key={value} role="menuitemradio" aria-checked={language === value} onClick={() => { setLanguage(value); setUserMenuAnchor(null); }}>
{t(value === "en" ? "languageEnglish" : "languageNorwegianBokmal")}
</MenuItem>)}
<Divider />
<MenuItem
onClick={() => {
@@ -647,7 +631,7 @@ export default function AppShell({
}}
open
>
<Box aria-hidden sx={{ height: headerHeight ?? { xs: 68, md: 76 }, flexShrink: 0 }} />
<Box aria-hidden sx={{ height: headerHeight ?? { xs: 56, md: 64 }, flexShrink: 0 }} />
{drawerContent}
</Drawer>
@@ -687,7 +671,7 @@ export default function AppShell({
tabIndex={-1}
sx={{
flex: 1,
"--app-shell-header-height": `${headerHeight ?? (isMobile ? 124 : 76)}px`,
"--app-shell-header-height": `${headerHeight ?? (isMobile ? 56 : 64)}px`,
p: "var(--app-page-gutter)",
// Keep the usual feedback lane stable before a delayed toast arrives.
pb: "calc(var(--app-page-gutter) + var(--app-bottom-nav-height, 0px) + max(96px, var(--app-toast-clearance, 0px)) + env(safe-area-inset-bottom, 0px))",
@@ -697,7 +681,7 @@ export default function AppShell({
}}
>
<Box sx={{ mx: "auto", maxWidth: pageLayout ? `var(--app-page-width-${pageLayout})` : 1320, width: "100%", minWidth: 0 }}>
<Box aria-hidden sx={{ height: headerHeight ?? { xs: isMobile ? 124 : 68, md: 76 } }} />
<Box aria-hidden sx={{ height: headerHeight ?? { xs: 56, md: 64 } }} />
<Box sx={{ display: "flex", flexDirection: "column", gap: 0.75, mb: "var(--app-section-gap)", minWidth: 0 }}>
<Box sx={{ minWidth: 0 }}>
<Breadcrumbs sx={{ color: "text.secondary", mb: 0.5, '& .MuiBreadcrumbs-ol': { flexWrap: 'wrap' } }}>