feat: workspace start of week column (closes #361) (#399)

* feat: workspace start of week column

* feat(l10n): add workspace setting for the first day of the week

Fixes #361

* feat: add Saturday as option

* chore: fix migration order

* chore: fix merge

---------

Co-authored-by: Henry <henry_ball@hotmail.co.uk>
This commit is contained in:
Matt
2026-03-11 17:05:03 -03:00
committed by GitHub
parent 94fd2cb11f
commit 8e7a95ff2b
17 changed files with 4362 additions and 1012 deletions

View File

@@ -24,6 +24,7 @@ import Toggle from "~/components/Toggle";
import { useModalFormState } from "~/hooks/useModalFormState";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { useWorkspace } from "~/providers/workspace";
import { api } from "~/utils/api";
import { formatMemberDisplayName, getAvatarUrl } from "~/utils/helpers";
@@ -53,6 +54,7 @@ export function NewCardForm({
queryParams,
}: NewCardFormProps) {
const { showPopup } = usePopup();
const { workspace } = useWorkspace();
const { closeModal, openModal, modalStates, clearModalState } = useModal();
const utils = api.useUtils();
@@ -491,6 +493,7 @@ export function NewCardForm({
setValue("dueDate", date ?? null);
setIsDateSelectorOpen(false);
}}
weekStartsOn={workspace.weekStartDay}
/>
</div>
</>

View File

@@ -5,6 +5,7 @@ import { HiMiniPlus } from "react-icons/hi2";
import DateSelector from "~/components/DateSelector";
import { usePopup } from "~/providers/popup";
import { useWorkspace } from "~/providers/workspace";
import { api } from "~/utils/api";
import { invalidateCard } from "~/utils/cardInvalidation";
@@ -22,6 +23,7 @@ export function DueDateSelector({
disabled = false,
}: DueDateSelectorProps) {
const { showPopup } = usePopup();
const { workspace } = useWorkspace();
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const [pendingDate, setPendingDate] = useState<Date | null | undefined>(
@@ -135,6 +137,7 @@ export function DueDateSelector({
<DateSelector
selectedDate={pendingDate ?? undefined}
onDateSelect={handleDateSelect}
weekStartsOn={workspace.weekStartDay}
/>
</div>
</>

View File

@@ -21,6 +21,7 @@ import UpdateWorkspaceDescriptionForm from "./components/UpdateWorkspaceDescript
import UpdateWorkspaceEmailVisibilityForm from "./components/UpdateWorkspaceEmailVisibilityForm";
import UpdateWorkspaceNameForm from "./components/UpdateWorkspaceNameForm";
import UpdateWorkspaceUrlForm from "./components/UpdateWorkspaceUrlForm";
import UpdateWeekStartDayForm from "./components/UpdateWeekStartDayForm";
import { UpgradeToProConfirmation } from "./components/UpgradeToProConfirmation";
export default function WorkspaceSettings() {
@@ -86,6 +87,15 @@ export default function WorkspaceSettings() {
disabled={!canEditWorkspace}
/>
<h2 className="mb-4 mt-8 text-[14px] font-bold text-neutral-900 dark:text-dark-1000">
{t`Week start day`}
</h2>
<UpdateWeekStartDayForm
workspacePublicId={workspace.publicId}
weekStartDay={workspaceData?.weekStartDay ?? 1}
disabled={!canEditWorkspace}
/>
<h2 className="mb-4 mt-8 text-[14px] font-bold text-neutral-900 dark:text-dark-1000">
{t`Email visibility`}
</h2>

View File

@@ -0,0 +1,59 @@
import { t } from "@lingui/core/macro";
import { useEffect, useState } from "react";
import { api } from "~/utils/api";
export default function UpdateWeekStartDayForm({
workspacePublicId,
weekStartDay,
disabled = false,
}: {
workspacePublicId: string;
weekStartDay: number;
disabled?: boolean;
}) {
const utils = api.useUtils();
const [value, setValue] = useState(weekStartDay);
useEffect(() => {
setValue(weekStartDay);
}, [weekStartDay]);
const updateWorkspace = api.workspace.update.useMutation({
onSuccess: () => {
if (workspacePublicId && workspacePublicId.length >= 12) {
void utils.workspace.byId.invalidate({
workspacePublicId,
});
void utils.workspace.all.invalidate();
}
},
});
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
if (disabled) return;
const newValue = Number(e.target.value);
setValue(newValue);
updateWorkspace.mutate({
workspacePublicId,
weekStartDay: newValue,
});
};
return (
<div className="flex gap-2">
<div className="mb-4 flex w-full max-w-[325px] items-center gap-2">
<select
value={value}
onChange={handleChange}
disabled={disabled || updateWorkspace.isPending}
className="block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-sm shadow-sm ring-1 ring-inset ring-light-600 placeholder:text-dark-800 focus:ring-2 focus:ring-inset focus:ring-light-700 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:leading-6"
>
<option value={0}>{t`Sunday`}</option>
<option value={1}>{t`Monday`}</option>
<option value={6}>{t`Saturday`}</option>
</select>
</div>
</div>
);
}