← back to davidaoliver__Imperial-Sporthorses

Function bodies 104 total

All specs Real LLM only Function bodies
handleSend function · javascript · L101-L128 (28 LOC)
src/pages/Chat.jsx
  async function handleSend(e, isAlert = false) {
    e.preventDefault()
    const trimmed = newMessage.trim()
    if (!trimmed || !profile) return

    if (isAlert && !confirm('Send this as an ALERT to everyone?')) return

    setSending(true)
    setNewMessage('')

    const row = { user_id: profile.id, content: trimmed }
    if (isAlert) row.is_alert = true

    const { error } = await supabase.from('messages').insert(row)

    if (error) {
      console.error('Error sending message:', error)
      alert('Failed to send: ' + error.message)
      setNewMessage(trimmed)
    } else if (isAlert) {
      // Send real push notification to all devices via Netlify Function
      sendPushAlert('🚨 Barn Alert', trimmed).catch((err) =>
        console.error('Push send error:', err)
      )
    }
    setSending(false)
    inputRef.current?.focus()
  }
formatTimestamp function · javascript · L130-L135 (6 LOC)
src/pages/Chat.jsx
  function formatTimestamp(dateStr) {
    const date = new Date(dateStr)
    if (isToday(date)) return format(date, 'h:mm a')
    if (isYesterday(date)) return 'Yesterday ' + format(date, 'h:mm a')
    return format(date, 'MMM d, h:mm a')
  }
getSenderName function · javascript · L137-L140 (4 LOC)
src/pages/Chat.jsx
  function getSenderName(msg) {
    if (msg.sender?.display_name) return msg.sender.display_name
    return 'Unknown'
  }
CompleteProfilePage function · javascript · L5-L119 (115 LOC)
src/pages/CompleteProfilePage.jsx
export default function CompleteProfilePage() {
  const { session, updateDisplayName, profile } = useAuth()
  const [name, setName] = useState('')
  const [error, setError] = useState('')
  const [saving, setSaving] = useState(false)
  const [retrying, setRetrying] = useState(true)

  // Give auth context a few seconds to finish loading profile
  useEffect(() => {
    const timer = setTimeout(() => setRetrying(false), 3000)
    return () => clearTimeout(timer)
  }, [])

  // If profile loads with a display_name, stop retrying immediately
  useEffect(() => {
    if (profile?.display_name) setRetrying(false)
  }, [profile])

  async function handleSubmit(e) {
    e.preventDefault()
    const trimmed = name.trim()
    if (!trimmed) {
      setError('Please enter your display name.')
      return
    }
    if (trimmed.length < 2) {
      setError('Name must be at least 2 characters.')
      return
    }
    setSaving(true)
    try {
      const timeout = new Promise((_, reject) =>
        
handleSubmit function · javascript · L23-L48 (26 LOC)
src/pages/CompleteProfilePage.jsx
  async function handleSubmit(e) {
    e.preventDefault()
    const trimmed = name.trim()
    if (!trimmed) {
      setError('Please enter your display name.')
      return
    }
    if (trimmed.length < 2) {
      setError('Name must be at least 2 characters.')
      return
    }
    setSaving(true)
    try {
      const timeout = new Promise((_, reject) =>
        setTimeout(() => reject(new Error('Request timed out')), 10000)
      )
      await Promise.race([updateDisplayName(trimmed), timeout])
    } catch (err) {
      setError(err.message === 'Request timed out'
        ? 'Request timed out. Check your connection and try again.'
        : 'Failed to save. Please try again.')
      console.error('[CompleteProfile] Error:', err)
    } finally {
      setSaving(false)
    }
  }
generateMainBarnStalls function · javascript · L7-L17 (11 LOC)
src/pages/FacilityMap.jsx
function generateMainBarnStalls() {
  const stalls = []
  const bx = 200, by = 340, sw = 27, sh = 35, gap = 2
  for (let i = 0; i < 8; i++) {
    stalls.push({ id: `mb-b${i+1}`, label: `${i+1}`, type: 'stall', x: bx+10+i*(sw+gap), y: by+170-sh-6, w: sw, h: sh, parent: 'main-barn', dbName: `Stall ${i+1}` })
  }
  for (let i = 0; i < 4; i++) {
    stalls.push({ id: `mb-t${i+1}`, label: `${i+9}`, type: 'stall', x: bx+10+i*(sw+gap), y: by+22, w: sw, h: sh, parent: 'main-barn', dbName: `Stall ${i+9}` })
  }
  return stalls
}
generateSixStallBarnStalls function · javascript · L20-L30 (11 LOC)
src/pages/FacilityMap.jsx
function generateSixStallBarnStalls() {
  const stalls = []
  const bx = 10, by = 170, sw = 40, sh = 35, gap = 3
  for (let i = 0; i < 3; i++) {
    stalls.push({ id: `sb-t${i+1}`, label: `${i+13}`, type: 'stall', x: bx+10+i*(sw+gap), y: by+22, w: sw, h: sh, parent: '6stall', dbName: `Stall ${i+13}` })
  }
  for (let i = 0; i < 3; i++) {
    stalls.push({ id: `sb-b${i+1}`, label: `${i+16}`, type: 'stall', x: bx+10+i*(sw+gap), y: by+130-sh-6, w: sw, h: sh, parent: '6stall', dbName: `Stall ${i+16}` })
  }
  return stalls
}
Repobility · MCP-ready · https://repobility.com
generateFourStallBarnStalls function · javascript · L33-L43 (11 LOC)
src/pages/FacilityMap.jsx
function generateFourStallBarnStalls() {
  const stalls = []
  const bx = 310, by = 130, sw = 38, sh = 32, gap = 3
  for (let i = 0; i < 2; i++) {
    stalls.push({ id: `fb-t${i+1}`, label: `${i+19}`, type: 'stall', x: bx+10+i*(sw+gap), y: by+22, w: sw, h: sh, parent: '4stall', dbName: `Stall ${i+19}` })
  }
  for (let i = 0; i < 2; i++) {
    stalls.push({ id: `fb-b${i+1}`, label: `${i+21}`, type: 'stall', x: bx+10+i*(sw+gap), y: by+110-sh-6, w: sw, h: sh, parent: '4stall', dbName: `Stall ${i+21}` })
  }
  return stalls
}
resetZoom function · javascript · L104-L106 (3 LOC)
src/pages/FacilityMap.jsx
  function resetZoom() {
    setViewBox({ x: 0, y: 0, w: 1000, h: 700 })
  }
zoomBy function · javascript · L108-L120 (13 LOC)
src/pages/FacilityMap.jsx
  function zoomBy(factor) {
    setViewBox((v) => {
      const newW = Math.min(MAX_ZOOM_W, Math.max(MIN_ZOOM_W, v.w * factor))
      const newH = newW * 0.7
      const cx = v.x + v.w / 2
      const cy = v.y + v.h / 2
      return {
        x: Math.max(-300, Math.min(1000 - newW + 300, cx - newW / 2)),
        y: Math.max(-300, Math.min(700 - newH + 300, cy - newH / 2)),
        w: newW, h: newH,
      }
    })
  }
getTouchDist function · javascript · L122-L124 (3 LOC)
src/pages/FacilityMap.jsx
  function getTouchDist(t1, t2) {
    return Math.hypot(t1.clientX - t2.clientX, t1.clientY - t2.clientY)
  }
getTouchCenter function · javascript · L126-L128 (3 LOC)
src/pages/FacilityMap.jsx
  function getTouchCenter(t1, t2) {
    return { x: (t1.clientX + t2.clientX) / 2, y: (t1.clientY + t2.clientY) / 2 }
  }
handleTouchStart function · javascript · L130-L140 (11 LOC)
src/pages/FacilityMap.jsx
  function handleTouchStart(e) {
    if (editMode) return
    if (e.touches.length === 2) {
      e.preventDefault()
      lastTouchDist.current = getTouchDist(e.touches[0], e.touches[1])
      lastTouchCenter.current = getTouchCenter(e.touches[0], e.touches[1])
    } else if (e.touches.length === 1) {
      setIsPanning(true)
      setPanStart({ x: e.touches[0].clientX, y: e.touches[0].clientY, vx: viewBox.x, vy: viewBox.y })
    }
  }
handleTouchMove function · javascript · L142-L174 (33 LOC)
src/pages/FacilityMap.jsx
  function handleTouchMove(e) {
    if (editMode) return
    if (e.touches.length === 2 && lastTouchDist.current) {
      e.preventDefault()
      const newDist = getTouchDist(e.touches[0], e.touches[1])
      const scale = lastTouchDist.current / newDist
      lastTouchDist.current = newDist
      setViewBox((v) => {
        const newW = Math.min(MAX_ZOOM_W, Math.max(MIN_ZOOM_W, v.w * scale))
        const newH = newW * 0.7
        const cx = v.x + v.w / 2
        const cy = v.y + v.h / 2
        return {
          x: Math.max(-200, Math.min(1000 - newW + 200, cx - newW / 2)),
          y: Math.max(-200, Math.min(700 - newH + 200, cy - newH / 2)),
          w: newW, h: newH,
        }
      })
    } else if (e.touches.length === 1 && isPanning && panStart) {
      const container = mapContainerRef.current
      if (!container) return
      const rect = container.getBoundingClientRect()
      const scaleX = viewBox.w / rect.width
      const scaleY = viewBox.h / rect.height
      const
handleTouchEnd function · javascript · L176-L185 (10 LOC)
src/pages/FacilityMap.jsx
  function handleTouchEnd(e) {
    if (e.touches.length < 2) {
      lastTouchDist.current = null
      lastTouchCenter.current = null
    }
    if (e.touches.length === 0) {
      setIsPanning(false)
      setPanStart(null)
    }
  }
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
toSvg function · javascript · L221-L228 (8 LOC)
src/pages/FacilityMap.jsx
  function toSvg(cx, cy) {
    const svg = svgRef.current
    if (!svg) return { x: 0, y: 0 }
    const pt = svg.createSVGPoint()
    pt.x = cx; pt.y = cy
    const p = pt.matrixTransform(svg.getScreenCTM().inverse())
    return { x: p.x, y: p.y }
  }
onAreaPointerDown function · javascript · L231-L237 (7 LOC)
src/pages/FacilityMap.jsx
  function onAreaPointerDown(e, idx) {
    if (!editMode) return
    e.stopPropagation(); e.preventDefault()
    const { x, y } = toSvg(e.clientX, e.clientY)
    const a = mapAreas[idx]
    setDragInfo({ idx, ox: x - a.x, oy: y - a.y, mode: 'move' })
  }
onHandleDown function · javascript · L240-L245 (6 LOC)
src/pages/FacilityMap.jsx
  function onHandleDown(e, idx, handle) {
    if (!editMode) return
    e.stopPropagation(); e.preventDefault()
    const { x, y } = toSvg(e.clientX, e.clientY)
    setDragInfo({ idx, sx: x, sy: y, mode: 'resize', handle, orig: { ...mapAreas[idx] } })
  }
onPointerMove function · javascript · L247-L267 (21 LOC)
src/pages/FacilityMap.jsx
  function onPointerMove(e) {
    if (!dragInfo) return
    const { x, y } = toSvg(e.clientX, e.clientY)
    setMapAreas((prev) => {
      const next = [...prev]
      const a = { ...next[dragInfo.idx] }
      if (dragInfo.mode === 'move') {
        a.x = Math.round(x - dragInfo.ox)
        a.y = Math.round(y - dragInfo.oy)
      } else {
        const dx = x - dragInfo.sx, dy = y - dragInfo.sy, o = dragInfo.orig, h = dragInfo.handle
        if (h.includes('e')) a.w = Math.max(20, Math.round(o.w + dx))
        if (h.includes('w')) { a.x = Math.round(o.x + dx); a.w = Math.max(20, Math.round(o.w - dx)) }
        if (h.includes('s')) a.h = Math.max(15, Math.round(o.h + dy))
        if (h.includes('n')) { a.y = Math.round(o.y + dy); a.h = Math.max(15, Math.round(o.h - dy)) }
      }
      next[dragInfo.idx] = a
      return next
    })
    setHasChanges(true)
  }
saveLayout function · javascript · L272-L285 (14 LOC)
src/pages/FacilityMap.jsx
  async function saveLayout() {
    const rows = mapAreas.map((a, i) => ({
      area_key: a.id, label: a.label, area_type: a.type,
      x: a.x, y: a.y, w: a.w, h: a.h,
      location_id: a.locationId || null, parent_key: a.parent || null, sort_order: i,
    }))
    const { error } = await supabase.from('map_areas').upsert(rows, { onConflict: 'area_key' })
    if (error) {
      console.error('Save layout error:', error)
      alert('Failed to save: ' + error.message)
    } else {
      setHasChanges(false)
    }
  }
findDbLocation function · javascript · L288-L306 (19 LOC)
src/pages/FacilityMap.jsx
  function findDbLocation(area) {
    if (area.locationId) return locations.find((l) => l.id === area.locationId)
    if (area.dbName) {
      // Exact match first
      let loc = locations.find((l) => l.name === area.dbName)
      if (loc) return loc
      // Case-insensitive match
      loc = locations.find((l) => l.name.toLowerCase() === area.dbName.toLowerCase())
      if (loc) return loc
      // Partial match (e.g. "Stall 1" matches "Stall 1" or "stall 1")
      loc = locations.find((l) => l.name.toLowerCase().includes(area.dbName.toLowerCase()) || area.dbName.toLowerCase().includes(l.name.toLowerCase()))
      return loc || null
    }
    // Try matching by label as last resort
    if (area.label) {
      return locations.find((l) => l.name.toLowerCase() === area.label.toLowerCase()) || null
    }
    return null
  }
getHorsesForArea function · javascript · L308-L317 (10 LOC)
src/pages/FacilityMap.jsx
  function getHorsesForArea(area) {
    const loc = findDbLocation(area)
    if (!loc) return []
    // Show horse if it's currently here, OR this is their home stall, OR their assigned pasture
    return horses.filter((h) =>
      h.current_location === loc.id ||
      h.home_stall === loc.id ||
      h.assigned_pasture === loc.id
    )
  }
moveHorse function · javascript · L319-L326 (8 LOC)
src/pages/FacilityMap.jsx
  async function moveHorse(horse, targetLocation) {
    if (targetLocation.type === 'Pasture' && horse.assigned_pasture && horse.assigned_pasture !== targetLocation.id) {
      const ap = locations.find((l) => l.id === horse.assigned_pasture)
      setWarningModal({ horse, targetLocation, assignedPastureName: ap?.name || 'Unknown' })
      return
    }
    await confirmMove(horse, targetLocation)
  }
Repobility analyzer · published findings · https://repobility.com
confirmMove function · javascript · L328-L332 (5 LOC)
src/pages/FacilityMap.jsx
  async function confirmMove(horse, targetLocation) {
    const { error } = await supabase.from('horses').update({ current_location: targetLocation.id }).eq('id', horse.id)
    if (error) console.error('Move error:', error)
    setDraggingHorse(null); setWarningModal(null)
  }
handleAreaTap function · javascript · L335-L349 (15 LOC)
src/pages/FacilityMap.jsx
  function handleAreaTap(area) {
    if (editMode) {
      // In edit mode, select for resize handles only (no popup panel)
      setSelectedArea(selectedArea?.id === area.id ? null : area)
      return
    }
    if (draggingHorse) {
      const loc = findDbLocation(area)
      if (loc && isAdmin) moveHorse(draggingHorse, loc)
      return
    }
    // Pastures always show horse names inline, no tap-to-expand
    if (area.type === 'pasture') return
    setSelectedArea(selectedArea?.id === area.id ? null : area)
  }
handleHorseTap function · javascript · L351-L354 (4 LOC)
src/pages/FacilityMap.jsx
  function handleHorseTap(e, horse) {
    e.stopPropagation()
    if (isAdmin && !editMode) setDraggingHorse(horse)
  }
updateLabel function · javascript · L357-L360 (4 LOC)
src/pages/FacilityMap.jsx
  function updateLabel(val) {
    setMapAreas((p) => p.map((a) => a.id === selectedArea.id ? { ...a, label: val } : a))
    setSelectedArea((p) => ({ ...p, label: val })); setEditLabel(val); setHasChanges(true)
  }
linkLocation function · javascript · L363-L366 (4 LOC)
src/pages/FacilityMap.jsx
  function linkLocation(locId) {
    setMapAreas((p) => p.map((a) => a.id === selectedArea.id ? { ...a, locationId: locId || null } : a))
    setSelectedArea((p) => ({ ...p, locationId: locId || null })); setEditLocationId(locId); setHasChanges(true)
  }
openHorseEdit function · javascript · L48-L57 (10 LOC)
src/pages/FeedRoom.jsx
  function openHorseEdit(horse) {
    setEditForm({
      am_grain: horse.am_grain || '',
      pm_grain: horse.pm_grain || '',
      hay_type: horse.hay_type || '',
      supplements: horse.supplements || '',
      meds_notes: horse.meds_notes || '',
    })
    setEditingHorse(horse)
  }
saveHorseEdit function · javascript · L59-L80 (22 LOC)
src/pages/FeedRoom.jsx
  async function saveHorseEdit(e) {
    e.preventDefault()
    if (!editingHorse) return
    setSaving(true)
    const { error } = await supabase
      .from('horses')
      .update({
        am_grain: editForm.am_grain.trim() || null,
        pm_grain: editForm.pm_grain.trim() || null,
        hay_type: editForm.hay_type.trim() || null,
        supplements: editForm.supplements.trim() || null,
        meds_notes: editForm.meds_notes.trim() || null,
      })
      .eq('id', editingHorse.id)
    setSaving(false)
    if (error) {
      console.error('Error updating horse:', error)
      return
    }
    setEditingHorse(null)
    fetchData()
  }
getExpirationStyle function · javascript · L115-L122 (8 LOC)
src/pages/FeedRoom.jsx
  function getExpirationStyle(dateStr) {
    if (!dateStr) return { class: 'text-neutral-500', label: '' }
    const days = differenceInDays(parseISO(dateStr), new Date())
    if (days < 0) return { class: 'bg-red-900/40 text-red-400', label: 'Expired' }
    if (days <= 14)
      return { class: 'bg-yellow-900/40 text-yellow-400', label: `${days}d left` }
    return { class: 'bg-green-900/40 text-green-400', label: `${days}d left` }
  }
Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
getSvgPoint function · javascript · L125-L133 (9 LOC)
src/pages/FeedRoom.jsx
  function getSvgPoint(e) {
    const svg = bowlSvgRef.current
    if (!svg) return { x: 0, y: 0 }
    const pt = svg.createSVGPoint()
    pt.x = e.clientX; pt.y = e.clientY
    const ctm = svg.getScreenCTM().inverse()
    const svgP = pt.matrixTransform(ctm)
    return { x: svgP.x, y: svgP.y }
  }
onBowlPointerDown function · javascript · L135-L143 (9 LOC)
src/pages/FeedRoom.jsx
  function onBowlPointerDown(e, bowlId) {
    if (!bowlEditMode) return
    e.preventDefault()
    e.stopPropagation()
    const pt = getSvgPoint(e)
    const bowl = bowls.find(b => b.id === bowlId)
    if (!bowl) return
    setDraggingBowl({ id: bowlId, offsetX: pt.x - bowl.x, offsetY: pt.y - bowl.y })
  }
onBowlPointerMove function · javascript · L145-L153 (9 LOC)
src/pages/FeedRoom.jsx
  function onBowlPointerMove(e) {
    if (!draggingBowl) return
    const pt = getSvgPoint(e)
    setBowls(prev => prev.map(b =>
      b.id === draggingBowl.id
        ? { ...b, x: Math.round(pt.x - draggingBowl.offsetX), y: Math.round(pt.y - draggingBowl.offsetY) }
        : b
    ))
  }
onBowlPointerUp function · javascript · L155-L157 (3 LOC)
src/pages/FeedRoom.jsx
  function onBowlPointerUp() {
    setDraggingBowl(null)
  }
saveBowlPositions function · javascript · L159-L164 (6 LOC)
src/pages/FeedRoom.jsx
  async function saveBowlPositions() {
    for (const b of bowls) {
      await supabase.from('feed_bowls').update({ x: b.x, y: b.y }).eq('id', b.id)
    }
    setBowlEditMode(false)
  }
handleAddDelivery function · javascript · L166-L189 (24 LOC)
src/pages/FeedRoom.jsx
  async function handleAddDelivery(e) {
    e.preventDefault()
    if (!newItem.feed_name.trim()) return

    const { error } = await supabase.from('feed_inventory').insert({
      feed_name: newItem.feed_name.trim(),
      quantity: newItem.quantity.trim() || null,
      delivery_date: newItem.delivery_date || null,
      expiration_date: newItem.expiration_date || null,
    })

    if (error) {
      console.error('Error adding delivery:', error)
      return
    }

    setNewItem({
      feed_name: '',
      quantity: '',
      delivery_date: format(new Date(), 'yyyy-MM-dd'),
      expiration_date: '',
    })
    setShowAddModal(false)
  }
LoginPage function · javascript · L3-L43 (41 LOC)
src/pages/LoginPage.jsx
export default function LoginPage() {
  const { signInWithGoogle } = useAuth()

  return (
    <div className="min-h-screen flex flex-col items-center justify-center bg-neutral-950 px-6">
      <div className="bg-neutral-900 border border-neutral-800 rounded-2xl shadow-2xl p-8 w-full max-w-sm text-center">
        <div className="flex justify-center mb-6">
          <img src="/logo.png" alt="Imperial Sporthorses" className="h-28 w-28 object-contain" />
        </div>
        <h1 className="text-2xl font-bold text-amber-400 mb-1">Imperial Sporthorses</h1>
        <p className="text-neutral-400 text-sm mb-8">
          Barn management for Katherine Palmer
        </p>
        <button
          onClick={signInWithGoogle}
          className="w-full flex items-center justify-center gap-3 bg-neutral-800 border border-neutral-700 rounded-xl px-4 py-3 text-neutral-200 font-medium hover:bg-neutral-700 active:bg-neutral-600 transition shadow-sm"
        >
          <svg className="w-5 h-5" view
ScheduleNotes function · javascript · L8-L138 (131 LOC)
src/pages/ScheduleNotes.jsx
export default function ScheduleNotes() {
  const { profile } = useAuth()
  const { notes, loading, createNote, saveNote, flushSave, deleteNote, togglePin } =
    useScheduleNotes(profile?.id)
  const [selectedNote, setSelectedNote] = useState(null)
  const [search, setSearch] = useState('')

  async function handleCreate() {
    const note = await createNote()
    if (note) setSelectedNote(note)
  }

  async function handleDelete(id) {
    const ok = await deleteNote(id)
    if (ok) setSelectedNote(null)
  }

  async function handleBack() {
    await flushSave()
    setSelectedNote(null)
  }

  // Filter notes by search
  const filtered = search.trim()
    ? notes.filter(
        (n) =>
          n.title.toLowerCase().includes(search.toLowerCase()) ||
          n.body.toLowerCase().includes(search.toLowerCase())
      )
    : notes

  const pinned = filtered.filter((n) => n.pinned)
  const unpinned = filtered.filter((n) => !n.pinned)

  if (loading) {
    return (
      <div className
Repobility · MCP-ready · https://repobility.com
handleCreate function · javascript · L15-L18 (4 LOC)
src/pages/ScheduleNotes.jsx
  async function handleCreate() {
    const note = await createNote()
    if (note) setSelectedNote(note)
  }
handleDelete function · javascript · L20-L23 (4 LOC)
src/pages/ScheduleNotes.jsx
  async function handleDelete(id) {
    const ok = await deleteNote(id)
    if (ok) setSelectedNote(null)
  }
handleBack function · javascript · L25-L28 (4 LOC)
src/pages/ScheduleNotes.jsx
  async function handleBack() {
    await flushSave()
    setSelectedNote(null)
  }
NoteCard function · javascript · L140-L162 (23 LOC)
src/pages/ScheduleNotes.jsx
function NoteCard({ note, onTap }) {
  const title = note.title || 'New Note'
  const preview = note.body ? note.body.slice(0, 80).replace(/\n/g, ' ') : 'No additional text'
  const timeAgo = note.updated_at
    ? formatDistanceToNow(new Date(note.updated_at), { addSuffix: true })
    : ''

  return (
    <button
      onClick={onTap}
      className="w-full text-left bg-neutral-900 border border-neutral-800 rounded-xl p-3 active:scale-[0.98] transition-all"
    >
      <div className="flex items-start justify-between gap-2">
        <div className="flex-1 min-w-0">
          <p className="text-sm font-semibold text-neutral-100 truncate">{title}</p>
          <p className="text-xs text-neutral-500 truncate mt-0.5">{preview}</p>
          <p className="text-[10px] text-neutral-600 mt-1">{timeAgo}</p>
        </div>
        {note.pinned && <Pin className="w-3.5 h-3.5 text-amber-500/60 shrink-0 mt-0.5" />}
      </div>
    </button>
  )
}
TaskBoard function · javascript · L47-L384 (338 LOC)
src/pages/TaskBoard.jsx
export default function TaskBoard() {
  const { profile, isAdmin } = useAuth()
  const [tasks, setTasks] = useState([])
  const [users, setUsers] = useState([])
  const [loading, setLoading] = useState(true)
  const [editingTask, setEditingTask] = useState(null)
  const [filterTab, setFilterTab] = useState('active')

  const fetchTasks = useCallback(async () => {
    try {
      console.log('[TaskBoard] Fetching board tasks...')
      // Try with join first
      const { data, error } = await supabase
        .from('tasks')
        .select('*, assigned_user:users!tasks_assigned_to_fkey(display_name)')
        .is('shift', null)
        .order('sort_order', { ascending: true })

      if (error) {
        console.warn('[TaskBoard] Join query failed:', error.message)
        // Fallback without join
        const { data: fallbackData, error: fbErr } = await supabase
          .from('tasks')
          .select('*')
          .is('shift', null)
          .order('sort_order', { ascending: tr
handleTaskTap function · javascript · L113-L139 (27 LOC)
src/pages/TaskBoard.jsx
  async function handleTaskTap(task) {
    let updates
    if (task.status === 'Pending') {
      updates = {
        status: 'In Progress',
        assigned_to: task.assigned_to || profile.id,
      }
    } else if (task.status === 'In Progress') {
      updates = {
        status: 'Done',
        completed_at: new Date().toISOString(),
      }
    } else if (task.status === 'Done') {
      // Undo — revert back to In Progress
      updates = {
        status: 'In Progress',
        completed_at: null,
      }
    }

    const { error } = await supabase
      .from('tasks')
      .update(updates)
      .eq('id', task.id)

    if (error) console.error('Error updating task:', error)
  }
handleReassign function · javascript · L141-L149 (9 LOC)
src/pages/TaskBoard.jsx
  async function handleReassign(taskId, userId) {
    const { error } = await supabase
      .from('tasks')
      .update({ assigned_to: userId || null })
      .eq('id', taskId)

    if (error) console.error('Error reassigning task:', error)
    setEditingTask(null)
  }
handleDeleteTask function · javascript · L151-L154 (4 LOC)
src/pages/TaskBoard.jsx
  async function handleDeleteTask(taskId) {
    const { error } = await supabase.from('tasks').delete().eq('id', taskId)
    if (error) console.error('Error deleting task:', error)
  }
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
getAssigneeName function · javascript · L156-L163 (8 LOC)
src/pages/TaskBoard.jsx
  function getAssigneeName(task) {
    if (task.assigned_user?.display_name) return task.assigned_user.display_name
    if (task.assigned_to) {
      const user = users.find((u) => u.id === task.assigned_to)
      return user?.display_name || 'Unknown'
    }
    return null
  }
groupByDate function · javascript · L117-L135 (19 LOC)
src/pages/WeeklyPlanner.jsx
  function groupByDate(tasks) {
    const grouped = {}
    for (const day of allDays) {
      const key = format(day, 'yyyy-MM-dd')
      grouped[key] = []
    }
    for (const task of tasks) {
      if (grouped[task.task_date]) {
        // Deduplicate: skip if same title+shift already exists for this date
        const isDupe = grouped[task.task_date].some(
          (t) => t.title === task.title && t.shift === task.shift
        )
        if (!isDupe) {
          grouped[task.task_date].push(task)
        }
      }
    }
    setWeekTasks(grouped)
  }
generateTasksForDate function · javascript · L228-L272 (45 LOC)
src/pages/WeeklyPlanner.jsx
  async function generateTasksForDate(dateStr) {
    if (templates.length === 0) return false
    if (generatingRef.current) return false
    generatingRef.current = true

    try {
      // Check if tasks already exist for this date (re-check from DB to avoid races)
      const { data: existing } = await supabase
        .from('tasks')
        .select('id')
        .eq('task_date', dateStr)
        .not('shift', 'is', null)
        .limit(1)
      if (existing && existing.length > 0) return false

      // Look up scheduled staff for this day
      const date = new Date(dateStr + 'T12:00:00')
      const dayOfWeek = date.getDay()
      const staffByShift = {}
      for (const entry of schedule) {
        if (entry.day_of_week === dayOfWeek) {
          if (!staffByShift[entry.shift]) staffByShift[entry.shift] = []
          staffByShift[entry.shift].push(entry.user_id)
        }
      }

      const rows = templates.map((t) => ({
        title: t.title,
        shift: t.shift,
       
‹ prevpage 2 / 3next ›