Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions src/components/settings/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import toast from 'react-hot-toast';
import { Card } from '@/components/ui';
import { bcClient } from '@/services/bc/bcClient';
import { useAuth, useProfilePhoto } from '@/services/auth';
import { useCompanyStore } from '@/hooks';
import { useCompanyStore, useSettingsStore } from '@/hooks';
import {
BuildingOffice2Icon,
BuildingOfficeIcon,
Expand Down Expand Up @@ -33,6 +33,7 @@ export function SettingsPanel() {
const { account, isAuthenticated } = useAuth();
const { photoUrl } = useProfilePhoto(isAuthenticated);
const { selectedCompany } = useCompanyStore();
const { requireTimesheetComments, updateSettings } = useSettingsStore();
const [companyInfo, setCompanyInfo] = useState<CompanyInfo | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
Expand Down Expand Up @@ -236,7 +237,30 @@ export function SettingsPanel() {
<p className="text-dark-400 text-sm">Get reminded to fill in your timesheet</p>
</div>
<label className="relative inline-flex cursor-pointer items-center">
<input type="checkbox" defaultChecked className="peer sr-only" />
<input
type="checkbox"
defaultChecked
aria-label="Timesheet Reminders"
className="peer sr-only"
/>
<div className="peer bg-dark-600 peer-checked:bg-thyme-600 peer-focus:ring-thyme-500 h-6 w-11 rounded-full peer-focus:ring-2 peer-focus:outline-none after:absolute after:top-[2px] after:left-[2px] after:h-5 after:w-5 after:rounded-full after:bg-white after:transition-all after:content-[''] peer-checked:after:translate-x-full"></div>
</label>
</div>
<div className="flex items-center justify-between">
<div>
<p className="text-dark-100">Require Comments on Time Entries</p>
<p className="text-dark-400 text-sm">
Enforce adding notes/comments when logging time
</p>
</div>
<label className="relative inline-flex cursor-pointer items-center">
<input
type="checkbox"
checked={requireTimesheetComments}
onChange={(e) => updateSettings({ requireTimesheetComments: e.target.checked })}
aria-label="Require Comments on Time Entries"
className="peer sr-only"
/>
<div className="peer bg-dark-600 peer-checked:bg-thyme-600 peer-focus:ring-thyme-500 h-6 w-11 rounded-full peer-focus:ring-2 peer-focus:outline-none after:absolute after:top-[2px] after:left-[2px] after:h-5 after:w-5 after:rounded-full after:bg-white after:transition-all after:content-[''] peer-checked:after:translate-x-full"></div>
</label>
</div>
Expand Down
11 changes: 9 additions & 2 deletions src/components/timesheet/TimeEntryModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useState, useEffect, useMemo, useCallback } from 'react';
import toast from 'react-hot-toast';
import { TrashIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline';
import { Modal, Button, Input, Select } from '@/components/ui';
import { useTimeEntriesStore, useProjectsStore } from '@/hooks';
import { useTimeEntriesStore, useProjectsStore, useSettingsStore } from '@/hooks';
import { useAuth } from '@/services/auth';
import { bcClient } from '@/services/bc/bcClient';
import type { TimeEntry, SelectOption } from '@/types';
Expand All @@ -26,6 +26,7 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP

const { addEntry, updateEntry, deleteEntry } = useTimeEntriesStore();
const { projects, selectedProject, selectedTask, selectProject, selectTask } = useProjectsStore();
const { requireTimesheetComments } = useSettingsStore();

const [customerId, setCustomerId] = useState('');
const [projectId, setProjectId] = useState('');
Expand Down Expand Up @@ -193,6 +194,11 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP
return;
}

if (requireTimesheetComments && !notes.trim()) {
toast.error('Notes are required. Please add a comment before saving.');
return;
}

if (notes.length > MAX_NOTES_LENGTH) {
toast.error(`Notes cannot exceed ${MAX_NOTES_LENGTH} characters.`);
return;
Expand Down Expand Up @@ -354,14 +360,15 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP
{/* Notes */}
<div>
<label htmlFor="notes" className="text-dark-200 mb-1 block text-sm font-medium">
Notes (optional)
Notes ({requireTimesheetComments ? 'required' : 'optional'})
</label>
<textarea
id="notes"
rows={3}
value={notes}
onChange={(e) => setNotes(e.target.value)}
maxLength={MAX_NOTES_LENGTH}
required={requireTimesheetComments}
className={`border-dark-600 bg-dark-700 text-dark-100 placeholder:text-dark-400 focus:ring-thyme-500 flex w-full rounded-lg border px-3 py-2 text-sm focus:border-transparent focus:ring-2 focus:outline-none ${
notes.length >= MAX_NOTES_LENGTH ? 'border-red-500' : ''
}`}
Expand Down
14 changes: 13 additions & 1 deletion src/components/timesheet/WeeklyTimesheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
UserCircleIcon,
EnvelopeIcon,
} from '@heroicons/react/24/outline';
import { useTimeEntriesStore, useProjectsStore, useTeammateStore } from '@/hooks';
import { useTimeEntriesStore, useProjectsStore, useTeammateStore, useSettingsStore } from '@/hooks';
import { useAuth } from '@/services/auth';
import { Button, Card, WeekNavigation, ExtensionPreviewWrapper } from '@/components/ui';
import { TimeEntryCell } from './TimeEntryCell';
Expand All @@ -38,6 +38,7 @@ export function WeeklyTimesheet() {
const userId = account?.localAccountId || '';
const userEmail = account?.username || '';
const { selectedTeammate } = useTeammateStore();
const { requireTimesheetComments } = useSettingsStore();
const isViewingTeammate = selectedTeammate !== null;

const searchParams = useSearchParams();
Expand Down Expand Up @@ -208,6 +209,17 @@ export function WeeklyTimesheet() {

const handleSubmitTimesheet = async () => {
if (!currentTimesheet) return;

if (requireTimesheetComments) {
const entriesMissingNotes = entries.filter((e) => !e.notes?.trim());
if (entriesMissingNotes.length > 0) {
toast.error(
`${entriesMissingNotes.length} ${entriesMissingNotes.length === 1 ? 'entry is' : 'entries are'} missing comments. Please add notes to all entries before submitting.`
);
return;
}
}

setIsSubmitting(true);
try {
await submitTimesheet();
Expand Down
1 change: 1 addition & 0 deletions src/hooks/useSettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const defaultSettings: UserSettings = {
defaultTaskId: undefined,
weeklyHoursTarget: 40,
notificationsEnabled: true,
requireTimesheetComments: false,
theme: 'system',
};

Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ export interface UserSettings {
defaultTaskId?: string;
weeklyHoursTarget: number;
notificationsEnabled: boolean;
requireTimesheetComments: boolean;
theme: 'light' | 'dark' | 'system';
}

Expand Down