← back to keeper92__liftlog

Function bodies 262 total

All specs Real LLM only Function bodies
fetchPerformanceHistory function · typescript · L501-L532 (32 LOC)
src/app/(app)/workout/[id]/page.tsx
    async function fetchPerformanceHistory(userId: string, exerciseId: string): Promise<PerformanceSet[]> {
      const pageSize = 1000;
      let from = 0;
      const history: PerformanceSet[] = [];

      while (!cancelled) {
        const to = from + pageSize - 1;
        const { data, error } = await supabase
          .from('sets')
          .select('id, weight, reps, left_weight, left_reps, right_weight, right_reps, is_split_lr, set_number, workouts!inner(user_id, date)')
          .eq('exercise_id', exerciseId)
          .eq('workouts.user_id', userId)
          .eq('is_completed', true)
          .eq('is_warmup', false)
          .order('workouts(date)', { ascending: false })
          .order('set_number', { ascending: true })
          .order('id', { ascending: true })
          .range(from, to);

        if (error || !data || data.length === 0) break;

        for (const row of data as unknown as PreviousPerformanceRow[]) {
          const normalized = normalizePreviousPerfo
loadPrevious function · typescript · L534-L546 (13 LOC)
src/app/(app)/workout/[id]/page.tsx
    async function loadPrevious() {
      const { data: { user } } = await supabase.auth.getUser();
      if (!user) return;

      const missingExercises = exercises.filter((ex) => previousPerformance[ex.exerciseId] === undefined);

      for (const ex of missingExercises) {
        const history = await fetchPerformanceHistory(user.id, ex.exerciseId);
        if (cancelled) return;
        setPreviousPerformance(ex.exerciseId, history);
        prefillEmptySetsWithPreviousPerformance(ex.exerciseId, history);
      }
    }
openTrainerTips function · typescript · L765-L780 (16 LOC)
src/app/(app)/workout/[id]/page.tsx
  async function openTrainerTips(exerciseId: string, exerciseName: string) {
    setTipsModal({ exerciseId, exerciseName });
    setLoadingTips(true);
    setExerciseDetails(null);

    const { data } = await supabase
      .from('exercises')
      .select('id, name, instructions, equipment, level, primary_muscles, secondary_muscles')
      .eq('id', exerciseId)
      .single();

    if (data) {
      setExerciseDetails(data as ExerciseDetails);
    }
    setLoadingTips(false);
  }
openHistory function · typescript · L782-L835 (54 LOC)
src/app/(app)/workout/[id]/page.tsx
  async function openHistory(exerciseId: string, exerciseName: string) {
    setHistoryModal({ exerciseId, exerciseName });
    setLoadingHistory(true);
    setHistoryData([]);

    const { data: { user } } = await supabase.auth.getUser();
    if (!user) {
      setLoadingHistory(false);
      return;
    }

    const { data } = await supabase
      .from('sets')
      .select('weight, reps, left_weight, left_reps, right_weight, right_reps, is_split_lr, set_number, is_warmup, workouts!inner(id, date, user_id)')
      .eq('exercise_id', exerciseId)
      .eq('workouts.user_id', user.id)
      .eq('is_completed', true)
      .order('workouts(date)', { ascending: false });

    if (data) {
      const workoutMap = new Map<string, { date: string; sets: typeof data }>();
      for (const row of data) {
        const workout = row.workouts as unknown as { id: string; date: string };
        const workoutId = workout.id;
        const date = workout.date;
        if (!workoutMap.has(workoutId
handleDragStart function · typescript · L975-L981 (7 LOC)
src/app/(app)/workout/[id]/page.tsx
  function handleDragStart(index: number, clientY: number) {
    setDragIndex(index);
    setDragOverIndex(index);
    touchStartY.current = clientY;
    // Haptic feedback if available
    if (navigator.vibrate) navigator.vibrate(30);
  }
handleDragMove function · typescript · L983-L995 (13 LOC)
src/app/(app)/workout/[id]/page.tsx
  function handleDragMove(clientY: number) {
    if (dragIndex === null) return;
    // Find which exercise card the touch is over
    for (let i = 0; i < exerciseRefs.current.length; i++) {
      const el = exerciseRefs.current[i];
      if (!el) continue;
      const rect = el.getBoundingClientRect();
      if (clientY >= rect.top && clientY <= rect.bottom) {
        setDragOverIndex(i);
        return;
      }
    }
  }
handleDragEnd function · typescript · L997-L1008 (12 LOC)
src/app/(app)/workout/[id]/page.tsx
  function handleDragEnd() {
    if (dragIndex !== null && dragOverIndex !== null && dragIndex !== dragOverIndex) {
      store.moveExercise(dragIndex, dragOverIndex);
    }
    clearMouseDragListeners();
    setDragIndex(null);
    setDragOverIndex(null);
    if (longPressTimer.current) {
      clearTimeout(longPressTimer.current);
      longPressTimer.current = null;
    }
  }
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
cancelDrag function · typescript · L1010-L1018 (9 LOC)
src/app/(app)/workout/[id]/page.tsx
  function cancelDrag() {
    clearMouseDragListeners();
    setDragIndex(null);
    setDragOverIndex(null);
    if (longPressTimer.current) {
      clearTimeout(longPressTimer.current);
      longPressTimer.current = null;
    }
  }
pickBestSet function · typescript · L67-L70 (4 LOC)
src/app/(app)/workout/summary/[id]/page.tsx
function pickBestSet(
  sets: Array<{ weight: number; reps: number }>,
  isAssistanceExercise: boolean,
): { weight: number; reps: number } {
normalizeLegacyWorkoutData function · typescript · L90-L110 (21 LOC)
src/app/(app)/workout/summary/[id]/page.tsx
function normalizeLegacyWorkoutData(data: LegacyWorkoutData): WorkoutData {
  return {
    ...data,
    sets: data.sets.map((set) => {
      const exerciseRef = Array.isArray(set.exercises) ? set.exercises[0] : set.exercises;
      return {
        exercise_id: set.exercise_id,
        weight: set.weight,
        reps: set.reps,
        set_number: set.set_number,
        is_warmup: set.is_warmup,
        exercises: { name: exerciseRef?.name || 'Exercise' },
        left_weight: null,
        left_reps: null,
        right_weight: null,
        right_reps: null,
        is_split_lr: false,
      };
    }),
  };
}
WorkoutSummaryPage function · typescript · L112-L118 (7 LOC)
src/app/(app)/workout/summary/[id]/page.tsx
export default function WorkoutSummaryPage() {
  return (
    <Suspense fallback={<div className="flex items-center justify-center min-h-dvh text-muted-foreground">Loading...</div>}>
      <WorkoutSummaryContent />
    </Suspense>
  );
}
load function · typescript · L135-L157 (23 LOC)
src/app/(app)/workout/summary/[id]/page.tsx
    async function load() {
      const { data, error } = await supabase
        .from('workouts')
        .select('id, name, start_time, end_time, sets(exercise_id, weight, reps, left_weight, left_reps, right_weight, right_reps, is_split_lr, set_number, is_warmup, exercises(name))')
        .eq('id', params.id as string)
        .single();
      if (data && !error) {
        setWorkout(data as unknown as WorkoutData);
        return;
      }

      if (!isMissingSplitSetColumnsError(error)) return;

      const { data: legacyData, error: legacyError } = await supabase
        .from('workouts')
        .select('id, name, start_time, end_time, sets(exercise_id, weight, reps, set_number, is_warmup, exercises(name))')
        .eq('id', params.id as string)
        .single();

      if (legacyData && !legacyError) {
        setWorkout(normalizeLegacyWorkoutData(legacyData as unknown as LegacyWorkoutData));
      }
    }
generateNotes function · typescript · L166-L280 (115 LOC)
src/app/(app)/workout/summary/[id]/page.tsx
    async function generateNotes() {
      setLoadingNotes(true);

      const { data: { user } } = await supabase.auth.getUser();
      if (!user) {
        setLoadingNotes(false);
        return;
      }

      // Get unique exercise IDs from this workout
      const exerciseIds = [...new Set(workoutData.sets.map((s) => s.exercise_id))];

      // Fetch previous performance for each exercise (excluding this workout)
      const comparisons: {
        exerciseName: string;
        isAssistanceExercise: boolean;
        currentBest: { weight: number; reps: number };
        previousBest: { weight: number; reps: number } | null;
      }[] = [];

      for (const exId of exerciseIds) {
        const exerciseSets = workoutData.sets.filter((s) => s.exercise_id === exId && !s.is_warmup);
        if (exerciseSets.length === 0) continue;

        const exerciseName = exerciseSets[0].exercises.name;
        const isAssistanceExercise = isAssistanceExerciseName(exerciseName);

        // Find b
handleSaveTemplate function · typescript · L285-L327 (43 LOC)
src/app/(app)/workout/summary/[id]/page.tsx
  async function handleSaveTemplate() {
    if (!workout) return;
    setSaving(true);

    const name = workout.name || 'My Template';

    const { data: { user } } = await supabase.auth.getUser();
    if (!user) {
      setSaving(false);
      return;
    }

    const { data: template, error: tErr } = await supabase
      .from('workout_templates')
      .insert({ user_id: user.id, name })
      .select()
      .single();

    if (tErr || !template) {
      setSaving(false);
      return;
    }

    // Get unique exercises in order of first appearance
    const exerciseOrder: string[] = [];
    for (const s of workout.sets) {
      if (!exerciseOrder.includes(s.exercise_id)) {
        exerciseOrder.push(s.exercise_id);
      }
    }

    const templateExercises = exerciseOrder.map((exId, idx) => ({
      template_id: template.id,
      exercise_id: exId,
      order_index: idx,
      default_sets: workout.sets.filter((s) => s.exercise_id === exId && !s.is_warmup).length || 3,
    }))
handleUpdateTemplate function · typescript · L329-L365 (37 LOC)
src/app/(app)/workout/summary/[id]/page.tsx
  async function handleUpdateTemplate() {
    if (!workout || !templateId) return;
    setSaving(true);

    // Delete existing template exercises
    await supabase
      .from('template_exercises')
      .delete()
      .eq('template_id', templateId);

    // Get unique exercises in order of first appearance
    const exerciseOrder: string[] = [];
    for (const s of workout.sets) {
      if (!exerciseOrder.includes(s.exercise_id)) {
        exerciseOrder.push(s.exercise_id);
      }
    }

    // Insert new template exercises
    const templateExercises = exerciseOrder.map((exId, idx) => ({
      template_id: templateId,
      exercise_id: exId,
      order_index: idx,
      default_sets: workout.sets.filter((s) => s.exercise_id === exId && !s.is_warmup).length || 3,
    }));

    await supabase.from('template_exercises').insert(templateExercises);

    // Update template updated_at
    await supabase
      .from('workout_templates')
      .update({ updated_at: new Date().toISOStrin
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
handleDiscardWorkout function · typescript · L367-L376 (10 LOC)
src/app/(app)/workout/summary/[id]/page.tsx
  async function handleDiscardWorkout() {
    if (!workout) return;
    if (!window.confirm('Discard this workout? It will be permanently deleted.')) return;

    setDiscarding(true);
    // Delete sets first (foreign key), then the workout
    await supabase.from('sets').delete().eq('workout_id', workout.id);
    await supabase.from('workouts').delete().eq('id', workout.id);
    router.push('/dashboard');
  }
AuthLayout function · typescript · L5-L20 (16 LOC)
src/app/(auth)/layout.tsx
export default function AuthLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <DesktopMobileFrame>
      <div className="relative flex min-h-dvh items-center justify-center overflow-hidden bg-background px-4 py-10 lg:min-h-full">
        <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top,_color-mix(in_srgb,var(--primary)_16%,transparent)_0%,transparent_55%)]" />
        <div className="relative w-full max-w-md">
          {children}
        </div>
      </div>
    </DesktopMobileFrame>
  );
}
LoginPage function · typescript · L20-L180 (161 LOC)
src/app/(auth)/login/page.tsx
export default function LoginPage() {
  const router = useRouter();
  const [formData, setFormData] = useState<LoginInput>({
    email: '',
    password: '',
  });
  const [errors, setErrors] = useState<Partial<Record<keyof LoginInput, string>>>({});
  const [generalError, setGeneralError] = useState('');
  const [loading, setLoading] = useState(false);
  const [demoLoading, setDemoLoading] = useState(false);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormData((prev) => ({ ...prev, [name]: value }));
    if (errors[name as keyof LoginInput]) {
      setErrors((prev) => ({ ...prev, [name]: undefined }));
    }
    if (generalError) setGeneralError('');
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setErrors({});
    setGeneralError('');

    const result = loginSchema.safeParse(formData);
    if (!result.success) {
      const fieldErrors: Partial<Record<keyof LoginInput, s
RegisterPage function · typescript · L18-L130 (113 LOC)
src/app/(auth)/register/page.tsx
export default function RegisterPage() {
  const router = useRouter();
  const [formData, setFormData] = useState<RegisterInput>({
    email: '',
    password: '',
  });
  const [errors, setErrors] = useState<Partial<Record<keyof RegisterInput, string>>>({});
  const [generalError, setGeneralError] = useState('');
  const [loading, setLoading] = useState(false);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormData((prev) => ({ ...prev, [name]: value }));
    if (errors[name as keyof RegisterInput]) {
      setErrors((prev) => ({ ...prev, [name]: undefined }));
    }
    if (generalError) setGeneralError('');
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setErrors({});
    setGeneralError('');

    const result = registerSchema.safeParse(formData);
    if (!result.success) {
      const fieldErrors: Partial<Record<keyof RegisterInput, string>> = {};
      for (const issue of
RootLayout function · typescript · L16-L29 (14 LOC)
src/app/layout.tsx
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head />
      <body className="antialiased bg-background text-foreground">
        {children}
      </body>
    </html>
  );
}
HomePage function · typescript · L3-L5 (3 LOC)
src/app/page.tsx
export default function HomePage() {
  redirect('/login');
}
getTimeGroup function · typescript · L12-L28 (17 LOC)
src/components/chat/ChatSidebar.tsx
function getTimeGroup(dateStr: string): string {
  const date = new Date(dateStr);
  const now = new Date();
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  const yesterday = new Date(today);
  yesterday.setDate(yesterday.getDate() - 1);
  const weekAgo = new Date(today);
  weekAgo.setDate(weekAgo.getDate() - 7);
  const monthAgo = new Date(today);
  monthAgo.setDate(monthAgo.getDate() - 30);

  if (date >= today) return 'Today';
  if (date >= yesterday) return 'Yesterday';
  if (date >= weekAgo) return 'Last 7 Days';
  if (date >= monthAgo) return 'Last 30 Days';
  return 'Older';
}
ChatSidebar function · typescript · L32-L180 (149 LOC)
src/components/chat/ChatSidebar.tsx
export default function ChatSidebar({ isOpen, onClose }: ChatSidebarProps) {
  const conversations = useChatStore((s) => s.conversations);
  const activeConversationId = useChatStore((s) => s.activeConversationId);
  const createConversation = useChatStore((s) => s.createConversation);
  const switchConversation = useChatStore((s) => s.switchConversation);
  const deleteConversation = useChatStore((s) => s.deleteConversation);

  // Lock body scroll when open
  useEffect(() => {
    if (isOpen) {
      document.body.style.overflow = 'hidden';
    }
    return () => {
      document.body.style.overflow = '';
    };
  }, [isOpen]);

  // Escape key to close
  const handleKeyDown = useCallback(
    (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose();
    },
    [onClose]
  );

  useEffect(() => {
    if (isOpen) document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  }, [isOpen, handleKeyDown]);

  functio
Repobility · severity-and-effort ranking · https://repobility.com
handleNewChat function · typescript · L62-L65 (4 LOC)
src/components/chat/ChatSidebar.tsx
  function handleNewChat() {
    createConversation();
    onClose();
  }
handleSelectConversation function · typescript · L67-L70 (4 LOC)
src/components/chat/ChatSidebar.tsx
  function handleSelectConversation(id: string) {
    switchConversation(id);
    onClose();
  }
handleDeleteConversation function · typescript · L72-L75 (4 LOC)
src/components/chat/ChatSidebar.tsx
  function handleDeleteConversation(e: React.MouseEvent, id: string) {
    e.stopPropagation();
    deleteConversation(id);
  }
FileUploadButton function · typescript · L13-L84 (72 LOC)
src/components/chat/FileUploadButton.tsx
export default function FileUploadButton({
  onFileContent,
  accept = '.csv,.json,.txt',
  disabled = false,
}: FileUploadButtonProps) {
  const inputRef = useRef<HTMLInputElement>(null);

  function handleClick() {
    inputRef.current?.click();
  }

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (!file) return;

    // Check file size (max 500KB)
    if (file.size > 500 * 1024) {
      alert('File too large. Maximum size is 500KB.');
      return;
    }

    const reader = new FileReader();
    reader.onload = (event) => {
      const content = event.target?.result as string;
      onFileContent(content, file.name);
      // Reset input so same file can be selected again
      if (inputRef.current) {
        inputRef.current.value = '';
      }
    };
    reader.onerror = () => {
      alert('Failed to read file. Please try again.');
    };
    reader.readAsText(file);
  }

  return (
    <>
      <Input
        ref={in
handleClick function · typescript · L20-L22 (3 LOC)
src/components/chat/FileUploadButton.tsx
  function handleClick() {
    inputRef.current?.click();
  }
handleChange function · typescript · L24-L47 (24 LOC)
src/components/chat/FileUploadButton.tsx
  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (!file) return;

    // Check file size (max 500KB)
    if (file.size > 500 * 1024) {
      alert('File too large. Maximum size is 500KB.');
      return;
    }

    const reader = new FileReader();
    reader.onload = (event) => {
      const content = event.target?.result as string;
      onFileContent(content, file.name);
      // Reset input so same file can be selected again
      if (inputRef.current) {
        inputRef.current.value = '';
      }
    };
    reader.onerror = () => {
      alert('Failed to read file. Please try again.');
    };
    reader.readAsText(file);
  }
normalizeWorkoutSet function · typescript · L89-L110 (22 LOC)
src/components/history/HistoryOverlay.tsx
function normalizeWorkoutSet(row: WorkoutSetRow): WorkoutSet {
  const exerciseRef = Array.isArray(row.exercises) ? row.exercises[0] : row.exercises;
  return {
    id: row.id,
    exercise_id: row.exercise_id,
    set_number: row.set_number,
    weight: row.weight,
    reps: row.reps,
    time: row.time,
    distance: row.distance ?? null,
    is_warmup: !!row.is_warmup,
    is_completed: row.is_completed !== false,
    is_split_lr: !!row.is_split_lr,
    left_weight: row.left_weight ?? null,
    left_reps: row.left_reps ?? null,
    right_weight: row.right_weight ?? null,
    right_reps: row.right_reps ?? null,
    exercises: {
      name: exerciseRef?.name || 'Exercise',
    },
  };
}
normalizeWorkoutRows function · typescript · L112-L121 (10 LOC)
src/components/history/HistoryOverlay.tsx
function normalizeWorkoutRows(rows: HistoryWorkoutRow[]): HistoryWorkout[] {
  return rows.map((row) => ({
    id: row.id,
    name: row.name,
    date: row.date,
    start_time: row.start_time,
    end_time: row.end_time,
    sets: (row.sets ?? []).map((set) => normalizeWorkoutSet(set)),
  }));
}
Want this analysis on your repo? https://repobility.com/scan/
setInitialMonthFromWorkouts function · typescript · L154-L161 (8 LOC)
src/components/history/HistoryOverlay.tsx
  function setInitialMonthFromWorkouts(rows: HistoryWorkout[]) {
    if (monthInitializedRef.current || rows.length === 0) return;
    const latestDate = new Date(rows[0].date);
    if (!Number.isNaN(latestDate.getTime())) {
      setCurrentMonth(new Date(latestDate.getFullYear(), latestDate.getMonth(), 1));
    }
    monthInitializedRef.current = true;
  }
load function · typescript · L244-L247 (4 LOC)
src/components/history/HistoryOverlay.tsx
    async function load() {
      await loadWorkouts();
      setLoading(false);
    }
makeLocalSetId function · typescript · L282-L284 (3 LOC)
src/components/history/HistoryOverlay.tsx
  function makeLocalSetId(exerciseId: string): string {
    return `new-${exerciseId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  }
makeTemporaryExerciseId function · typescript · L286-L288 (3 LOC)
src/components/history/HistoryOverlay.tsx
  function makeTemporaryExerciseId(): string {
    return `new-exercise-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  }
addExerciseGroup function · typescript · L290-L313 (24 LOC)
src/components/history/HistoryOverlay.tsx
  function addExerciseGroup(initialName?: string) {
    const promptedName = initialName ?? window.prompt('Exercise name');
    const exerciseName = (promptedName || '').trim();
    if (!exerciseName) return;

    const exerciseId = makeTemporaryExerciseId();
    const newDraft: EditSetDraft = {
      localId: makeLocalSetId(exerciseId),
      setId: null,
      exerciseId,
      exerciseName,
      weight: '',
      reps: '',
      isSplit: false,
      time: null,
    };

    setEditExerciseOrder((prev) => [...prev, exerciseId]);
    setEditExerciseNames((prev) => ({ ...prev, [exerciseId]: exerciseName }));
    setEditSetDraftsByExercise((prev) => ({
      ...prev,
      [exerciseId]: [newDraft],
    }));
  }
closeEditOverlay function · typescript · L315-L321 (7 LOC)
src/components/history/HistoryOverlay.tsx
  function closeEditOverlay() {
    if (savingEdits || deleting === editingWorkoutId) return;
    setEditingWorkoutId(null);
    setEditExerciseOrder([]);
    setEditExerciseNames({});
    setEditSetDraftsByExercise({});
  }
updateEditSetField function · typescript · L323-L334 (12 LOC)
src/components/history/HistoryOverlay.tsx
  function updateEditSetField(exerciseId: string, localId: string, field: 'weight' | 'reps', value: string) {
    setEditSetDraftsByExercise((prev) => {
      const next = [...(prev[exerciseId] || [])];
      const idx = next.findIndex((draft) => draft.localId === localId);
      if (idx === -1) return prev;
      next[idx] = { ...next[idx], [field]: value };
      return {
        ...prev,
        [exerciseId]: next,
      };
    });
  }
addSetDraft function · typescript · L336-L352 (17 LOC)
src/components/history/HistoryOverlay.tsx
  function addSetDraft(exerciseId: string, exerciseName: string) {
    const newDraft: EditSetDraft = {
      localId: makeLocalSetId(exerciseId),
      setId: null,
      exerciseId,
      exerciseName,
      weight: '',
      reps: '',
      isSplit: false,
      time: null,
    };

    setEditSetDraftsByExercise((prev) => ({
      ...prev,
      [exerciseId]: [...(prev[exerciseId] || []), newDraft],
    }));
  }
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
removeSetDraft function · typescript · L354-L359 (6 LOC)
src/components/history/HistoryOverlay.tsx
  function removeSetDraft(exerciseId: string, localId: string) {
    setEditSetDraftsByExercise((prev) => ({
      ...prev,
      [exerciseId]: (prev[exerciseId] || []).filter((draft) => draft.localId !== localId),
    }));
  }
moveSetDraft function · typescript · L361-L379 (19 LOC)
src/components/history/HistoryOverlay.tsx
  function moveSetDraft(exerciseId: string, localId: string, direction: 'up' | 'down') {
    setEditSetDraftsByExercise((prev) => {
      const current = [...(prev[exerciseId] || [])];
      const idx = current.findIndex((draft) => draft.localId === localId);
      if (idx === -1) return prev;

      const target = direction === 'up' ? idx - 1 : idx + 1;
      if (target < 0 || target >= current.length) return prev;

      const temp = current[idx];
      current[idx] = current[target];
      current[target] = temp;

      return {
        ...prev,
        [exerciseId]: current,
      };
    });
  }
applyDateToTimestamp function · typescript · L381-L387 (7 LOC)
src/components/history/HistoryOverlay.tsx
  function applyDateToTimestamp(originalIso: string, targetDate: string): string {
    const original = new Date(originalIso);
    const [year, month, day] = targetDate.split('-').map((v) => Number.parseInt(v, 10));
    const updated = new Date(original);
    updated.setUTCFullYear(year, month - 1, day);
    return updated.toISOString();
  }
parseNullableNumber function · typescript · L389-L394 (6 LOC)
src/components/history/HistoryOverlay.tsx
  function parseNullableNumber(value: string): number | null {
    const trimmed = value.trim();
    if (!trimmed) return null;
    const parsed = Number.parseFloat(trimmed);
    return Number.isFinite(parsed) ? parsed : null;
  }
parseNullableInt function · typescript · L396-L401 (6 LOC)
src/components/history/HistoryOverlay.tsx
  function parseNullableInt(value: string): number | null {
    const trimmed = value.trim();
    if (!trimmed) return null;
    const parsed = Number.parseInt(trimmed, 10);
    return Number.isFinite(parsed) ? parsed : null;
  }
handleSaveEdits function · typescript · L403-L612 (210 LOC)
src/components/history/HistoryOverlay.tsx
  async function handleSaveEdits() {
    if (!editingWorkout) return;

    setSavingEdits(true);
    try {
      const trimmedName = editName.trim();
      const autoNameDateInput = editDate ? `${editDate}T12:00:00.000Z` : editingWorkout.date;
      const nextName = trimmedName || formatAutoWorkoutName(autoNameDateInput);

      const workoutUpdates: {
        name?: string;
        date?: string;
        start_time?: string;
        end_time?: string | null;
      } = {};

      if (nextName !== (editingWorkout.name || formatAutoWorkoutName(editingWorkout.date))) {
        workoutUpdates.name = nextName;
      }

      const currentDate = editingWorkout.date.split('T')[0];
      if (editDate && editDate !== currentDate) {
        const nextStart = applyDateToTimestamp(editingWorkout.start_time, editDate);
        workoutUpdates.date = nextStart;
        workoutUpdates.start_time = nextStart;

        if (editingWorkout.end_time) {
          const originalStartMs = new Date(editingWork
handleDelete function · typescript · L614-L630 (17 LOC)
src/components/history/HistoryOverlay.tsx
  async function handleDelete(workoutId: string, askConfirm = true) {
    if (askConfirm && !window.confirm('Delete this workout? This cannot be undone.')) return;

    setDeleting(workoutId);
    const { error } = await supabase.from('workouts').delete().eq('id', workoutId);

    if (!error) {
      setWorkouts((prev) => prev.filter((w) => w.id !== workoutId));
      if (editingWorkoutId === workoutId) {
        setEditingWorkoutId(null);
        setEditExerciseOrder([]);
        setEditExerciseNames({});
        setEditSetDraftsByExercise({});
      }
    }
    setDeleting(null);
  }
getWorkoutsForDate function · typescript · L632-L634 (3 LOC)
src/components/history/HistoryOverlay.tsx
  function getWorkoutsForDate(dateStr: string): HistoryWorkout[] {
    return workouts.filter((w) => toDateKey(w.date) === dateStr);
  }
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
formatDateKey function · typescript · L636-L638 (3 LOC)
src/components/history/HistoryOverlay.tsx
  function formatDateKey(year: number, month: number, day: number): string {
    return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
  }
toDateKey function · typescript · L640-L650 (11 LOC)
src/components/history/HistoryOverlay.tsx
  function toDateKey(value: string): string {
    const parsed = new Date(value);
    if (!Number.isNaN(parsed.getTime())) {
      return formatDateKey(parsed.getFullYear(), parsed.getMonth(), parsed.getDate());
    }

    const match = value.match(/^(\d{4})-(\d{2})-(\d{2})/);
    if (match) return `${match[1]}-${match[2]}-${match[3]}`;

    return value.split('T')[0];
  }
toDateAtNoonUtc function · typescript · L652-L654 (3 LOC)
src/components/history/HistoryOverlay.tsx
  function toDateAtNoonUtc(dateKey: string): string {
    return `${dateKey}T12:00:00.000Z`;
  }
‹ prevpage 3 / 6next ›