feat(admin): Phase V2-6 Admin CRM 고도화 — CRUD + 차트 + 결제 + 감사
- DB: audit_log 테이블(diff 포함) + subscriptions.admin_note + super_admin role - Edge Functions 4개: admin-users, admin-subscriptions, admin-payments, admin-audit-log - 공유 유틸: admin-auth.ts(권한 검증), audit.ts(감사로그 기록) - Swagger UI: 독립 정적 페이지 + OpenAPI 3.0 spec - CRUD 페이지: 구독 생성/수정/삭제, role 변경, 감사로그 목록/상세 - recharts: feature별 StackedBar + DAU Line + Top Users HorizontalBar - 결제 이력: DB + Payple API 병행 조회 - 권한: super_admin만 위험 작업, admin은 조회 전용 - RLS: admin/super_admin IN 정책 + super_admin 쓰기 정책 - SQL RPC: admin_usage_by_feature, admin_top_users, admin_dau
This commit is contained in:
parent
f7c50eb2ed
commit
dca1b90faa
34 changed files with 3142 additions and 45 deletions
93
apps/admin/src/components/memo-dialog.tsx
Normal file
93
apps/admin/src/components/memo-dialog.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
'use client'
|
||||
|
||||
// apps/admin/src/components/memo-dialog.tsx
|
||||
// 메모 입력 다이얼로그 — 감사로그 기록 시 필수
|
||||
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
TextField,
|
||||
Button,
|
||||
} from '@mui/material'
|
||||
import { PhosphorText } from '@d3ro/ui/components/ds'
|
||||
import { d3roPalette, d3roFontMono } from '@d3ro/ui/theme'
|
||||
|
||||
interface MemoDialogProps {
|
||||
open: boolean
|
||||
title: string
|
||||
description?: string
|
||||
onConfirm: (memo: string) => void
|
||||
onCancel: () => void
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export function MemoDialog({ open, title, description, onConfirm, onCancel, loading }: MemoDialogProps): React.ReactElement {
|
||||
const [memo, setMemo] = useState('')
|
||||
|
||||
const handleConfirm = (): void => {
|
||||
if (memo.trim()) {
|
||||
onConfirm(memo.trim())
|
||||
setMemo('')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = (): void => {
|
||||
setMemo('')
|
||||
onCancel()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleCancel}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
PaperProps={{ sx: { bgcolor: d3roPalette.bg.card, color: d3roPalette.text.primary } }}
|
||||
>
|
||||
<DialogTitle sx={{ fontFamily: d3roFontMono }}>
|
||||
<PhosphorText variant="heading">{title}</PhosphorText>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{description && (
|
||||
<PhosphorText variant="dim" sx={{ display: 'block', mb: 2 }}>
|
||||
{description}
|
||||
</PhosphorText>
|
||||
)}
|
||||
<TextField
|
||||
autoFocus
|
||||
fullWidth
|
||||
multiline
|
||||
rows={3}
|
||||
placeholder="Reason for this action (required)..."
|
||||
value={memo}
|
||||
onChange={(e) => setMemo(e.target.value)}
|
||||
sx={{
|
||||
mt: 1,
|
||||
'& .MuiInputBase-root': {
|
||||
fontFamily: d3roFontMono,
|
||||
fontSize: 13,
|
||||
color: d3roPalette.text.primary,
|
||||
bgcolor: d3roPalette.bg.inset,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
<Button onClick={handleCancel} sx={{ fontFamily: d3roFontMono, color: d3roPalette.text.secondary }}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
disabled={!memo.trim() || loading}
|
||||
variant="contained"
|
||||
sx={{ fontFamily: d3roFontMono }}
|
||||
>
|
||||
{loading ? 'Processing...' : 'Confirm'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue