|
1 | 1 | "use client"; |
2 | 2 |
|
3 | | -import { CheckIcon } from "@/shared/asset/svg/CheckIcon"; |
4 | | -import { Button } from "@/shared/ui"; |
5 | | -import { useCallback, useEffect, useState } from "react"; |
6 | | -import CustomDropdown from "../Dropdown"; |
7 | 3 | import { Score } from "@/views/evaluation/model/score"; |
8 | | -import { saveScore } from "../../api/saveScore"; |
9 | | -import { toast } from "sonner"; |
10 | | -import { colors } from "@/shared/utils/color"; |
11 | 4 | import { cn } from "@/shared/utils/cn"; |
12 | | - |
13 | | -type Label = "completion_expression" | "creativity_composition" | "stage_manner_performance"; |
14 | | - |
15 | | -type ValueType = { |
16 | | - write: boolean; |
17 | | - max: number; |
18 | | - show: boolean; |
19 | | - label: Label; |
20 | | -}; |
21 | | - |
22 | | -const scores: ValueType[] = [ |
23 | | - { write: false, max: 40, show: false, label: "completion_expression" }, |
24 | | - { write: false, max: 30, show: false, label: "creativity_composition" }, |
25 | | - { write: false, max: 30, show: false, label: "stage_manner_performance" }, |
26 | | -]; |
| 5 | +import { Button } from "@/shared/ui"; |
| 6 | +import DownArrow from "@/shared/asset/svg/DownArrow"; |
| 7 | +import UpArrow from "@/shared/asset/svg/UpArrow"; |
| 8 | +import { useCallback, useState } from "react"; |
| 9 | +import { saveScore } from "../../api/saveScore"; |
| 10 | +import { MAX } from "../../const/max"; |
27 | 11 |
|
28 | 12 | export default function EvaluationCard({ |
29 | | - judge_id, |
30 | 13 | stage_manner_performance, |
31 | 14 | completion_expression, |
32 | 15 | creativity_composition, |
33 | | - total_score, |
34 | 16 | team_id, |
35 | 17 | team_name, |
36 | | - is_judged, |
37 | 18 | is_performed, |
38 | 19 | }: Score) { |
39 | | - const [values, setValues] = useState<ValueType[]>(scores); |
40 | | - const [scoreValues, setScoreValues] = useState<Record<Label, number>>({ |
41 | | - completion_expression: 0, |
42 | | - creativity_composition: 0, |
43 | | - stage_manner_performance: 0, |
44 | | - }); |
45 | | - const [variant, setVariant] = useState<"submitted" | "active" | "disabled">("active"); |
| 20 | + const [perform, setPerform] = useState(stage_manner_performance); |
| 21 | + const [compleition, setCompleition] = useState(completion_expression); |
| 22 | + const [creativity, setCreativity] = useState(creativity_composition); |
46 | 23 |
|
47 | | - useEffect(() => { |
48 | | - setScoreValues({ |
49 | | - completion_expression: Number(completion_expression) || 0, |
50 | | - creativity_composition: Number(creativity_composition) || 0, |
51 | | - stage_manner_performance: Number(stage_manner_performance) || 0, |
52 | | - }); |
53 | | - if (is_judged) { |
54 | | - setVariant("submitted"); |
55 | | - return; |
56 | | - } else if (!is_performed) { |
57 | | - setVariant("disabled"); |
| 24 | + const handleUp = useCallback((evaluationType: "perform" | "compleition" | "creativity") => { |
| 25 | + switch (evaluationType) { |
| 26 | + case "perform": |
| 27 | + setPerform(prev => Math.min(prev + 1, MAX.perform)); |
| 28 | + break; |
| 29 | + case "compleition": |
| 30 | + setCompleition(prev => Math.min(prev + 1, MAX.compleition)); |
| 31 | + break; |
| 32 | + case "creativity": |
| 33 | + setCreativity(prev => Math.min(prev + 1, MAX.creativity)); |
| 34 | + break; |
58 | 35 | } |
59 | | - }, [ |
60 | | - judge_id, |
61 | | - stage_manner_performance, |
62 | | - completion_expression, |
63 | | - creativity_composition, |
64 | | - total_score, |
65 | | - team_id, |
66 | | - is_judged, |
67 | | - is_performed, |
68 | | - ]); |
| 36 | + }, []); |
69 | 37 |
|
70 | | - const handleSave = useCallback(async () => { |
71 | | - const { completion_expression, creativity_composition, stage_manner_performance } = scoreValues; |
72 | | - const res = await saveScore( |
73 | | - team_id, |
74 | | - Number(stage_manner_performance) || 0, |
75 | | - Number(completion_expression) || 0, |
76 | | - Number(creativity_composition) || 0, |
77 | | - ); |
78 | | - if (res.status === 200) { |
79 | | - toast.success("심사 내용이 저장되었습니다"); |
80 | | - setVariant("submitted"); |
81 | | - } else { |
82 | | - toast.error("심사 내용 저장에 실패하였습니다"); |
83 | | - setVariant("active"); |
| 38 | + const handleDown = useCallback((evaluationType: "perform" | "compleition" | "creativity") => { |
| 39 | + switch (evaluationType) { |
| 40 | + case "perform": |
| 41 | + setPerform(prev => Math.max(prev - 1, 0)); |
| 42 | + break; |
| 43 | + case "compleition": |
| 44 | + setCompleition(prev => Math.max(prev - 1, 0)); |
| 45 | + break; |
| 46 | + case "creativity": |
| 47 | + setCreativity(prev => Math.max(prev - 1, 0)); |
| 48 | + break; |
84 | 49 | } |
85 | | - }, [team_id, scoreValues]); |
86 | | - |
87 | | - const changeActive = useCallback(() => { |
88 | | - setVariant("active"); |
89 | 50 | }, []); |
90 | 51 |
|
| 52 | + const handleSave = useCallback(() => { |
| 53 | + saveScore(team_id, perform, compleition, creativity); |
| 54 | + }, [team_id, perform, compleition, creativity]); |
| 55 | + |
91 | 56 | return ( |
92 | | - <ul |
| 57 | + <div |
93 | 58 | className={cn( |
94 | | - "w-full text-body3b flex py-14 border items-center rounded-md border-gray-100 border-solid justify-between px-24 pl-[80px]", |
| 59 | + "text-body1b grid grid-cols-6 py-14 border text-gray-300 mx-24 rounded-md border-gray-100 border-solid items-center text-center", |
95 | 60 | !is_performed && "bg-#E9E9E9", |
96 | 61 | )} |
97 | 62 | > |
98 | | - <li className="text-main-600">{team_id + ". " + team_name}</li> |
99 | | - {values.map((v, i) => { |
100 | | - return v.write ? ( |
101 | | - <input |
102 | | - className="w-[46.53px]" |
103 | | - key={v.label} |
104 | | - max={v.max} |
105 | | - min={0} |
106 | | - type="number" |
107 | | - value={scoreValues[v.label]} |
108 | | - onChange={e => { |
109 | | - const val = e.target.value === "" ? 0 : Number(e.target.value); |
110 | | - |
111 | | - if (!Number.isNaN(val) && val <= v.max && val >= 0) { |
112 | | - setScoreValues(prev => ({ ...prev, [v.label]: val })); |
113 | | - } |
114 | | - }} |
115 | | - onBlur={() => { |
116 | | - setValues(prev => |
117 | | - prev.map((item, idx) => |
118 | | - idx === i |
119 | | - ? { |
120 | | - ...item, |
121 | | - write: false, |
122 | | - show: false, |
123 | | - } |
124 | | - : item, |
125 | | - ), |
126 | | - ); |
127 | | - }} |
128 | | - autoFocus |
129 | | - /> |
130 | | - ) : ( |
131 | | - <CustomDropdown |
132 | | - key={v.label} |
133 | | - value={scoreValues[v.label]} |
134 | | - max={v.max} |
135 | | - isOpen={v.show} |
136 | | - onToggle={() => { |
137 | | - setValues(prev => |
138 | | - prev.map((item, idx) => (idx === i ? { ...item, show: !item.show } : item)), |
139 | | - ); |
140 | | - }} |
141 | | - onSelect={selectedValue => { |
142 | | - setScoreValues(prev => ({ ...prev, [v.label]: Number(selectedValue) })); |
143 | | - setValues(prev => |
144 | | - prev.map((item, idx) => |
145 | | - idx === i ? { ...item, write: false, show: false } : item, |
146 | | - ), |
147 | | - ); |
148 | | - }} |
149 | | - onDoubleClick={() => { |
150 | | - setValues(prev => |
151 | | - prev.map((item, idx) => (idx === i ? { ...item, write: true, show: false } : item)), |
152 | | - ); |
153 | | - }} |
154 | | - /> |
155 | | - ); |
156 | | - })} |
157 | | - <Button |
158 | | - onClick={variant === "submitted" ? changeActive : handleSave} |
159 | | - variant={variant === "submitted" ? "third" : "default"} |
160 | | - className={cn("py-12 px-16 gap-12 w-[126px] justify-center flex items-center")} |
161 | | - > |
162 | | - <CheckIcon color={variant === "submitted" ? colors.main[600] : "white"} /> |
163 | | - {Object.values(scoreValues).reduce((sum, n) => sum + Number(n || 0), 0)} |
164 | | - </Button> |
165 | | - </ul> |
| 63 | + <div className="text-main-600 text-center">{team_id + ". " + team_name}</div> |
| 64 | + <div className="flex items-center justify-center gap-8"> |
| 65 | + <span className="ml-12">{compleition}</span> |
| 66 | + <div onClick={() => handleDown("compleition")}> |
| 67 | + <DownArrow height={36} width={37} /> |
| 68 | + </div> |
| 69 | + <div onClick={() => handleUp("compleition")}> |
| 70 | + <UpArrow /> |
| 71 | + </div> |
| 72 | + </div> |
| 73 | + <div className="flex items-center justify-center gap-8"> |
| 74 | + <span className="ml-12">{creativity}</span> |
| 75 | + <div onClick={() => handleDown("creativity")}> |
| 76 | + <DownArrow height={36} width={37} /> |
| 77 | + </div> |
| 78 | + <div onClick={() => handleUp("creativity")}> |
| 79 | + <UpArrow /> |
| 80 | + </div> |
| 81 | + </div> |
| 82 | + <div className="flex items-center justify-center gap-8"> |
| 83 | + <span className="ml-12">{perform}</span> |
| 84 | + <div onClick={() => handleDown("perform")}> |
| 85 | + <DownArrow height={36} width={37} /> |
| 86 | + </div> |
| 87 | + <div onClick={() => handleUp("perform")}> |
| 88 | + <UpArrow /> |
| 89 | + </div> |
| 90 | + </div> |
| 91 | + <div className="text-main-600 text-center">{perform + compleition + creativity}</div> |
| 92 | + <div className="w-full flex justify-center px-24 gap-24"> |
| 93 | + <Button onClick={handleSave} className="text-body2b w-1/2"> |
| 94 | + 저장 |
| 95 | + </Button> |
| 96 | + </div> |
| 97 | + </div> |
166 | 98 | ); |
167 | 99 | } |
0 commit comments