@@ -15,60 +15,86 @@ import {
1515 DialogContent ,
1616 LinearProgress ,
1717 Autocomplete ,
18- InputLabel
1918} from '@mui/material' ;
2019import { ButtonProps } from "@mui/material/Button" ;
2120import { Search as SearchIcon } from '@mui/icons-material' ;
2221import { styled } from '@mui/material/styles' ;
2322import successImg from "../../assets/success.png" ;
2423import failImg from "../../assets/fail.png" ;
24+ import { API_BASE_URL } from "./config" ;
2525
26+ type IssuePayload = {
27+ applicant_name : string ;
28+ recipient_email : string ;
29+ season : string ;
30+ course_name : string ;
31+ } ;
2632
27- type StudyMeta = {
28- periods : string [ ] ;
29- studiesByPeriod ?: Record < string , string [ ] > ;
30- studies ?: string [ ] ;
33+ type IssueResponse = {
34+ returnCode : number ; // 200, 404, 500 ๋ฑ
3135} ;
3236
33- type IssuePayload = {
37+ type ApiStudy = {
38+ id : string ;
3439 name : string ;
35- email : string ;
36- period : string ;
37- studies : string [ ] ;
40+ season : number ;
41+ description : string ;
42+ created_at : string ;
43+ updated_at : string ;
3844} ;
3945
40- type IssueResponse = {
41- returnCode : number ; // 200, 404, 500 ๋ฑ
46+ type StudyMeta = {
47+ periods : string [ ] ;
48+ studiesByPeriod : Record < string , string [ ] > ;
49+ studies : string [ ] ;
4250} ;
4351
4452async function fetchStudyMeta ( ) : Promise < StudyMeta > {
45- // const res = await fetch('/api/studies/meta');
46- // if (!res.ok) throw new Error('Failed to load meta');
47- // return (await res.json()) as StudyMeta;
53+ const res = await fetch ( `${ API_BASE_URL } /certs/all-projects` ) ;
54+ if ( ! res . ok ) throw new Error ( "Failed to load meta" ) ;
55+
56+ const data = ( await res . json ( ) ) as ApiStudy [ ] ;
57+
58+ // season(=period) ๊ธฐ์ค์ผ๋ก ๊ทธ๋ฃนํ
59+ const studiesByPeriod : Record < string , string [ ] > = { } ;
60+ const studies : string [ ] = [ ] ;
61+
62+ for ( const item of data ) {
63+ const period = String ( item . season ) ;
64+ if ( ! studiesByPeriod [ period ] ) {
65+ studiesByPeriod [ period ] = [ ] ;
66+ }
67+ studiesByPeriod [ period ] . push ( item . name ) ;
68+ studies . push ( item . name ) ;
69+ }
70+
71+ const periods = Object . keys ( studiesByPeriod ) . sort ( ) ;
4872
49- await new Promise ( r => setTimeout ( r , 300 ) ) ;
5073 return {
51- periods : [ "10" , "11" ] ,
52- studiesByPeriod : {
53- "10" : [ "DevFactory" , "JobPT" , "3D Vision" ] ,
54- "11" : [ "AI Research Club" , "Test Study" ]
55- } ,
56- studies : [ "DevFactory" , "JobPT" , "AI Research Club" , "3D Vision" , "Test Study" ]
74+ periods,
75+ studiesByPeriod,
76+ studies,
5777 } ;
5878}
5979
60- async function issueCertificate ( payload : IssuePayload ) : Promise < IssueResponse > {
61- const body = JSON . stringify ( payload ) ;
62- console . log ( 'body' , body ) ;
63- // const res = await fetch('/api/certificates/issue', {
64- // method: 'POST',
65- // headers: { 'Content-Type': 'application/json' },
66- // body: JSON.stringify(payload),
67- // });
68- // if (!res.ok) throw new Error('Issue API failed');
69- // return (await res.json()) as IssueResponse;
70- await new Promise ( r => setTimeout ( r , 500 ) ) ;
71- return { returnCode : 200 } ;
80+ async function issueCertificate ( payload : IssuePayload ) : Promise < IssueResponse > {
81+ const res = await fetch ( `${ API_BASE_URL } /certs/create` , {
82+ method : "POST" ,
83+ headers : { "Content-Type" : "application/json" } ,
84+ body : JSON . stringify ( payload ) ,
85+ } ) ;
86+
87+ let json : any = { } ;
88+ try {
89+ json = await res . json ( ) ;
90+ } catch {
91+ json = { } ;
92+ }
93+
94+ return {
95+ ...json ,
96+ returnCode : res . status , // HTTP ์ํ ์ฝ๋ ๊ทธ๋๋ก ์ฌ์ฉ
97+ } ;
7298}
7399
74100const SuccessIcon : React . FC = ( ) => {
@@ -259,8 +285,6 @@ const ExportCertificateForm = () => {
259285
260286 const [ returnCode , setReturnCode ] = useState < number | null > ( null ) ;
261287
262- const [ testReturnCode , setTestReturnCode ] = useState < number > ( 200 ) ;
263-
264288 useEffect ( ( ) => {
265289 let mounted = true ;
266290 ( async ( ) => {
@@ -323,34 +347,60 @@ const ExportCertificateForm = () => {
323347 setIsComplete ( false ) ;
324348 setReturnCode ( null ) ;
325349 setProgress ( 0 ) ;
326-
350+
327351 // ๊ฐ์ง ์งํ๋ฐ
328352 const interval = setInterval ( ( ) => {
329353 setProgress ( prev => {
330354 if ( prev >= 100 ) {
331355 clearInterval ( interval ) ;
332356 return 100 ;
333357 }
334- return prev + 10 ;
358+ return prev + 1 ;
335359 } ) ;
336- } , 150 ) ;
337-
360+ } , 200 ) ; // 0.2์ด๋ง๋ค 1% โ ์ด 20์ด
361+
338362 try {
339- // ์ค์ API ํธ์ถ (์๋ต returnCode๋ ๋ฌด์ํ๊ณ , UI์์ ์ ํํ testReturnCode๋ฅผ ๋ฐ์)
340- await issueCertificate ( {
341- name : formData . name . trim ( ) ,
342- email : formData . email . trim ( ) ,
343- period : formData . period ,
344- studies : tags ,
363+ // ๊ฐ ํ๊ทธ๋ณ ํธ์ถ์ allSettled๋ก ์ํํ์ฌ ๋ถ๋ถ ์คํจ๋ ์์ง
364+ const settled = await Promise . allSettled (
365+ tags . map ( tag =>
366+ issueCertificate ( {
367+ applicant_name : formData . name . trim ( ) ,
368+ recipient_email : formData . email . trim ( ) ,
369+ season : formData . period , // ๊ธฐ์กด ๋ก์ง ์ ์ง
370+ course_name : tag , // ๊ฐ ์คํฐ๋๋ณ ํธ์ถ
371+ } )
372+ )
373+ ) ;
374+
375+ // ํ๊ทธ๋ณ ์ฝ๋ ์์ง
376+ const perTagResults = settled . map ( ( r , idx ) => {
377+ if ( r . status === "fulfilled" ) {
378+ return { tag : tags [ idx ] , code : r . value . returnCode } ;
379+ } else {
380+ return { tag : tags [ idx ] , code : 500 } ; // ๋คํธ์ํฌ ์ค๋ฅ ๊ฐ์ ๊ฒฝ์ฐ
381+ }
345382 } ) ;
346-
347- // ํ
์คํธ์ฉ returnCode ์ ์ฉ
348- // 200: ์ฑ๊ณต, 300: ์ผ๋ฐ ์คํจ, ๋๋จธ์ง(์: 302): โ๋ช
๋จ์ ์์โ ์๋๋ฆฌ์ค
349- setReturnCode ( testReturnCode ) ;
383+
384+ // ๋ชจ๋ 200์ด๋ฉด 200
385+ // 500์ด ํ๋๋ผ๋ ์์ผ๋ฉด 500 ("๋ช
๋จ์ ์์" ์๋๋ฆฌ์ค)
386+ // ๊ทธ ์ธ 300(์ผ๋ฐ ์คํจ)
387+ const codes = perTagResults . map ( r => r . code ) ;
388+ let overall : number ;
389+
390+ if ( codes . every ( c => c === 200 ) ) {
391+ overall = 200 ;
392+ } else if ( codes . some ( c => c === 500 ) ) {
393+ overall = 500 ;
394+ } else {
395+ overall = 300 ;
396+ }
397+
398+ setReturnCode ( overall ) ;
350399 } catch ( e ) {
351- // ํธ์ถ ์์ฒด ์คํจ ์ ์ผ๋ฐ ์คํจ๋ก ์ฒ๋ฆฌ
352- setReturnCode ( 300 ) ;
400+ // ์์ธ์ ์ผ๋ก ์ฌ๊ธฐ๊น์ง ๋จ์ด์ง๋ฉด ์ผ๋ฐ ์คํจ
401+ setReturnCode ( 500 ) ;
353402 } finally {
403+ clearInterval ( interval ) ; // ์์ ํ๊ฒ ์ธํฐ๋ฒ ์ ๋ฆฌ
354404 setIsLoading ( false ) ;
355405 setIsComplete ( true ) ;
356406 }
@@ -499,22 +549,6 @@ const ExportCertificateForm = () => {
499549 />
500550 </ FieldRow >
501551
502- < FieldRow label = "ํ
์คํธ ์ฝ๋" >
503- < StyledFormControl fullWidth size = "medium" >
504- < InputLabel id = "returncode-label" > returnCode (ํ
์คํธ)</ InputLabel >
505- < Select
506- labelId = "returncode-label"
507- value = { String ( testReturnCode ) }
508- label = "returnCode (ํ
์คํธ)"
509- onChange = { ( e ) => setTestReturnCode ( Number ( e . target . value ) ) }
510- >
511- < MenuItem value = "200" > 200 (์ฑ๊ณต)</ MenuItem >
512- < MenuItem value = "404" > 404 (๋ช
๋จ ์์)</ MenuItem >
513- < MenuItem value = "500" > 500 (์คํจ)</ MenuItem >
514- </ Select >
515- </ StyledFormControl >
516- </ FieldRow >
517-
518552 { /* Buttons */ }
519553 < Box sx = { { display : 'flex' , gap : 3 , mt : 2 } } >
520554 < StyledButton
@@ -611,7 +645,7 @@ const ExportCertificateForm = () => {
611645 </ Box >
612646 ) }
613647
614- { ! isLoading && returnCode === 500 && (
648+ { ! isLoading && returnCode === 300 && (
615649 /* ์ผ๋ฐ ์คํจ */
616650 < Box >
617651 < FailIcon />
@@ -631,7 +665,7 @@ const ExportCertificateForm = () => {
631665 </ Box >
632666 ) }
633667
634- { ! isLoading && returnCode === 404 && (
668+ { ! isLoading && returnCode === 500 && (
635669 /* ๋ช
๋จ ์์ */
636670 < Box >
637671 < FailIcon />
0 commit comments