manage license app launch policies
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-10 13:14:23 +09:00
parent 93fc7b7b4e
commit ac0c8eb29d
7 changed files with 620 additions and 97 deletions
+32 -8
View File
@@ -9,7 +9,7 @@ import { listApps } from '../applications/application-api';
import {
createLauncherNotice,
deleteLauncherNotice,
loadLauncherNotices,
loadLauncherNoticesPage,
updateLauncherNotice,
type LauncherNotice
} from './operations-api';
@@ -123,6 +123,10 @@ export function NoticesPage() {
const { toast } = useToast();
const [notices, setNotices] = useState<LauncherNotice[]>([]);
const [apps, setApps] = useState<AppWithComponents[]>([]);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [keyword, setKeyword] = useState('');
const [filterValues, setFilterValues] = useState<Record<string, string>>({});
const [loading, setLoading] = useState(true);
const [failed, setFailed] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
@@ -136,11 +140,12 @@ export function NoticesPage() {
const refresh = () => {
setLoading(true);
Promise.all([
loadLauncherNotices(),
loadLauncherNoticesPage({ page, limit: 6, keyword, appKey: filterValues.appKey }),
listApps().catch(() => [])
])
.then(([items, appItems]) => {
setNotices(items);
.then(([result, appItems]) => {
setNotices(result.items);
setTotal(result.total);
setApps(appItems);
setFailed(false);
})
@@ -154,7 +159,7 @@ export function NoticesPage() {
useEffect(() => {
refresh();
}, []);
}, [page, keyword, filterValues.appKey]);
useEffect(() => {
if (!menuNotice) return;
@@ -184,7 +189,7 @@ export function NoticesPage() {
const appLabel = (appKey: string) => appNameByKey.get(appKey) ?? appKey;
const rows = useMemo(
() => notices.map((notice, index) => ({ ...notice, no: notices.length - index, appName: appLabel(notice.appKey) })),
() => notices.map((notice) => ({ ...notice, no: notice.no ?? 0, appName: appLabel(notice.appKey) })),
[appNameByKey, notices]
);
@@ -303,9 +308,14 @@ export function NoticesPage() {
const filters = useMemo<Array<DataTableFilter<(typeof rows)[number]>>>(
() => [
{ key: 'appKey', label: '대상 앱', options: [...new Set(rows.map((row) => row.appKey))].map((appKey) => ({ label: appLabel(appKey), value: appKey })), getValue: (row) => row.appKey }
{
key: 'appKey',
label: '대상 앱',
options: ['Launcher', ...apps.map((app) => app.key)].map((appKey) => ({ label: appLabel(appKey), value: appKey })),
getValue: (row) => row.appKey
}
],
[rows]
[apps, appNameByKey]
);
return (
@@ -338,6 +348,20 @@ export function NoticesPage() {
minWidth={1040}
loading={loading}
emptyMessage="등록된 공지사항이 없습니다."
serverPagination={{
page,
total,
onPageChange: setPage,
onSearchChange: (value) => {
setKeyword(value);
setPage(1);
},
filterValues,
onFilterChange: (key, value) => {
setFilterValues((prev) => ({ ...prev, [key]: value }));
setPage(1);
}
}}
/>
{menuNotice ? (() => {