Skip to content

Commit ffe4308

Browse files
feat: 사용자 검색 기능 추가
2 parents 7145cf2 + d9e1431 commit ffe4308

3 files changed

Lines changed: 212 additions & 59 deletions

File tree

client/src/app/admin/darak/people/page.tsx

Lines changed: 79 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { get, put } from "@/config/api"
2121
import { type User } from "@server/entity/user"
2222
import { Community } from "@server/entity/community"
2323
import { MouseEvent, useEffect, useRef, useState } from "react"
24+
import UserSearch from "@/components/UserSearch"
2425

2526
export default function People() {
2627
const [communityList, setCommunityList] = useState<Community[]>([])
@@ -57,7 +58,7 @@ export default function People() {
5758

5859
async function fetchCommunityUserList(communityId: number) {
5960
const communityUserListData = await get(
60-
`/admin/community/user-list/${communityId}`
61+
`/admin/community/user-list/${communityId}`,
6162
)
6263
setChildCommunityList(communityUserListData)
6364
}
@@ -66,7 +67,7 @@ export default function People() {
6667
const communityListData = await get("/admin/community")
6768
setCommunityList(communityListData)
6869
const noCommunityUserData = await get(
69-
"/admin/community/no-community-user-list"
70+
"/admin/community/no-community-user-list",
7071
)
7172
setNoCommunityUser(noCommunityUserData)
7273

@@ -121,7 +122,7 @@ export default function People() {
121122

122123
async function saveCommunityLeader(
123124
community: Community,
124-
leaderId: number | null
125+
leaderId: number | null,
125126
) {
126127
await put("/admin/community/save-leader", {
127128
groupId: community.id,
@@ -132,7 +133,7 @@ export default function People() {
132133

133134
async function saveCommunityDeputyLeader(
134135
community: Community,
135-
deputyLeaderId: number | null
136+
deputyLeaderId: number | null,
136137
) {
137138
await put("/admin/community/save-deputy-leader", {
138139
groupId: community.id,
@@ -207,7 +208,7 @@ export default function People() {
207208

208209
function CommunityBox({ displayCommunity }: { displayCommunity: Community }) {
209210
const myCommunity = childCommunityList.find(
210-
(community) => community.id === displayCommunity.id
211+
(community) => community.id === displayCommunity.id,
211212
)
212213

213214
function onClickCommunity(e: MouseEvent) {
@@ -338,7 +339,7 @@ export default function People() {
338339
onChange={(e) => {
339340
saveCommunityDeputyLeader(
340341
displayCommunity,
341-
e.target.value as number
342+
e.target.value as number,
342343
)
343344
}}
344345
>
@@ -408,8 +409,28 @@ export default function People() {
408409
return `${getParentCommunityName(community.parent)} > ${community.name}`
409410
}
410411

412+
const handleSelectUser = (user: User) => {
413+
if (!user.community) {
414+
alert("미배정 사용자입니다.")
415+
return
416+
}
417+
418+
const parentId = user.community.parent ? user.community.parent.id : null
419+
420+
if (!parentId) {
421+
setSelectedRootCommunity(null)
422+
} else {
423+
const parentCommunity = communityList.find((c) => c.id === parentId)
424+
if (parentCommunity) {
425+
setSelectedRootCommunity(parentCommunity)
426+
} else {
427+
console.warn("상위 그룹을 찾을 수 없습니다.")
428+
}
429+
}
430+
}
431+
411432
return (
412-
<Box sx={{ bgcolor: "grey.50", minHeight: "100vh" }}>
433+
<Box sx={{ bgcolor: "grey.50" }}>
413434
<Box p={1.5}>
414435
<Stack
415436
direction="row"
@@ -421,71 +442,72 @@ export default function People() {
421442
커뮤니티 관리
422443
</Typography>
423444
<Stack direction="row" gap={1} alignItems="center">
424-
<Chip
425-
label={`미배정: ${noCommunityUser.length}명`}
426-
size="small"
427-
variant="outlined"
428-
color={noCommunityUser.length > 0 ? "warning" : "success"}
429-
/>
445+
<Box mb={1.5}>
446+
<UserSearch onSelectUser={handleSelectUser} />
447+
</Box>
430448
</Stack>
431449
</Stack>
432-
433450
<Stack
434451
direction="row"
435452
gap={2}
436453
onMouseUp={() => {
437454
selectedUser.current = null
438455
}}
439456
>
440-
{/* 미배정 사용자 영역 */}
441-
<Paper
442-
elevation={2}
443-
sx={{
444-
width: 320,
445-
p: 2,
446-
borderRadius: 2,
447-
bgcolor: "background.paper",
448-
border: "2px dashed",
449-
borderColor: "warning.light",
450-
maxHeight: "calc(100vh - 200px)",
451-
overflow: "hidden",
452-
}}
453-
onMouseUp={removeCommunityToUser}
454-
>
455-
<Typography
456-
variant="subtitle1"
457-
fontWeight="bold"
458-
mb={1}
459-
color="warning.main"
460-
>
461-
미배정 사용자 ({noCommunityUser.length}명)
462-
</Typography>
463-
<Typography
464-
variant="caption"
465-
color="text.secondary"
466-
mb={2}
467-
display="block"
468-
>
469-
다락방에 속하지 않은 사용자들입니다.
470-
</Typography>
471-
<Box
457+
<Stack gap={1.5}>
458+
{/* 미배정 사용자 영역 */}
459+
<Paper
460+
elevation={2}
472461
sx={{
473-
display: "flex",
474-
flexWrap: "wrap",
475-
gap: 0.5,
476-
maxHeight: "calc(100vh - 300px)",
477-
overflowY: "auto",
478-
pr: 1,
462+
width: 320,
463+
p: 2,
464+
borderRadius: 2,
465+
bgcolor: "background.paper",
466+
border: "2px dashed",
467+
borderColor: "warning.light",
468+
maxHeight: "calc(100vh - 200px)",
469+
overflow: "hidden",
479470
}}
471+
onMouseUp={removeCommunityToUser}
480472
>
481-
{noCommunityUser.map((user) => UserBox({ user }))}
482-
</Box>
483-
</Paper>
473+
<Typography
474+
variant="subtitle1"
475+
fontWeight="bold"
476+
mb={1}
477+
color="warning.main"
478+
>
479+
미배정 사용자 ({noCommunityUser.length}명)
480+
</Typography>
481+
<Typography
482+
variant="caption"
483+
color="text.secondary"
484+
mb={2}
485+
display="block"
486+
>
487+
다락방에 속하지 않은 사용자들입니다.
488+
</Typography>
489+
<Box
490+
sx={{
491+
display: "flex",
492+
flexWrap: "wrap",
493+
gap: 0.5,
494+
maxHeight: "calc(100vh - 300px)",
495+
overflowY: "auto",
496+
pr: 1,
497+
}}
498+
>
499+
{noCommunityUser.map((user) => UserBox({ user }))}
500+
</Box>
501+
</Paper>
502+
</Stack>
484503

485504
{/* 커뮤니티 영역 */}
486505
<Box
487506
flex="1"
488-
sx={{ maxHeight: "calc(100vh - 150px)", overflowY: "auto" }}
507+
sx={{
508+
maxHeight: "calc(100vh - (64px + 64px + 24px))",
509+
overflowY: "auto",
510+
}}
489511
>
490512
{/* 네비게이션 */}
491513
<Paper
@@ -535,7 +557,7 @@ export default function People() {
535557
setSelectedRootCommunity(
536558
selectedRootCommunity
537559
? selectedRootCommunity.parent
538-
: null
560+
: null,
539561
)
540562
}
541563
>
@@ -578,7 +600,6 @@ export default function People() {
578600
)}
579601
</Box>
580602
</Stack>
581-
582603
{/* 드래그 중인 사용자 표시 */}
583604
{selectedUser.current && selectedUser.current.id && (
584605
<Box
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { useState, useEffect, useMemo } from "react"
2+
import { TextField, Autocomplete, Box, Typography } from "@mui/material"
3+
import axios from "@/config/axios"
4+
import { type User } from "@server/entity/user"
5+
import { debounce } from "lodash"
6+
7+
interface UserSearchProps {
8+
onSelectUser: (user: User) => void
9+
}
10+
11+
export default function UserSearch({ onSelectUser }: UserSearchProps) {
12+
const [open, setOpen] = useState(false)
13+
const [options, setOptions] = useState<User[]>([])
14+
const [loading, setLoading] = useState(false)
15+
const [inputValue, setInputValue] = useState("")
16+
17+
const fetchUsers = useMemo(
18+
() =>
19+
debounce(
20+
async (
21+
request: { input: string },
22+
callback: (results?: User[]) => void,
23+
) => {
24+
try {
25+
const { data } = await axios.get<User[]>(
26+
`/admin/community/search-user?name=${request.input}`,
27+
)
28+
callback(data)
29+
} catch (error) {
30+
console.error(error)
31+
callback([])
32+
}
33+
},
34+
300,
35+
),
36+
[],
37+
)
38+
39+
useEffect(() => {
40+
let active = true
41+
42+
if (inputValue === "") {
43+
setOptions([])
44+
return undefined
45+
}
46+
47+
setLoading(true)
48+
fetchUsers({ input: inputValue }, (results) => {
49+
if (active && results) {
50+
setOptions(results)
51+
}
52+
setLoading(false)
53+
})
54+
55+
return () => {
56+
active = false
57+
}
58+
}, [inputValue, fetchUsers])
59+
60+
return (
61+
<Autocomplete
62+
open={open}
63+
onOpen={() => setOpen(true)}
64+
onClose={() => setOpen(false)}
65+
isOptionEqualToValue={(option, value) => option.id === value.id}
66+
getOptionLabel={(option) => `${option.name} (${option.yearOfBirth})`}
67+
options={options}
68+
loading={loading}
69+
onInputChange={(event, newInputValue) => {
70+
setInputValue(newInputValue)
71+
}}
72+
onChange={(event, newValue) => {
73+
if (newValue) {
74+
onSelectUser(newValue)
75+
setOpen(false)
76+
}
77+
}}
78+
renderOption={(props, option) => {
79+
const { key, ...optionProps } = props
80+
return (
81+
<li key={key} {...optionProps}>
82+
<Box>
83+
<Typography variant="body2">
84+
{option.name} ({option.yearOfBirth})
85+
</Typography>
86+
<Typography variant="caption" color="text.secondary">
87+
{option.community ? option.community.name : "미배정"}
88+
</Typography>
89+
</Box>
90+
</li>
91+
)
92+
}}
93+
renderInput={(params) => (
94+
<TextField {...params} label="사용자 검색" size="small" />
95+
)}
96+
sx={{ width: 300 }}
97+
/>
98+
)
99+
}

server/src/routes/admin/communityRouter.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import express from "express"
22
import { communityDatabase, userDatabase } from "../../model/dataSource"
3-
import { IsNull, Not } from "typeorm"
3+
import { IsNull, Like, Not } from "typeorm"
44

55
const router = express.Router()
66

@@ -79,6 +79,39 @@ router.get("/user-list/:groupId", async (req, res) => {
7979
res.send(groupList)
8080
})
8181

82+
router.get("/search-user", async (req, res) => {
83+
const { name } = req.query
84+
if (!name) {
85+
res.send([])
86+
return
87+
}
88+
89+
const users = await userDatabase.find({
90+
where: {
91+
name: Like(`%${name}%`),
92+
},
93+
relations: {
94+
community: {
95+
parent: true,
96+
},
97+
},
98+
select: {
99+
id: true,
100+
name: true,
101+
yearOfBirth: true,
102+
community: {
103+
id: true,
104+
name: true,
105+
parent: {
106+
id: true,
107+
name: true,
108+
},
109+
},
110+
},
111+
})
112+
res.send(users)
113+
})
114+
82115
router.get("/no-community-user-list", async (req, res) => {
83116
const userList = await userDatabase.find({
84117
where: {

0 commit comments

Comments
 (0)