Skip to content

Commit 7c10510

Browse files
authored
v1.3.0
Resolved #103
2 parents 0a3af27 + cf79760 commit 7c10510

43 files changed

Lines changed: 5245 additions & 7572 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

__tests__/admin/wordsCount.test.ts

Lines changed: 0 additions & 84 deletions
This file was deleted.

app/admin/add-words/AddWordsHome.tsx

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,18 @@ export default function WordsAddHome() {
223223
// 문서 및 주제 정보 맵핑
224224
const letterDocsInfo: Record<string, number> = {};
225225
const themeDocsInfo: Record<string, number> = {};
226-
const themeCodeInfo: Record<string, number> = {}
226+
const themeCodeInfo: Record<string, number> = {};
227+
const themeInInfo: Record<number, {id: number, name: string, code: string}> = {};
227228

228229
docsDatas.filter(({ typez }) => typez === "letter").forEach(({ id, name }) => {
229230
letterDocsInfo[name] = id
230231
})
231232
docsDatas.filter(({ typez }) => typez === "theme").forEach(({ id, name }) => {
232233
themeDocsInfo[name] = id
233234
})
234-
themeData.forEach(({ id, code }) => {
235+
themeData.forEach(({ id, code, name }) => {
235236
themeCodeInfo[code] = id
237+
themeInInfo[id] = { id, name, code };
236238
})
237239

238240
// DB 쿼리 준비
@@ -319,10 +321,8 @@ export default function WordsAddHome() {
319321
}
320322

321323
setProgress(40);
322-
const existingWordThemes: Record<number, { themeId: number, themeCode: string, themeName: string }[]> = {}; // 단어id - 주제들
323324
const checkedData: Record<string, number> = {}; // 단어 - 단어id
324-
const existingWordMap: Record<string, { wordId: number, themes: { id: number, code: string, name: string }[] }> = {}; // 기존단어 - {단어id, 주제들}
325-
325+
326326
// 기존 단어 체크
327327
const { data: needCheckedWordsData, error: ff } = await supabaseInQueryChunk(
328328
needCheckWord,
@@ -342,41 +342,21 @@ export default function WordsAddHome() {
342342
if (ff) return makeError(ff);
343343

344344
// 기존 단어 맵핑
345+
const existingWordThemes: Record<string, { themeId: number; themeCode: string; themeName: string; }[]> = {}; // 단어 - 기존주제들
345346
for (const data of needCheckedWordsData) {
346347
checkedData[data.word] = data.id
347-
existingWordMap[data.word] = { wordId: data.id, themes: [] };
348+
existingWordThemes[data.word] = data.wthemes.map((themeId) => {
349+
const themeInfo = themeInInfo[themeId];
350+
if (!themeInfo) return undefined;
351+
return { themeId: themeInfo.id, themeCode: themeInfo.code, themeName: themeInfo.name };
352+
}).filter(Boolean) as { themeId: number; themeCode: string; themeName: string; }[];
348353
}
349354

350-
// 기존 단어의 주제 맵핑
351-
const { data: existingWordThemesData, error: ee } = await supabaseInQueryChunk(
352-
needCheckedWordsData.map(({ id }) => id),
353-
async (chunk) => {
354-
const r = await SCM.get().wordsThemesByWordId(chunk);
355-
return { ...r };
356-
},
357-
{
358-
chunkSize: 100,
359-
concurrency: 3,
360-
onProgress(_, __, current, total) {
361-
setCurrentTask(`기존단어의 주제 체크중... ${current}/${total}`);
362-
}
363-
}
364-
)
365-
if (ee) return makeError(ee);
366-
const result: Record<number, { themeId: number; themeCode: string; themeName: string; }[]> = {};
367-
existingWordThemesData.forEach(({ word_id, themes: { id, code, name } }) => {
368-
if (!result[word_id]) { result[word_id] = []; }
369-
result[word_id].push({ themeId: id, themeCode: code, themeName: name });
370-
});
371-
Object.entries(result).forEach(([wordId, themes]) => {
372-
existingWordThemes[Number(wordId)] = (existingWordThemes[Number(wordId)] ?? []).concat(themes.map(({ themeId, themeCode, themeName }) => ({ themeId, themeCode, themeName })));
373-
});
374-
375355
// 기존에는 있는 주제이지만 JSON에는 없는 주제 제거 쿼리 준비
376356
for (const data of jsonData) {
377357
const addThemesSet = new Set(data.themes);
378-
if (existingWordThemes[checkedData[data.word]]) {
379-
for (const existTheme of existingWordThemes[checkedData[data.word]]) {
358+
if (existingWordThemes[data.word]) {
359+
for (const existTheme of existingWordThemes[data.word]) {
380360
if (!addThemesSet.has(existTheme.themeCode)) {
381361
wordThemeDelQuery.push({
382362
word_id: checkedData[data.word],

app/admin/request-words/AdminRequestHome.tsx

Lines changed: 110 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { isNoin } from '@/app/lib/lib'
3838
import { addWordQueryType } from '@/app/types/type'
3939
import Link from 'next/link'
4040
import { ArrowLeft } from 'lucide-react'
41+
import ThemeSelectModal from './ThemeSelectModal'
4142

4243
// 타입 정의
4344
type Theme = {
@@ -65,10 +66,24 @@ export default function AdminHome({ requestDatas, refreshFn }: { requestDatas: W
6566
const [currentPage, setCurrentPage] = useState<number>(1);
6667
const [allSelected, setAllSelected] = useState<boolean>(false);
6768
const [errorModalView, setErrorModalView] = useState<ErrorMessage | null>(null);
69+
const [themeModalOpen, setThemeModalOpen] = useState<boolean>(false);
70+
const [selectedRequestForModal, setSelectedRequestForModal] = useState<WordRequest | null>(null);
71+
const [allThemes, setAllThemes] = useState<{ id: number; name: string; code: string }[]>([]);
6872
const user = useSelector((state: RootState) => state.user);
6973

7074
const PAGE_SIZE = 30;
7175

76+
// 전체 주제 목록 불러오기
77+
useEffect(() => {
78+
const loadAllThemes = async () => {
79+
const { data, error } = await SCM.get().allThemes();
80+
if (!error && data) {
81+
setAllThemes(data);
82+
}
83+
};
84+
loadAllThemes();
85+
}, []);
86+
7287
// 요청 타입별 필터링
7388
const filteredRequests = requestDatas.filter(request => {
7489
if (selectedTab === "all") return true;
@@ -108,30 +123,32 @@ export default function AdminHome({ requestDatas, refreshFn }: { requestDatas: W
108123
setSelectedRequests(newSelected);
109124
};
110125

111-
// 주제 선택 토글
112-
const toggleTheme = (requestId: number, themeId: number) => {
113-
const currentThemes = selectedThemes[requestId] || new Set<number>();
126+
// 주제 선택 버튼 클릭 핸들러
127+
const handleThemeSelectClick = (request: WordRequest) => {
128+
setSelectedRequestForModal(request);
129+
setThemeModalOpen(true);
130+
};
131+
132+
// 모달에서 주제 선택 확인 핸들러
133+
const handleThemeModalConfirm = (selectedThemesList: Theme[]) => {
134+
if (!selectedRequestForModal) return;
135+
114136
const newSelectedThemes = { ...selectedThemes };
137+
const themeIds = new Set(selectedThemesList.map(t => t.theme_id));
138+
newSelectedThemes[selectedRequestForModal.id] = themeIds;
139+
setSelectedThemes(newSelectedThemes);
115140

116-
if (currentThemes.has(themeId)) {
117-
currentThemes.delete(themeId);
118-
if (currentThemes.size === 0) {
119-
toggleRequest(requestId)
120-
}
121-
} else {
122-
currentThemes.add(themeId);
141+
// 주제가 선택되면 해당 요청도 자동으로 선택
142+
if (themeIds.size > 0) {
123143
const newSelected = new Set(selectedRequests);
124-
if (!newSelected.has(requestId)) {
125-
newSelected.add(requestId);
144+
if (!newSelected.has(selectedRequestForModal.id)) {
145+
newSelected.add(selectedRequestForModal.id);
126146
if (newSelected.size === currentRequests.length) {
127147
setAllSelected(true);
128148
}
129149
setSelectedRequests(newSelected);
130150
}
131151
}
132-
133-
newSelectedThemes[requestId] = currentThemes;
134-
setSelectedThemes(newSelectedThemes);
135152
};
136153

137154
const makeError = (error: PostgrestError) => {
@@ -155,11 +172,18 @@ export default function AdminHome({ requestDatas, refreshFn }: { requestDatas: W
155172
const request = requestDatas.find(r => r.id === reqId);
156173
const selectedThemeIds = selectedThemes[reqId] || new Set<number>();
157174

175+
// allThemes에서 선택된 주제 정보 가져오기
176+
const selectedThemeObjects = allThemes
177+
.filter(theme => selectedThemeIds.has(theme.id))
178+
.map(theme => ({
179+
theme_id: theme.id,
180+
theme_name: theme.name,
181+
theme_code: theme.code
182+
}));
183+
158184
return {
159185
...request,
160-
selectedThemes: request?.wait_themes?.filter(theme =>
161-
selectedThemeIds.has(theme.theme_id)
162-
)
186+
selectedThemes: selectedThemeObjects
163187
};
164188
});
165189

@@ -173,6 +197,8 @@ export default function AdminHome({ requestDatas, refreshFn }: { requestDatas: W
173197

174198
// 승인할 목록에서 쿼리에 맞게 배분
175199
for (const req of requestsToApprove) {
200+
const selectedThemeIds = selectedThemes[req.id!] || new Set<number>();
201+
176202
switch (req.request_type) {
177203
case "add":
178204
if (!req.word || !req.selectedThemes || req.selectedThemes.length === 0) continue;
@@ -188,10 +214,16 @@ export default function AdminHome({ requestDatas, refreshFn }: { requestDatas: W
188214
continue
189215

190216
case "theme_change":
191-
if (!req.word_id || !req.selectedThemes) continue;
217+
if (!req.word_id) continue;
192218
const addT: { word_id: number, theme_id: number }[] = [];
193219
const delT: { word_id: number, theme_id: number }[] = [];
194-
req.selectedThemes.forEach((theme) => {
220+
221+
// theme_change는 wait_themes를 직접 사용
222+
const themesToProcess = req.wait_themes?.filter(theme =>
223+
selectedThemeIds.has(theme.theme_id)
224+
) || [];
225+
226+
themesToProcess.forEach((theme) => {
195227
if (theme.typez === "add") {
196228
addT.push({ word_id: req.word_id as number, theme_id: theme.theme_id })
197229
}
@@ -619,14 +651,57 @@ export default function AdminHome({ requestDatas, refreshFn }: { requestDatas: W
619651
{renderRequestTypeBadge(request.request_type)}
620652
</TableCell>
621653
<TableCell>
622-
{request.wait_themes ? (
654+
{request.request_type === 'add' ? (
655+
<div className="space-y-2">
656+
<Button
657+
variant="outline"
658+
size="sm"
659+
onClick={() => handleThemeSelectClick(request)}
660+
className="w-full"
661+
>
662+
주제 선택 ({selectedThemes[request.id]?.size || 0})
663+
</Button>
664+
{selectedThemes[request.id] && selectedThemes[request.id].size > 0 && (
665+
<div className="flex flex-wrap gap-1">
666+
{allThemes
667+
.filter(theme => selectedThemes[request.id]?.has(theme.id))
668+
.map((theme, index) => (
669+
<Badge key={`badge-${theme.id}-${index}`} variant="secondary" className="text-xs">
670+
{theme.name}
671+
</Badge>
672+
))}
673+
</div>
674+
)}
675+
</div>
676+
) : request.wait_themes ? (
623677
<div className="flex flex-col gap-2">
624678
{request.wait_themes.map((theme, index) => (
625679
<div key={`t-${theme.theme_id}-${request.id}-${index ^ 10110}`} className="flex items-center gap-2">
626680
<Checkbox
627681
id={`theme-${request.id}-${theme.theme_id}`}
628682
checked={selectedThemes[request.id]?.has(theme.theme_id) || false}
629-
onCheckedChange={() => toggleTheme(request.id, theme.theme_id)}
683+
onCheckedChange={() => {
684+
const currentThemes = selectedThemes[request.id] || new Set<number>();
685+
const newSelectedThemes = { ...selectedThemes };
686+
if (currentThemes.has(theme.theme_id)) {
687+
currentThemes.delete(theme.theme_id);
688+
if (currentThemes.size === 0) {
689+
toggleRequest(request.id);
690+
}
691+
} else {
692+
currentThemes.add(theme.theme_id);
693+
const newSelected = new Set(selectedRequests);
694+
if (!newSelected.has(request.id)) {
695+
newSelected.add(request.id);
696+
if (newSelected.size === currentRequests.length) {
697+
setAllSelected(true);
698+
}
699+
setSelectedRequests(newSelected);
700+
}
701+
}
702+
newSelectedThemes[request.id] = currentThemes;
703+
setSelectedThemes(newSelectedThemes);
704+
}}
630705
/>
631706
<label htmlFor={`theme-${request.id}-${theme.theme_id}`} className="text-sm flex items-center text-gray-700 dark:text-gray-200">
632707
{theme.theme_name}
@@ -709,6 +784,19 @@ export default function AdminHome({ requestDatas, refreshFn }: { requestDatas: W
709784
</CardFooter>
710785
</Card>
711786
{errorModalView && <ErrorModal error={errorModalView} onClose={() => setErrorModalView(null)} />}
787+
{selectedRequestForModal && (
788+
<ThemeSelectModal
789+
isOpen={themeModalOpen}
790+
onClose={() => {
791+
setThemeModalOpen(false);
792+
setSelectedRequestForModal(null);
793+
}}
794+
word={selectedRequestForModal.word}
795+
initialSelectedThemes={selectedRequestForModal.wait_themes || []}
796+
initialSelectedThemeIds={selectedThemes[selectedRequestForModal.id]}
797+
onConfirm={handleThemeModalConfirm}
798+
/>
799+
)}
712800
</div>
713801
</div>
714802
)

0 commit comments

Comments
 (0)