All files / Bandstand/src/app/(band)/activity page.tsx

0% Statements 0/118
0% Branches 0/1
0% Functions 0/1
0% Lines 0/118

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138                                                                                                                                                                                                                                                                                   
export const dynamic = 'force-dynamic'
 
import Link from 'next/link'
import {prisma} from '@/lib/db'
import {displayName} from '@/lib/songs'
import {avatarUrl} from '@/lib/avatars'
import Avatar from '@/components/Avatar'
import {pageSession} from '@/lib/guard'
 
export const metadata = {title: 'Recent changes'}
 
const dayFmt = (timeZone: string) =>
  new Intl.DateTimeFormat('en-US', {
    weekday: 'long',
    month: 'short',
    day: 'numeric',
    year: 'numeric',
    timeZone,
  })
const timeFmt = (timeZone: string) =>
  new Intl.DateTimeFormat('en-US', {
    hour: 'numeric',
    minute: '2-digit',
    timeZone,
  })
 
function hrefFor(
  a: {
    targetType: string
    targetId: string | null
    action: string
  },
  scheduling: boolean,
) {
  if (!a.targetId) return null
  if (a.targetType === 'song')
    return a.action.startsWith('chart.')
      ? `/songs/${a.targetId}/history`
      : `/songs/${a.targetId}`
  if (a.targetType === 'setlist' && a.action !== 'setlist.delete')
    return `/setlists/${a.targetId}`
  if (a.targetType === 'rehearsal') return '/rehearsals'
  if (a.targetType === 'availability') return scheduling ? '/rehearsals' : null
  return null
}
 
/** Who changed what, newest first — every save in the app lands here. */
export default async function ActivityPage() {
  const {band} = await pageSession()
  const day = dayFmt(band.timezone)
  const time = timeFmt(band.timezone)
  const rows = await prisma.activity.findMany({
    // This band's changes, and personal ones (photo, password, shared
    // availability) by people in it
    where: {
      OR: [
        {bandId: band.id},
        {
          bandId: null,
          user: {memberships: {some: {bandId: band.id}}},
        },
      ],
    },
    orderBy: {createdAt: 'desc'},
    take: 200,
    include: {
      user: {
        select: {
          id: true,
          name: true,
          displayName: true,
          email: true,
          avatarAt: true,
        },
      },
    },
  })
  const groups: {day: string; rows: typeof rows}[] = []
  for (const r of rows) {
    const d = day.format(r.createdAt)
    if (groups[groups.length - 1]?.day !== d) groups.push({day: d, rows: []})
    groups[groups.length - 1].rows.push(r)
  }
  return (
    <main className="mx-auto max-w-3xl px-4 pb-10 pt-5">
      <h1 className="text-3xl font-extrabold">Recent changes</h1>
      <p className="mt-1 text-muted">
        Everything anyone has changed, newest first.
      </p>
      {!rows.length && <p className="py-8 text-muted">Nothing yet.</p>}
      {groups.map((g) => (
        <section key={g.day} className="mt-6">
          <h2 className="mb-1 text-xs font-bold uppercase tracking-widest text-muted">
            {g.day}
          </h2>
          <ul>
            {g.rows.map((r) => {
              const href = hrefFor(r, band.scheduling)
              const body = (
                <>
                  <span className="w-16 shrink-0 font-mono text-xs text-faint">
                    {time.format(r.createdAt)}
                  </span>
                  <Avatar
                    name={displayName(r.user)}
                    src={r.user ? avatarUrl(r.user) : null}
                    size={24}
                    className="self-center"
                  />
                  <span>
                    <strong>{displayName(r.user)}</strong> {r.summary}
                  </span>
                </>
              )
              return (
                <li key={r.id} className="border-t border-line">
                  {href ? (
                    <Link
                      href={href}
                      className="flex min-h-12 items-baseline gap-3 py-2.5 no-underline hover:bg-panel-2"
                    >
                      {body}
                    </Link>
                  ) : (
                    <div className="flex min-h-12 items-baseline gap-3 py-2.5">
                      {body}
                    </div>
                  )}
                </li>
              )
            })}
          </ul>
        </section>
      ))}
    </main>
  )
}