From fac0f67eabcf04595d9c496dc8b2507349b7f7db Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sat, 5 Sep 2026 12:39:10 +0300 Subject: [PATCH] Link between own/main team schedule --- .../components/MySchedule.module.css | 7 ++++ .../availability/components/MySchedule.tsx | 24 ++++++++++++-- .../routes/t.$customUrl.schedule.module.css | 14 ++++++++ .../routes/t.$customUrl.schedule.tsx | 26 +++++++++++---- ...-availability-team-schedule-cross-links.md | 5 +++ e2e/events.spec.ts | 32 +++++++++++++++++++ e2e/pages/calendar/events-page.ts | 5 +++ e2e/pages/team/team-schedule-page.ts | 1 + locales/da/schedule.json | 2 ++ locales/de/schedule.json | 2 ++ locales/en/schedule.json | 2 ++ locales/es-ES/schedule.json | 2 ++ locales/es-US/schedule.json | 2 ++ locales/fr-CA/schedule.json | 2 ++ locales/fr-EU/schedule.json | 2 ++ locales/he/schedule.json | 2 ++ locales/it/schedule.json | 2 ++ locales/ja/schedule.json | 2 ++ locales/ko/schedule.json | 2 ++ locales/nl/schedule.json | 2 ++ locales/pl/schedule.json | 2 ++ locales/pt-BR/schedule.json | 2 ++ locales/ru/schedule.json | 2 ++ locales/zh/schedule.json | 2 ++ 24 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 changelog/2026-09-05-availability-team-schedule-cross-links.md diff --git a/app/features/availability/components/MySchedule.module.css b/app/features/availability/components/MySchedule.module.css index 5e607b07f..01ce5666b 100644 --- a/app/features/availability/components/MySchedule.module.css +++ b/app/features/availability/components/MySchedule.module.css @@ -6,6 +6,13 @@ flex-wrap: wrap; } +.title { + display: flex; + align-items: center; + gap: var(--s-1); + flex-wrap: wrap; +} + .weekHeading { font-size: var(--font-xs); font-weight: var(--weight-semi); diff --git a/app/features/availability/components/MySchedule.tsx b/app/features/availability/components/MySchedule.tsx index e9a8ee816..833b5ee7b 100644 --- a/app/features/availability/components/MySchedule.tsx +++ b/app/features/availability/components/MySchedule.tsx @@ -1,13 +1,16 @@ +import { Users } from "lucide-react"; import * as React from "react"; import { useTranslation } from "react-i18next"; import type { FetcherWithComponents } from "react-router"; import * as R from "remeda"; -import { SendouButton } from "~/components/elements/Button"; +import { LinkButton, SendouButton } from "~/components/elements/Button"; import { toastQueue } from "~/components/elements/Toast"; +import { useUser } from "~/features/auth/core/user"; import { useUnsavedChangesChecker } from "~/form/UnsavedChangesGuard"; import { useDateTimeFormat } from "~/hooks/intl/useDateTimeFormat"; import { useActionSubmit } from "~/hooks/useActionSubmit"; import { useSearchParamsTyped } from "~/modules/search-params/hooks"; +import { teamSchedulePage } from "~/utils/urls"; import { saveWeekSchema } from "../availability-schemas"; import { scheduleWeekSearchParams } from "../availability-search-params"; import type { AvailabilityEditorWeek } from "../availability-types"; @@ -19,6 +22,7 @@ import { WeekToggle } from "./WeekToggle"; /** The events page's "My schedule": editor with current/next week toggle, "Copy last week" prefill and save. */ export function MySchedule({ data }: { data: MyScheduleData }) { const { t } = useTranslation(["schedule"]); + const user = useUser(); const [{ week }, setParams] = useSearchParamsTyped(scheduleWeekSearchParams); const [weeks, setWeeks] = React.useState>(() => data.weeks.map((editorWeek) => editorWeek.days), @@ -87,7 +91,23 @@ export function MySchedule({ data }: { data: MyScheduleData }) { return (
-

{t("schedule:editor.title")}

+
+

{t("schedule:editor.title")}

+ {user?.team ? ( + } + testId="team-schedule-link" + > + {t("schedule:editor.teamSchedule")} + + ) : null} +
= 640px) { + flex-grow: 0; + } +} + .heading { font-size: var(--font-md); color: var(--color-text); diff --git a/app/features/availability/routes/t.$customUrl.schedule.tsx b/app/features/availability/routes/t.$customUrl.schedule.tsx index c009a816f..4354a70d8 100644 --- a/app/features/availability/routes/t.$customUrl.schedule.tsx +++ b/app/features/availability/routes/t.$customUrl.schedule.tsx @@ -1,6 +1,6 @@ import clsx from "clsx"; import { isSameDay } from "date-fns"; -import { Flag, Pencil, Plus, Trash } from "lucide-react"; +import { CalendarClock, Flag, Pencil, Plus, Trash } from "lucide-react"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { useLoaderData, useMatches } from "react-router"; @@ -8,7 +8,7 @@ import * as R from "remeda"; import type * as v from "valibot"; import { ActionButton } from "~/components/ActionButton"; import { Alert } from "~/components/Alert"; -import { SendouButton } from "~/components/elements/Button"; +import { LinkButton, SendouButton } from "~/components/elements/Button"; import { SendouDialog } from "~/components/elements/Dialog"; import { FormMessage } from "~/components/FormMessage"; import { UserLink } from "~/components/UserLink"; @@ -24,6 +24,7 @@ import { useSearchParamsTyped } from "~/modules/search-params/hooks"; import { databaseTimestampToDate } from "~/utils/dates"; import invariant from "~/utils/invariant"; import type { SendouRouteHandle } from "~/utils/remix.server"; +import { EVENTS_PAGE } from "~/utils/urls"; import { action } from "../actions/t.$customUrl.schedule.server"; import { addTeamEventSchema, @@ -91,11 +92,22 @@ function ScheduleWeeks({ weeks }: { weeks: Array }) { shownWeek.days[6].noonAt, )} - setParams({ week: value })} - /> +
+ } + testId="edit-availability-link" + > + {t("schedule:team.editAvailability")} + + setParams({ week: value })} + /> +
diff --git a/changelog/2026-09-05-availability-team-schedule-cross-links.md b/changelog/2026-09-05-availability-team-schedule-cross-links.md new file mode 100644 index 000000000..94de649db --- /dev/null +++ b/changelog/2026-09-05-availability-team-schedule-cross-links.md @@ -0,0 +1,5 @@ +--- +navItem: calendar +type: feature +--- +The availability editor links to your team's schedule and the team schedule links back, both keeping the week you are looking at diff --git a/e2e/events.spec.ts b/e2e/events.spec.ts index 6eabfa126..9f99145d3 100644 --- a/e2e/events.spec.ts +++ b/e2e/events.spec.ts @@ -12,6 +12,7 @@ import { test, } from "./helpers/playwright"; import { EventsPage } from "./pages/calendar/events-page"; +import { TeamSchedulePage } from "./pages/team/team-schedule-page"; const JOINED_TOURNAMENT_NAME = "Joined Tournament"; const ORGANIZED_TOURNAMENT_NAME = "Organized Tournament"; @@ -120,6 +121,37 @@ test.describe("My schedule", () => { await isNotVisible(events.weekNotFilledMarker("current")); }); + test("crosses over to the team schedule and back, keeping the week", async ({ + page, + factories, + }) => { + const { customUrl } = await factories.TeamFactory.create({ + name: "Team Olive", + memberUserIds: [ADMIN_ID], + }); + + await impersonate(page, ADMIN_ID); + await setTimezoneCookie(page); + + const events = new EventsPage(page); + await events.goto(); + + // the week the editor is showing carries over to the team schedule + await events.locators.nextWeekToggle.click(); + await events.locators.teamScheduleLink.click(); + await expect(page).toHaveURL( + new RegExp(`/t/${customUrl}/schedule\\?week=next`), + ); + + const schedule = new TeamSchedulePage(page); + await expect(schedule.locators.grid).toBeVisible(); + + // the week the schedule is showing carries over to the editor + await schedule.locators.editAvailabilityLink.click(); + await expect(page).toHaveURL(/\/events\?week=next/); + await expect(events.weekNotFilledMarker("next")).toBeVisible(); + }); + test("shows a commitment as a locked block on the editor", async ({ page, factories, diff --git a/e2e/pages/calendar/events-page.ts b/e2e/pages/calendar/events-page.ts index 9c3736f69..f07378e9b 100644 --- a/e2e/pages/calendar/events-page.ts +++ b/e2e/pages/calendar/events-page.ts @@ -29,6 +29,11 @@ export class EventsPage { saveWeekButton: page.getByTestId("save-week-button"), copyLastWeekButton: page.getByTestId("copy-last-week-button"), dayEditorPopover: page.getByRole("dialog"), + teamScheduleLink: page.getByTestId("team-schedule-link"), + // the chip radio input is visually hidden, so the label is what clicks + nextWeekToggle: page.locator( + 'label[for="chip-radio-my-schedule-week-next"]', + ), }; } diff --git a/e2e/pages/team/team-schedule-page.ts b/e2e/pages/team/team-schedule-page.ts index e2089bcc3..36c0161ac 100644 --- a/e2e/pages/team/team-schedule-page.ts +++ b/e2e/pages/team/team-schedule-page.ts @@ -16,6 +16,7 @@ export class TeamSchedulePage { notes: page.getByTestId("schedule-note"), teamEvents: page.getByTestId("schedule-team-event"), addEventButton: page.getByTestId("add-team-event-button"), + editAvailabilityLink: page.getByTestId("edit-availability-link"), // the chip radio input is visually hidden, so the label is what clicks nextWeekToggle: page.locator( 'label[for="chip-radio-schedule-week-next"]', diff --git a/locales/da/schedule.json b/locales/da/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/da/schedule.json +++ b/locales/da/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/de/schedule.json b/locales/de/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/de/schedule.json +++ b/locales/de/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/en/schedule.json b/locales/en/schedule.json index f5a088b9b..240d195a4 100644 --- a/locales/en/schedule.json +++ b/locales/en/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "Availability saved", "editor.saveWeek": "Save week", "editor.title": "My availability", + "editor.teamSchedule": "Team schedule", "editor.timesInYourTimezone": "Times in your time zone", "editor.visibility": "Visible to your teammates and friends", "events.title": "Team events", @@ -32,6 +33,7 @@ "registration.summary.unknown": "{{amount}} unknown", "team.canPlay": "Team can play ({{players}}+)", "team.currentWeek": "This week", + "team.editAvailability": "Edit my availability", "team.hidden": "Only team members can see the team schedule", "team.nextWeek": "Next week", "team.noSchedule": "No schedule", diff --git a/locales/es-ES/schedule.json b/locales/es-ES/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/es-ES/schedule.json +++ b/locales/es-ES/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/es-US/schedule.json b/locales/es-US/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/es-US/schedule.json +++ b/locales/es-US/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/fr-CA/schedule.json b/locales/fr-CA/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/fr-CA/schedule.json +++ b/locales/fr-CA/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/fr-EU/schedule.json b/locales/fr-EU/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/fr-EU/schedule.json +++ b/locales/fr-EU/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/he/schedule.json b/locales/he/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/he/schedule.json +++ b/locales/he/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/it/schedule.json b/locales/it/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/it/schedule.json +++ b/locales/it/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/ja/schedule.json b/locales/ja/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/ja/schedule.json +++ b/locales/ja/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/ko/schedule.json b/locales/ko/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/ko/schedule.json +++ b/locales/ko/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/nl/schedule.json b/locales/nl/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/nl/schedule.json +++ b/locales/nl/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/pl/schedule.json b/locales/pl/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/pl/schedule.json +++ b/locales/pl/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/pt-BR/schedule.json b/locales/pt-BR/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/pt-BR/schedule.json +++ b/locales/pt-BR/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/ru/schedule.json b/locales/ru/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/ru/schedule.json +++ b/locales/ru/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "", diff --git a/locales/zh/schedule.json b/locales/zh/schedule.json index 7673a7253..101742cb5 100644 --- a/locales/zh/schedule.json +++ b/locales/zh/schedule.json @@ -10,6 +10,7 @@ "editor.saved": "", "editor.saveWeek": "", "editor.title": "", + "editor.teamSchedule": "", "editor.timesInYourTimezone": "", "editor.visibility": "", "events.title": "", @@ -32,6 +33,7 @@ "registration.summary.unknown": "", "team.canPlay": "", "team.currentWeek": "", + "team.editAvailability": "", "team.hidden": "", "team.nextWeek": "", "team.noSchedule": "",