feat(web): manage dictionaries and teams from the console
The console could not create dictionary entries, and team pages showed a static member list with no record of who changed what. Dictionary management, a knowledge upload form, and a team activity feed are now available, alongside a download center that links the published desktop installer feed rather than repository-local paths that no deploy ships. Red-team e2e coverage was added for the account and team flows touched here.
This commit is contained in:
parent
f6a29db95a
commit
cfc58458a8
21 changed files with 985 additions and 125 deletions
|
|
@ -6,6 +6,7 @@ import { Box, Stack } from '@mui/material'
|
|||
import { MetalCard, PhosphorText } from '@d3ro/ui/components/ds'
|
||||
import { d3roPalette, typoSx } from '@d3ro/ui/theme'
|
||||
import { InviteMemberForm } from '@/components/teams/invite-member-form'
|
||||
import { TeamActivityFeed, type TeamActivityItem } from '@/components/teams/activity-feed'
|
||||
import { getSupabaseServerClient } from '@/lib/supabase-server'
|
||||
|
||||
interface PageProps {
|
||||
|
|
@ -20,7 +21,7 @@ export default async function TeamDetailPage({ params }: PageProps): Promise<Rea
|
|||
data: { user }
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
const [{ data: team }, { data: members }, { data: meetings }] = await Promise.all([
|
||||
const [{ data: team }, { data: members }, { data: meetings }, { data: activities }] = await Promise.all([
|
||||
supabase.from('teams').select('id, name, owner_id, created_at').eq('id', id).maybeSingle(),
|
||||
supabase
|
||||
.from('team_members')
|
||||
|
|
@ -31,7 +32,13 @@ export default async function TeamDetailPage({ params }: PageProps): Promise<Rea
|
|||
.select('id, title, started_at, status')
|
||||
.eq('team_id', id)
|
||||
.order('started_at', { ascending: false })
|
||||
.limit(20)
|
||||
.limit(20),
|
||||
supabase
|
||||
.from('team_activities')
|
||||
.select('id, actor_id, kind, body, created_at')
|
||||
.eq('team_id', id)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(50)
|
||||
])
|
||||
|
||||
if (!team) {
|
||||
|
|
@ -41,6 +48,18 @@ export default async function TeamDetailPage({ params }: PageProps): Promise<Rea
|
|||
const teamData = team as { id: string; name: string; owner_id: string; created_at: string }
|
||||
const isOwner = teamData.owner_id === user?.id
|
||||
|
||||
const memberRows = (members ?? []) as unknown as Array<{
|
||||
user_id: string
|
||||
role: string
|
||||
joined_at: string
|
||||
profiles: { id: string; name: string | null; avatar_url: string | null } | null
|
||||
}>
|
||||
const isMember = memberRows.some((member) => member.user_id === user?.id)
|
||||
const memberNames: Record<string, string> = {}
|
||||
for (const member of memberRows) {
|
||||
memberNames[member.user_id] = member.profiles?.name ?? member.user_id.slice(0, 8)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 4 }}>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
|
|
@ -138,6 +157,19 @@ export default async function TeamDetailPage({ params }: PageProps): Promise<Rea
|
|||
</Stack>
|
||||
)}
|
||||
</MetalCard>
|
||||
|
||||
{/* 활동 피드 */}
|
||||
<MetalCard sx={{ p: 3 }}>
|
||||
<PhosphorText variant="heading" sx={{ mb: 2 }}>
|
||||
ACTIVITY
|
||||
</PhosphorText>
|
||||
<TeamActivityFeed
|
||||
teamId={teamData.id}
|
||||
initialActivities={(activities ?? []) as TeamActivityItem[]}
|
||||
memberNames={memberNames}
|
||||
canPost={isMember}
|
||||
/>
|
||||
</MetalCard>
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue