fix: use admin R2 avatars for operator activity..
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
MonitorPlay,
|
MonitorPlay,
|
||||||
Bell,
|
Bell,
|
||||||
|
BookOpen,
|
||||||
Boxes,
|
Boxes,
|
||||||
ChartNoAxesColumnIncreasing,
|
ChartNoAxesColumnIncreasing,
|
||||||
FileKey,
|
FileKey,
|
||||||
@@ -99,7 +100,7 @@ export const navGroups = [
|
|||||||
{ label: '라이센스', icon: FileKey },
|
{ label: '라이센스', icon: FileKey },
|
||||||
{ label: '공지사항', icon: Megaphone },
|
{ label: '공지사항', icon: Megaphone },
|
||||||
{ label: '약관', icon: ScrollText },
|
{ label: '약관', icon: ScrollText },
|
||||||
{ label: '매뉴얼', icon: ScrollText }
|
{ label: '매뉴얼', icon: BookOpen }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,6 +19,27 @@ type ManualForm = Pick<ManualDocument, 'appKey' | 'title' | 'body' | 'isPublishe
|
|||||||
|
|
||||||
const emptyForm: ManualForm = { appKey: '', title: '', body: '', isPublished: true, sortOrder: 0 };
|
const emptyForm: ManualForm = { appKey: '', title: '', body: '', isPublished: true, sortOrder: 0 };
|
||||||
|
|
||||||
|
function ManualCreator({ document }: { document: ManualDocument }) {
|
||||||
|
const [failed, setFailed] = useState(false);
|
||||||
|
const name = document.createdByName || document.createdByEmail || '-';
|
||||||
|
const avatarUrl = document.createdByAvatarUrl || '';
|
||||||
|
const initial = name.trim().charAt(0).toUpperCase() || 'A';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
|
{avatarUrl && !failed ? (
|
||||||
|
<img src={avatarUrl} alt="" className="size-7 shrink-0 rounded-full border border-border bg-muted object-cover" onError={() => setFailed(true)} />
|
||||||
|
) : (
|
||||||
|
<span className="inline-flex size-7 shrink-0 items-center justify-center rounded-full border border-border bg-muted text-xs font-semibold text-muted-foreground">{initial}</span>
|
||||||
|
)}
|
||||||
|
<div className="min-w-0">
|
||||||
|
<p className="truncate text-sm font-medium text-foreground">{name}</p>
|
||||||
|
<p className="mt-0.5 whitespace-nowrap text-xs text-muted-foreground">{formatDateTime(document.createdAt)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function ManualsPage() {
|
export function ManualsPage() {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [documents, setDocuments] = useState<ManualDocument[]>([]);
|
const [documents, setDocuments] = useState<ManualDocument[]>([]);
|
||||||
@@ -92,7 +113,7 @@ export function ManualsPage() {
|
|||||||
{ key: 'body', header: '본문', flexible: true, className: 'px-5 py-3', cell: (row) => <p className="truncate">{row.body}</p> },
|
{ key: 'body', header: '본문', flexible: true, className: 'px-5 py-3', cell: (row) => <p className="truncate">{row.body}</p> },
|
||||||
{ key: 'published', header: '공개', width: 100, className: 'px-5 py-3', cell: (row) => <StatusBadge status={row.isPublished ? '공개' : '비공개'} tone={row.isPublished ? 'green' : 'neutral'} /> },
|
{ key: 'published', header: '공개', width: 100, className: 'px-5 py-3', cell: (row) => <StatusBadge status={row.isPublished ? '공개' : '비공개'} tone={row.isPublished ? 'green' : 'neutral'} /> },
|
||||||
{ key: 'sortOrder', header: '순서', width: 80, className: 'px-5 py-3 text-center', cell: (row) => row.sortOrder },
|
{ key: 'sortOrder', header: '순서', width: 80, className: 'px-5 py-3 text-center', cell: (row) => row.sortOrder },
|
||||||
{ key: 'createdAt', header: '등록일', width: 160, className: 'px-5 py-3 text-xs text-muted-foreground', cell: (row) => formatDateTime(row.createdAt) },
|
{ key: 'createdAt', header: '생성', width: 190, className: 'px-5 py-3', cell: (row) => <ManualCreator document={row} /> },
|
||||||
{ key: 'actions', header: '', className: 'px-5 py-3', cell: (row) => <div className="relative flex justify-end"><Button variant="ghost" className="size-8 px-0" title="메뉴" aria-label="메뉴" onClick={(event) => { event.stopPropagation(); const rect = event.currentTarget.getBoundingClientRect(); setMenu((previous) => previous?.id === row.id ? null : { id: row.id, x: rect.right, y: rect.bottom + 6 }); }}><MoreHorizontal className="size-4" /></Button></div> },
|
{ key: 'actions', header: '', className: 'px-5 py-3', cell: (row) => <div className="relative flex justify-end"><Button variant="ghost" className="size-8 px-0" title="메뉴" aria-label="메뉴" onClick={(event) => { event.stopPropagation(); const rect = event.currentTarget.getBoundingClientRect(); setMenu((previous) => previous?.id === row.id ? null : { id: row.id, x: rect.right, y: rect.bottom + 6 }); }}><MoreHorizontal className="size-4" /></Button></div> },
|
||||||
], []);
|
], []);
|
||||||
|
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ export type ManualDocument = {
|
|||||||
body: string;
|
body: string;
|
||||||
isPublished: boolean;
|
isPublished: boolean;
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
|
createdById?: string | null;
|
||||||
|
createdByName?: string | null;
|
||||||
|
createdByEmail?: string | null;
|
||||||
|
createdByAvatarUrl?: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user