|
| 1 | +// style |
1 | 2 | import "@/views/main/style/dwn-modal.sass"; |
| 3 | + |
| 4 | +// components |
2 | 5 | import Modal from "@/views/components/modal"; |
3 | 6 |
|
| 7 | +// libraries |
| 8 | +import { useEffect, useMemo, useState } from "react"; |
| 9 | + |
| 10 | +// apis |
| 11 | +import { getFileDwn } from "@/api/main/fileDwn"; |
| 12 | +import { downloadAs } from "@/utils/download"; |
| 13 | + |
| 14 | +type FileKind = "MINDMAP" | "RECORDING" | "SUMMARY"; |
| 15 | +type FileFormat = "jpg" | "png" | "zip" | "txt"; |
| 16 | + |
4 | 17 | interface DwnModalProps { |
5 | 18 | onCloseModal: () => void; |
| 19 | + projectId: string; |
| 20 | + projectName: string; |
6 | 21 | } |
7 | 22 |
|
8 | | -const DwnModal = ({ |
| 23 | +const KIND_FORMAT_OPTIONS: Record< |
| 24 | + FileKind, |
| 25 | + { value: FileFormat; label: string }[] |
| 26 | +> = { |
| 27 | + MINDMAP: [ |
| 28 | + { value: "jpg", label: "JPG 이미지(.jpg)" }, |
| 29 | + { value: "png", label: "PNG 이미지(.png)" }, |
| 30 | + ], |
| 31 | + RECORDING: [{ value: "zip", label: "ZIP(.zip)" }], |
| 32 | + SUMMARY: [{ value: "txt", label: "텍스트(.txt)" }], |
| 33 | +}; |
| 34 | + |
| 35 | +export default function DwnModal({ |
9 | 36 | onCloseModal, |
10 | | -}: DwnModalProps) => { |
| 37 | + projectId, |
| 38 | + projectName, |
| 39 | +}: DwnModalProps) { |
| 40 | + const [kind, setKind] = useState<FileKind | "">(""); |
| 41 | + const [format, setFormat] = useState<FileFormat>("jpg"); |
| 42 | + |
| 43 | + const canSubmit = !!kind; |
| 44 | + |
| 45 | + // 현재 kind에 따른 형식 옵션 |
| 46 | + const formatOptions = useMemo(() => { |
| 47 | + return kind ? KIND_FORMAT_OPTIONS[kind as FileKind] : []; |
| 48 | + }, [kind]); |
| 49 | + |
| 50 | + // kind 바뀌면 해당 kind의 첫 옵션으로 format 동기화 |
| 51 | + useEffect(() => { |
| 52 | + if (!kind) return; |
| 53 | + const first = KIND_FORMAT_OPTIONS[kind as FileKind][0]?.value; |
| 54 | + if (first && format !== first) setFormat(first); |
| 55 | + }, [kind]); // eslint-disable-line react-hooks/exhaustive-deps |
| 56 | + |
| 57 | + const handleSubmit = async (e: React.FormEvent) => { |
| 58 | + e.preventDefault(); |
| 59 | + if (!canSubmit) return; |
| 60 | + |
| 61 | + await getFileDwn(projectId, kind).then((res) => { |
| 62 | + const url = res.data.data.projectUrl; |
| 63 | + downloadAs(url, `${projectName}.${format}`); |
| 64 | + }); |
| 65 | + }; |
| 66 | + |
11 | 67 | return ( |
12 | 68 | <Modal onCloseModal={onCloseModal}> |
13 | 69 | <p className="modal-title">다운로드</p> |
14 | | - <div className="modal-wrap"> |
| 70 | + |
| 71 | + <form className="modal-wrap" onSubmit={handleSubmit}> |
| 72 | + {/* 파일 종류 선택 */} |
15 | 73 | <div className="file-wrap"> |
16 | 74 | <h3>파일 선택</h3> |
17 | 75 | <div className="radio-container"> |
18 | | - <div className="radio-wrap mind-map"> |
19 | | - <div className="img"></div> |
20 | | - <input type="radio" name="file" id="mind-map" value="1"/> |
| 76 | + <label |
| 77 | + className={`radio-wrap mind-map ${ |
| 78 | + kind === "MINDMAP" ? "active" : "" |
| 79 | + }`} |
| 80 | + htmlFor="mind-map" |
| 81 | + > |
| 82 | + <div className="img" /> |
| 83 | + <input |
| 84 | + type="radio" |
| 85 | + name="file" |
| 86 | + id="mind-map" |
| 87 | + value="MINDMAP" |
| 88 | + checked={kind === "MINDMAP"} |
| 89 | + onChange={() => setKind("MINDMAP")} |
| 90 | + required |
| 91 | + /> |
21 | 92 | <div className="radio-title">마인드맵</div> |
22 | | - </div> |
23 | | - <div className="radio-wrap record"> |
24 | | - <div className="img"></div> |
25 | | - <input type="radio" name="file" id="record" value="2"/> |
| 93 | + </label> |
| 94 | + |
| 95 | + <label |
| 96 | + className={`radio-wrap record ${ |
| 97 | + kind === "RECORDING" ? "active" : "" |
| 98 | + }`} |
| 99 | + htmlFor="record" |
| 100 | + > |
| 101 | + <div className="img" /> |
| 102 | + <input |
| 103 | + type="radio" |
| 104 | + name="file" |
| 105 | + id="record" |
| 106 | + value="RECORDING" |
| 107 | + checked={kind === "RECORDING"} |
| 108 | + onChange={() => setKind("RECORDING")} |
| 109 | + required |
| 110 | + /> |
26 | 111 | <div className="radio-title">음성・스크립트</div> |
27 | | - </div> |
28 | | - <div className="radio-wrap summary"> |
29 | | - <div className="img"></div> |
30 | | - <input type="radio" name="file" id="summary" value="3"/> |
| 112 | + </label> |
| 113 | + |
| 114 | + <label |
| 115 | + className={`radio-wrap summary ${ |
| 116 | + kind === "SUMMARY" ? "active" : "" |
| 117 | + }`} |
| 118 | + htmlFor="summary" |
| 119 | + > |
| 120 | + <div className="img" /> |
| 121 | + <input |
| 122 | + type="radio" |
| 123 | + name="file" |
| 124 | + id="summary" |
| 125 | + value="SUMMARY" |
| 126 | + checked={kind === "SUMMARY"} |
| 127 | + onChange={() => setKind("SUMMARY")} |
| 128 | + required |
| 129 | + /> |
31 | 130 | <div className="radio-title">요약본</div> |
32 | | - </div> |
| 131 | + </label> |
33 | 132 | </div> |
34 | 133 | </div> |
| 134 | + |
| 135 | + {/* 형식 선택 */} |
35 | 136 | <div className="type-wrap"> |
36 | 137 | <h3>형식 선택</h3> |
37 | 138 | <div className="select-wrap"> |
38 | | - <select> |
39 | | - <option>JPG 이미지(.jpg)</option> |
40 | | - <option>PNG 이미지(.png)</option> |
| 139 | + <select |
| 140 | + aria-label="파일 형식" |
| 141 | + value={format} |
| 142 | + onChange={(e) => setFormat(e.target.value as FileFormat)} |
| 143 | + disabled={!kind} // 종류 선택 전엔 비활성화 |
| 144 | + > |
| 145 | + {!kind && <option value="">다운로드 파일을 선택하세요</option>} |
| 146 | + {formatOptions.map((opt) => ( |
| 147 | + <option key={opt.value} value={opt.value}> |
| 148 | + {opt.label} |
| 149 | + </option> |
| 150 | + ))} |
41 | 151 | </select> |
42 | 152 | </div> |
43 | 153 | </div> |
44 | | - </div> |
| 154 | + |
| 155 | + <button type="submit" className="btn-primary" disabled={!canSubmit}> |
| 156 | + 다운로드 |
| 157 | + </button> |
| 158 | + </form> |
45 | 159 | </Modal> |
46 | 160 | ); |
47 | | -}; |
48 | | - |
49 | | -export default DwnModal; |
| 161 | +} |
0 commit comments