Add timezone preference and fix all date handling to be timezone-aware
Build and push image / build (push) Successful in 56s
Build and push image / build (push) Successful in 56s
Dates were computed using browser/server local time with no explicit timezone, causing inconsistencies when server runs in UTC. Now all "today" computations and date formatting use the user's chosen IANA timezone, persisted in localStorage and selectable from Settings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,10 +4,10 @@ import type { Bootstrap, InventoryItem, Item, Product, Strain } from "../../type
|
||||
import {
|
||||
ASSET_ID_RE,
|
||||
TYPES,
|
||||
TODAY_STR,
|
||||
enrichItems,
|
||||
getLastInstance,
|
||||
} from "../../types.js";
|
||||
import { getToday, getStoredTimezone } from "../../tz.js";
|
||||
import { fmt } from "../../format.js";
|
||||
import { api } from "../../api.js";
|
||||
import { Btn, Field, Input, Select } from "../primitives/index.js";
|
||||
@@ -376,7 +376,7 @@ function InstanceDetailsStep({
|
||||
thc: last?.thc ?? (cfg?.showCannabinoidPct !== false ? 22 : 0),
|
||||
cbd: last?.cbd ?? (cfg?.showCannabinoidPct !== false ? 0.4 : 0),
|
||||
totalCannabinoids: last?.totalCannabinoids ?? (cfg?.showCannabinoidPct !== false ? 26 : 0),
|
||||
purchaseDate: TODAY_STR,
|
||||
purchaseDate: getToday(getStoredTimezone()),
|
||||
});
|
||||
const [newShopName, setNewShopName] = useState("");
|
||||
const [newShopLocation, setNewShopLocation] = useState("");
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Bootstrap, Item } from "../../types.js";
|
||||
import { TYPES, helpers, TODAY_STR, enrichItems } from "../../types.js";
|
||||
import { TYPES, helpers, enrichItems } from "../../types.js";
|
||||
import { getToday, getStoredTimezone } from "../../tz.js";
|
||||
import { api } from "../../api.js";
|
||||
import { Btn, Field, Input, Select } from "../primitives/index.js";
|
||||
import { ScanField, type ScanResult } from "../ScanField.js";
|
||||
@@ -40,7 +41,7 @@ export function AuditFlow({
|
||||
.sort((a, b) => helpers.daysSinceCheck(b) - helpers.daysSinceCheck(a));
|
||||
|
||||
const [itemId, setItemId] = useState(initialItem?.id ?? "");
|
||||
const [date, setDate] = useState(TODAY_STR);
|
||||
const [date, setDate] = useState(getToday(getStoredTimezone()));
|
||||
const [confirmedBy, setConfirmedBy] = useState<"asset" | "visual">("asset");
|
||||
|
||||
const item = allItems.find((i) => i.id === itemId);
|
||||
@@ -51,7 +52,7 @@ export function AuditFlow({
|
||||
if (i.kind === "discrete") {
|
||||
return String(i.countLastAudit ?? i.countOriginal);
|
||||
}
|
||||
return helpers.estimatedRemaining(i, TODAY_STR).toFixed(2);
|
||||
return helpers.estimatedRemaining(i, getToday(getStoredTimezone())).toFixed(2);
|
||||
};
|
||||
const [value, setValue] = useState<string>(initialValueFor(item));
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Bootstrap, Item } from "../../types.js";
|
||||
import { TODAY_STR } from "../../types.js";
|
||||
import { getToday, getStoredTimezone } from "../../tz.js";
|
||||
import { api } from "../../api.js";
|
||||
import type { BatchOp } from "../../api.js";
|
||||
import { Btn, Field, Input, Select } from "../primitives/index.js";
|
||||
@@ -22,7 +22,7 @@ export function BulkCheckinModal({
|
||||
const eligible = items.filter((i) => i.status === "checked-out");
|
||||
const excluded = items.length - eligible.length;
|
||||
|
||||
const [date, setDate] = useState(TODAY_STR);
|
||||
const [date, setDate] = useState(getToday(getStoredTimezone()));
|
||||
const [binId, setBinId] = useState(data.bins[0]?.id ?? "");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Bootstrap, Item } from "../../types.js";
|
||||
import { TODAY_STR } from "../../types.js";
|
||||
import { getToday, getStoredTimezone } from "../../tz.js";
|
||||
import { api } from "../../api.js";
|
||||
import type { BatchOp } from "../../api.js";
|
||||
import { Btn, Field, Input } from "../primitives/index.js";
|
||||
@@ -22,7 +22,7 @@ export function BulkCheckoutModal({
|
||||
const eligible = items.filter((i) => i.status === "active");
|
||||
const excluded = items.length - eligible.length;
|
||||
|
||||
const [date, setDate] = useState(TODAY_STR);
|
||||
const [date, setDate] = useState(getToday(getStoredTimezone()));
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const checkout = useMutation({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Bootstrap, Item } from "../../types.js";
|
||||
import { TODAY_STR } from "../../types.js";
|
||||
import { getToday, getStoredTimezone } from "../../tz.js";
|
||||
import { api } from "../../api.js";
|
||||
import type { BatchOp } from "../../api.js";
|
||||
import { Btn, Field, Icon, Input, Textarea } from "../primitives/index.js";
|
||||
@@ -24,7 +24,7 @@ export function BulkConsumeModal({
|
||||
|
||||
const [rating, setRating] = useState(4);
|
||||
const [notes, setNotes] = useState("");
|
||||
const [date, setDate] = useState(TODAY_STR);
|
||||
const [date, setDate] = useState(getToday(getStoredTimezone()));
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const finish = useMutation({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Bootstrap, Item } from "../../types.js";
|
||||
import { TODAY_STR } from "../../types.js";
|
||||
import { getToday, getStoredTimezone } from "../../tz.js";
|
||||
import { api } from "../../api.js";
|
||||
import type { BatchOp } from "../../api.js";
|
||||
import { Btn, Field, Input, Select, Textarea } from "../primitives/index.js";
|
||||
@@ -32,7 +32,7 @@ export function BulkGoneModal({
|
||||
|
||||
const [reason, setReason] = useState("lost");
|
||||
const [notes, setNotes] = useState("");
|
||||
const [date, setDate] = useState(TODAY_STR);
|
||||
const [date, setDate] = useState(getToday(getStoredTimezone()));
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const mark = useMutation({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Bootstrap, Item } from "../../types.js";
|
||||
import { TYPES, helpers, TODAY_STR, enrichItems } from "../../types.js";
|
||||
import { TYPES, helpers, enrichItems } from "../../types.js";
|
||||
import { getToday, getStoredTimezone } from "../../tz.js";
|
||||
import { fmt } from "../../format.js";
|
||||
import { api } from "../../api.js";
|
||||
import { Btn, Field, Input, Select } from "../primitives/index.js";
|
||||
@@ -24,14 +25,14 @@ export function CheckinFlow({
|
||||
const checkedOut = allItems.filter((i) => i.status === "checked-out");
|
||||
const [itemId, setItemId] = useState(initialItem?.id ?? "");
|
||||
const [binId, setBinId] = useState(data.bins[0]?.id ?? "");
|
||||
const [date, setDate] = useState(TODAY_STR);
|
||||
const [date, setDate] = useState(getToday(getStoredTimezone()));
|
||||
const [remaining, setRemaining] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const item = allItems.find((i) => i.id === itemId);
|
||||
const isBulk = item?.kind === "bulk";
|
||||
const cfg = item ? TYPES.find((t) => t.id === item.type) : undefined;
|
||||
const est = item ? helpers.estimatedRemaining(item, TODAY_STR) : 0;
|
||||
const est = item ? helpers.estimatedRemaining(item, getToday(getStoredTimezone())) : 0;
|
||||
|
||||
const checkin = useMutation({
|
||||
mutationFn: () => {
|
||||
@@ -58,7 +59,7 @@ export function CheckinFlow({
|
||||
setItemId(result.item.id);
|
||||
const scanned = allItems.find((i) => i.id === result.item.id);
|
||||
if (scanned?.kind === "bulk") {
|
||||
setRemaining(helpers.estimatedRemaining(scanned, TODAY_STR).toFixed(2));
|
||||
setRemaining(helpers.estimatedRemaining(scanned, getToday(getStoredTimezone())).toFixed(2));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -116,7 +117,7 @@ export function CheckinFlow({
|
||||
<div style={{ fontSize: 12, color: "var(--ink-3)" }}>
|
||||
<span className="mono">{item.assetId}</span> ·{" "}
|
||||
{helpers.brandName(data, item.brandId)} · checked out{" "}
|
||||
{fmt.dateShort(item.checkoutDate)}
|
||||
{fmt.dateShort(item.checkoutDate, getStoredTimezone())}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Bootstrap, Item } from "../../types.js";
|
||||
import { helpers, TODAY_STR, enrichItems } from "../../types.js";
|
||||
import { helpers, enrichItems } from "../../types.js";
|
||||
import { getToday, getStoredTimezone } from "../../tz.js";
|
||||
import { fmt } from "../../format.js";
|
||||
import { api } from "../../api.js";
|
||||
import { Btn, Field, Icon, Input } from "../primitives/index.js";
|
||||
@@ -23,7 +24,7 @@ export function CheckoutFlow({
|
||||
const allItems = enrichItems(data);
|
||||
const active = allItems.filter((i) => i.status === "active");
|
||||
const [itemId, setItemId] = useState(initialItem?.id ?? "");
|
||||
const [date, setDate] = useState(TODAY_STR);
|
||||
const [date, setDate] = useState(getToday(getStoredTimezone()));
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const item = allItems.find((i) => i.id === itemId);
|
||||
@@ -103,7 +104,7 @@ export function CheckoutFlow({
|
||||
<div style={{ fontSize: 12, color: "var(--ink-3)" }}>
|
||||
<span className="mono">{item.assetId}</span> ·{" "}
|
||||
{helpers.brandName(data, item.brandId)} · {bin?.name ?? "no bin"} ·
|
||||
purchased {fmt.dateShort(item.purchaseDate)}
|
||||
purchased {fmt.dateShort(item.purchaseDate, getStoredTimezone())}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: "right" }}>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Bootstrap, Item } from "../../types.js";
|
||||
import { helpers, TODAY_STR, enrichItems } from "../../types.js";
|
||||
import { helpers, enrichItems } from "../../types.js";
|
||||
import { getToday, getStoredTimezone } from "../../tz.js";
|
||||
import { fmt } from "../../format.js";
|
||||
import { api } from "../../api.js";
|
||||
import { Btn, Field, Icon, Input, Textarea } from "../primitives/index.js";
|
||||
@@ -25,7 +26,7 @@ export function ConsumeFlow({
|
||||
const [itemId, setItemId] = useState(initialItem?.id ?? "");
|
||||
const [rating, setRating] = useState(4);
|
||||
const [notes, setNotes] = useState("");
|
||||
const [date, setDate] = useState(TODAY_STR);
|
||||
const [date, setDate] = useState(getToday(getStoredTimezone()));
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const item = allItems.find((i) => i.id === itemId);
|
||||
@@ -96,7 +97,7 @@ export function ConsumeFlow({
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: "var(--ink-3)" }}>
|
||||
<span className="mono">{item.assetId}</span> · {helpers.brandName(data, item.brandId)} · {bin?.name} · purchased{" "}
|
||||
{fmt.dateShort(item.purchaseDate)}
|
||||
{fmt.dateShort(item.purchaseDate, getStoredTimezone())}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: "right" }}>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Bootstrap, Item } from "../../types.js";
|
||||
import { helpers, TODAY_STR, enrichItems } from "../../types.js";
|
||||
import { helpers, enrichItems } from "../../types.js";
|
||||
import { getToday, getStoredTimezone } from "../../tz.js";
|
||||
import { remainingShort } from "../../stats.js";
|
||||
import { api } from "../../api.js";
|
||||
import { Btn, Field, Input, Select, Textarea } from "../primitives/index.js";
|
||||
@@ -32,7 +33,7 @@ export function MarkGoneFlow({
|
||||
const [itemId, setItemId] = useState(initialItem?.id ?? active[0]?.id ?? "");
|
||||
const [reason, setReason] = useState("lost");
|
||||
const [notes, setNotes] = useState("");
|
||||
const [date, setDate] = useState(TODAY_STR);
|
||||
const [date, setDate] = useState(getToday(getStoredTimezone()));
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const item = allItems.find((i) => i.id === itemId);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user