fix(timesheet): extend mobile layout to xl and tighten row field widths
This commit is contained in:
@@ -1235,11 +1235,11 @@ function EntryEditorFields({
|
|||||||
onChange={(projectId) => (onProjectChange ? onProjectChange(projectId) : onChange({ projectId }))}
|
onChange={(projectId) => (onProjectChange ? onProjectChange(projectId) : onChange({ projectId }))}
|
||||||
placeholder={t.timesheet?.projectLabel || "Project"}
|
placeholder={t.timesheet?.projectLabel || "Project"}
|
||||||
portalOwnerId={portalOwnerId}
|
portalOwnerId={portalOwnerId}
|
||||||
className="min-w-0 max-w-[150px] 2xl:max-w-max flex-1"
|
className="min-w-0 max-w-fit flex-1"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{selectedProject?.client?.name && (
|
{selectedProject?.client?.name && (
|
||||||
<span className="min-w-0 max-w-[120px] 2xl:max-w-max shrink truncate text-sm text-slate-400 dark:text-slate-500" title={selectedProject.client.name}>
|
<span className="min-w-0 max-w-[120px] 2xl:max-w-fit shrink truncate text-sm text-slate-400 dark:text-slate-500" title={selectedProject.client.name}>
|
||||||
- {selectedProject.client.name}
|
- {selectedProject.client.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -1748,7 +1748,7 @@ function MobileRecordedEntryCard({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={wrapperRef} className="relative overflow-hidden border-b border-slate-200 bg-slate-100/70 dark:border-slate-800 dark:bg-slate-900/70 md:hidden">
|
<div ref={wrapperRef} className="relative overflow-hidden border-b border-slate-200 bg-slate-100/70 dark:border-slate-800 dark:bg-slate-900/70 xl:hidden">
|
||||||
<div className="pointer-events-none absolute inset-y-0 left-0 flex w-24 items-center justify-start bg-emerald-500/12 ps-4 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-300">
|
<div className="pointer-events-none absolute inset-y-0 left-0 flex w-24 items-center justify-start bg-emerald-500/12 ps-4 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-300">
|
||||||
<Play className="h-4 w-4" />
|
<Play className="h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
@@ -2526,110 +2526,10 @@ export default function Timesheet() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
onBlurCapture={handleTimerBlurCapture}
|
|
||||||
className="mb-4 hidden rounded-xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-950 md:block xl:hidden"
|
|
||||||
>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Input
|
|
||||||
value={timerDraft.description}
|
|
||||||
placeholder={t.timesheet?.descriptionPlaceholder || "What are you working on?"}
|
|
||||||
onChange={(event) => setTimerDraft((current) => ({ ...current, description: event.target.value }))}
|
|
||||||
disabled={isStartingTimer}
|
|
||||||
className="h-11 border-slate-200 bg-slate-50 text-sm dark:border-slate-700 dark:bg-slate-900"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_minmax(180px,220px)_auto]">
|
|
||||||
<Select
|
|
||||||
value={timerDraft.projectId}
|
|
||||||
onChange={(value) => setTimerDraft((current) => ({ ...current, projectId: String(value) }))}
|
|
||||||
options={[
|
|
||||||
{ value: "", label: t.timesheet?.projectLabel || "Project" },
|
|
||||||
...runningTimerProjects.map((project) => ({ value: project.id, label: project.name })),
|
|
||||||
]}
|
|
||||||
className="w-full"
|
|
||||||
buttonClassName="h-11 w-full rounded-md border border-slate-200 bg-slate-50 px-3 text-sm shadow-none outline-none dark:border-slate-700 dark:bg-slate-900 focus:ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
||||||
disabled={isStartingTimer}
|
|
||||||
portalOwnerId={timerEditorOwnerId}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex min-w-0 items-center">
|
|
||||||
<TagMultiSelect
|
|
||||||
tags={runningTimerTags}
|
|
||||||
selectedTags={timerDraft.tags}
|
|
||||||
onToggleTag={(tagId) =>
|
|
||||||
setTimerDraft((current) => ({ ...current, tags: toggleTagId(current.tags, tagId) }))
|
|
||||||
}
|
|
||||||
emptyHint={t.timesheet?.noTagsHint || "Create tags first from the Tags page."}
|
|
||||||
title={t.tags?.title || "Tags"}
|
|
||||||
compact
|
|
||||||
portalOwnerId={timerEditorOwnerId}
|
|
||||||
className="max-w-full"
|
|
||||||
buttonClassName="max-w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-start lg:justify-end">
|
|
||||||
<div className="flex h-11 min-w-[132px] items-center justify-center rounded-md border border-slate-200 bg-slate-50 px-4 text-sm font-semibold text-slate-900 dark:border-slate-700 dark:bg-slate-900 dark:text-white">
|
|
||||||
{runningEntry ? formatDuration(runningEntry, ticker) : "00:00:00"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
||||||
<BillableIconButton
|
|
||||||
checked={timerDraft.isBillable}
|
|
||||||
onChange={(checked) => setTimerDraft((current) => ({ ...current, isBillable: checked }))}
|
|
||||||
label={t.timesheet?.billable || "Billable"}
|
|
||||||
disabled={isStartingTimer}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex shrink-0 items-center gap-2">
|
|
||||||
{runningEntry ? (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => void handleStop(runningEntry)}
|
|
||||||
className="h-11 w-11 rounded-md"
|
|
||||||
title={t.timesheet?.stopTimer || "Stop"}
|
|
||||||
aria-label={t.timesheet?.stopTimer || "Stop"}
|
|
||||||
>
|
|
||||||
<Square className="h-4 w-4 fill-current" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="icon"
|
|
||||||
onClick={openDiscardTimerModal}
|
|
||||||
disabled={isDiscardingTimer}
|
|
||||||
className="h-11 w-11 rounded-md"
|
|
||||||
title={(t.actions as { discard?: string } | undefined)?.discard || "Discard"}
|
|
||||||
aria-label={(t.actions as { discard?: string } | undefined)?.discard || "Discard"}
|
|
||||||
>
|
|
||||||
{isDiscardingTimer ? "..." : <Trash2 className="h-4 w-4" />}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
onClick={() => void handleStartTimer()}
|
|
||||||
disabled={isStartingTimer}
|
|
||||||
size="icon"
|
|
||||||
className="h-11 w-11 rounded-md"
|
|
||||||
title={t.timesheet?.startTimer || "Start"}
|
|
||||||
aria-label={t.timesheet?.startTimer || "Start"}
|
|
||||||
>
|
|
||||||
{isStartingTimer ? "..." : <Play className="h-4 w-4 fill-current" />}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref={mobileTimerRef}
|
ref={mobileTimerRef}
|
||||||
onBlurCapture={handleTimerBlurCapture}
|
onBlurCapture={handleTimerBlurCapture}
|
||||||
className="mb-4 rounded-xl border border-slate-200 bg-white p-3 shadow-sm dark:border-slate-800 dark:bg-slate-950 md:hidden"
|
className="mb-4 rounded-xl border border-slate-200 bg-white p-3 shadow-sm dark:border-slate-800 dark:bg-slate-950 xl:hidden"
|
||||||
>
|
>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Input
|
<Input
|
||||||
@@ -2802,20 +2702,7 @@ export default function Timesheet() {
|
|||||||
lang={lang}
|
lang={lang}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden md:block xl:hidden">
|
<div className="xl:hidden">
|
||||||
<RecordedEntryCard
|
|
||||||
entry={entry}
|
|
||||||
t={t}
|
|
||||||
projects={projects}
|
|
||||||
tags={tags}
|
|
||||||
onDelete={openDeleteModal}
|
|
||||||
onRestart={handleRestartFromEntry}
|
|
||||||
onEntryUpdated={handleEntryUpdated}
|
|
||||||
variant="tablet"
|
|
||||||
lang={lang}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="md:hidden">
|
|
||||||
<MobileRecordedEntryCard
|
<MobileRecordedEntryCard
|
||||||
entry={entry}
|
entry={entry}
|
||||||
t={t}
|
t={t}
|
||||||
|
|||||||
Reference in New Issue
Block a user