Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dbacc3bec | ||
|
|
c21f02e5d1 |
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.9",
|
"version": "2.4.10",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.9",
|
"version": "2.4.10",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.102.0",
|
"@anthropic-ai/sdk": "^0.102.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "app-ts",
|
"name": "app-ts",
|
||||||
"version": "2.4.9",
|
"version": "2.4.10",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/server.js",
|
"main": "dist/server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from "react";
|
||||||
import { styled } from "@mui/material/styles";
|
import { styled, useTheme } from "@mui/material/styles";
|
||||||
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import {
|
import {
|
||||||
GridData,
|
GridData,
|
||||||
@@ -11,7 +12,8 @@ import {
|
|||||||
} from "../lib/queries/plan";
|
} from "../lib/queries/plan";
|
||||||
import type { Project } from "../lib/queries/projects";
|
import type { Project } from "../lib/queries/projects";
|
||||||
import PlanRangeChips from "./PlanRangeChips";
|
import PlanRangeChips from "./PlanRangeChips";
|
||||||
import { LoadingState } from "../ui";
|
import { LoadingState, Select, Field } from "../ui";
|
||||||
|
import { useAuth } from "../context/AuthContext";
|
||||||
import { fonts } from "../theme";
|
import { fonts } from "../theme";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,7 +32,10 @@ const PlanGridRoot = styled(Box)(({ theme }) => ({
|
|||||||
border: `1px solid ${theme.vars!.palette.divider}`,
|
border: `1px solid ${theme.vars!.palette.divider}`,
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
boxShadow: theme.shadows[2],
|
boxShadow: theme.shadows[2],
|
||||||
maxHeight: "calc(100dvh - 240px)",
|
// Never collapse below ~5 rows: on a phone in LANDSCAPE 100dvh is only
|
||||||
|
// ~360-400px, so the bare calc left <160px and the cells disappeared
|
||||||
|
// under the sticky header. The grid scrolls internally either way.
|
||||||
|
maxHeight: "max(calc(100dvh - 240px), 360px)",
|
||||||
isolation: "isolate",
|
isolation: "isolate",
|
||||||
|
|
||||||
"& .plan-grid": {
|
"& .plan-grid": {
|
||||||
@@ -464,6 +469,15 @@ export default function PlanGrid({
|
|||||||
pulseKey,
|
pulseKey,
|
||||||
onCellClick,
|
onCellClick,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
// Phone layout: the multi-person grid doesn't fit a portrait viewport, so
|
||||||
|
// a person picker shows ONE column at a time (Datum + selected person).
|
||||||
|
// Desktop/tablet keeps all columns.
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
const { user: authUser } = useAuth();
|
||||||
|
// null = "no explicit choice yet" → defaults to the logged-in user when
|
||||||
|
// they are in the plan, else the first person.
|
||||||
|
const [mobileUserId, setMobileUserId] = useState<number | null>(null);
|
||||||
const today = useMemo(() => todayIso(), []);
|
const today = useMemo(() => todayIso(), []);
|
||||||
const catMap = useMemo(() => categoryMap(categories), [categories]);
|
const catMap = useMemo(() => categoryMap(categories), [categories]);
|
||||||
// Index projects by id once per projects change instead of a linear
|
// Index projects by id once per projects change instead of a linear
|
||||||
@@ -483,7 +497,15 @@ export default function PlanGrid({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!data) return <LoadingState />;
|
if (!data) return <LoadingState />;
|
||||||
const users = data.users;
|
const allUsers = data.users;
|
||||||
|
// Mobile: narrow to the picked person (derived, not stored — a stale pick
|
||||||
|
// after the user list changes falls back gracefully).
|
||||||
|
const mobileUser =
|
||||||
|
allUsers.find((u) => u.id === mobileUserId) ??
|
||||||
|
allUsers.find((u) => u.id === authUser?.id) ??
|
||||||
|
allUsers[0];
|
||||||
|
const users =
|
||||||
|
isMobile && allUsers.length > 1 && mobileUser ? [mobileUser] : allUsers;
|
||||||
// pulseKey?.nonce is included in the data-pulse attribute so a second
|
// pulseKey?.nonce is included in the data-pulse attribute so a second
|
||||||
// mutation on the same cell re-triggers the animation (CSS animations
|
// mutation on the same cell re-triggers the animation (CSS animations
|
||||||
// don't restart unless the keyframe applies to a fresh element/class
|
// don't restart unless the keyframe applies to a fresh element/class
|
||||||
@@ -493,6 +515,21 @@ export default function PlanGrid({
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{isMobile && allUsers.length > 1 && (
|
||||||
|
<Box sx={{ mb: 1.5 }}>
|
||||||
|
<Field label="Osoba">
|
||||||
|
<Select
|
||||||
|
value={String(mobileUser?.id ?? "")}
|
||||||
|
onChange={(value) => setMobileUserId(Number(value))}
|
||||||
|
options={allUsers.map((u) => ({
|
||||||
|
value: String(u.id),
|
||||||
|
label: u.full_name,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<PlanGridRoot data-pulse={pulseAttr}>
|
<PlanGridRoot data-pulse={pulseAttr}>
|
||||||
<table className="plan-grid">
|
<table className="plan-grid">
|
||||||
{/* The colgroup is what actually controls column width in a
|
{/* The colgroup is what actually controls column width in a
|
||||||
@@ -644,5 +681,6 @@ export default function PlanGrid({
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</PlanGridRoot>
|
</PlanGridRoot>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,7 +422,12 @@ export default function OdinChat() {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: "calc(100dvh - 100px)",
|
// svh (NOT dvh): dvh grows live as the mobile URL bar collapses, so a
|
||||||
|
// dvh-sized full-height layout always lets the page scroll by the
|
||||||
|
// browser-chrome height. svh sizes for the bar-visible viewport — the
|
||||||
|
// page never scrolls and the chat's message list scrolls internally.
|
||||||
|
// Desktop: svh === vh.
|
||||||
|
height: "calc(100svh - 100px)",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
border: 1,
|
border: 1,
|
||||||
borderColor: "divider",
|
borderColor: "divider",
|
||||||
|
|||||||
@@ -667,19 +667,29 @@ export default function PlanWork() {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Mobile: stacked full-width rows (same pattern as headerActionsSx on
|
||||||
|
the detail pages); sm+ keeps the single wrapping toolbar row. The
|
||||||
|
paired controls (Dnes/arrows, Týden/Měsíc) stay together as one
|
||||||
|
full-width row each. */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexWrap: "wrap",
|
flexDirection: { xs: "column", sm: "row" },
|
||||||
alignItems: "center",
|
flexWrap: { sm: "wrap" },
|
||||||
|
alignItems: { xs: "stretch", sm: "center" },
|
||||||
gap: 1.5,
|
gap: 1.5,
|
||||||
mb: 2,
|
mb: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Nav buttons grouped so they stay on one row when the toolbar
|
{/* Nav buttons grouped so they stay on one row when the toolbar
|
||||||
wraps on mobile. */}
|
wraps on mobile. */}
|
||||||
<Box sx={{ display: "inline-flex", gap: 1 }}>
|
<Box sx={{ display: "flex", gap: 1 }}>
|
||||||
<Button variant="outlined" color="inherit" onClick={goToToday}>
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="inherit"
|
||||||
|
onClick={goToToday}
|
||||||
|
sx={{ flex: { xs: 1, sm: "0 0 auto" } }}
|
||||||
|
>
|
||||||
Dnes
|
Dnes
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -701,8 +711,8 @@ export default function PlanWork() {
|
|||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
flex: 1,
|
flex: { sm: 1 },
|
||||||
minWidth: 220,
|
minWidth: { sm: 220 },
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
@@ -727,7 +737,11 @@ export default function PlanWork() {
|
|||||||
<Box
|
<Box
|
||||||
role="group"
|
role="group"
|
||||||
aria-label="Měřítko zobrazení"
|
aria-label="Měřítko zobrazení"
|
||||||
sx={{ display: "inline-flex", gap: 1 }}
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: 1,
|
||||||
|
"& > button": { flex: { xs: 1, sm: "0 0 auto" } },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant={view === "week" ? "contained" : "outlined"}
|
variant={view === "week" ? "contained" : "outlined"}
|
||||||
|
|||||||
Reference in New Issue
Block a user