diff --git a/.yarn/releases/yarn-4.1.0.cjs b/.yarn/releases/yarn-4.1.0.cjs index 3832537a..738adce5 100755 --- a/.yarn/releases/yarn-4.1.0.cjs +++ b/.yarn/releases/yarn-4.1.0.cjs @@ -1,5 +1,5 @@ #!/usr/bin/env node - +/* eslint-disable */ //prettier-ignore (()=>{var Z3e=Object.create;var NR=Object.defineProperty;var $3e=Object.getOwnPropertyDescriptor;var e_e=Object.getOwnPropertyNames;var t_e=Object.getPrototypeOf,r_e=Object.prototype.hasOwnProperty;var ve=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+t+'" is not supported')});var Et=(t,e)=>()=>(t&&(e=t(t=0)),e);var _=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),Vt=(t,e)=>{for(var r in e)NR(t,r,{get:e[r],enumerable:!0})},n_e=(t,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of e_e(e))!r_e.call(t,a)&&a!==r&&NR(t,a,{get:()=>e[a],enumerable:!(o=$3e(e,a))||o.enumerable});return t};var $e=(t,e,r)=>(r=t!=null?Z3e(t_e(t)):{},n_e(e||!t||!t.__esModule?NR(r,"default",{value:t,enumerable:!0}):r,t));var vi={};Vt(vi,{SAFE_TIME:()=>x7,S_IFDIR:()=>wD,S_IFLNK:()=>ID,S_IFMT:()=>Ou,S_IFREG:()=>jw});var Ou,wD,jw,ID,x7,k7=Et(()=>{Ou=61440,wD=16384,jw=32768,ID=40960,x7=456789e3});var ar={};Vt(ar,{EBADF:()=>Io,EBUSY:()=>i_e,EEXIST:()=>u_e,EINVAL:()=>o_e,EISDIR:()=>c_e,ENOENT:()=>a_e,ENOSYS:()=>s_e,ENOTDIR:()=>l_e,ENOTEMPTY:()=>f_e,EOPNOTSUPP:()=>p_e,EROFS:()=>A_e,ERR_DIR_CLOSED:()=>LR});function Tl(t,e){return Object.assign(new Error(`${t}: ${e}`),{code:t})}function i_e(t){return Tl("EBUSY",t)}function s_e(t,e){return Tl("ENOSYS",`${t}, ${e}`)}function o_e(t){return Tl("EINVAL",`invalid argument, ${t}`)}function Io(t){return Tl("EBADF",`bad file descriptor, ${t}`)}function a_e(t){return Tl("ENOENT",`no such file or directory, ${t}`)}function l_e(t){return Tl("ENOTDIR",`not a directory, ${t}`)}function c_e(t){return Tl("EISDIR",`illegal operation on a directory, ${t}`)}function u_e(t){return Tl("EEXIST",`file already exists, ${t}`)}function A_e(t){return Tl("EROFS",`read-only filesystem, ${t}`)}function f_e(t){return Tl("ENOTEMPTY",`directory not empty, ${t}`)}function p_e(t){return Tl("EOPNOTSUPP",`operation not supported, ${t}`)}function LR(){return Tl("ERR_DIR_CLOSED","Directory handle was closed")}var BD=Et(()=>{});var Ea={};Vt(Ea,{BigIntStatsEntry:()=>ty,DEFAULT_MODE:()=>UR,DirEntry:()=>OR,StatEntry:()=>ey,areStatsEqual:()=>_R,clearStats:()=>vD,convertToBigIntStats:()=>g_e,makeDefaultStats:()=>Q7,makeEmptyStats:()=>h_e});function Q7(){return new ey}function h_e(){return vD(Q7())}function vD(t){for(let e in t)if(Object.hasOwn(t,e)){let r=t[e];typeof r=="number"?t[e]=0:typeof r=="bigint"?t[e]=BigInt(0):MR.types.isDate(r)&&(t[e]=new Date(0))}return t}function g_e(t){let e=new ty;for(let r in t)if(Object.hasOwn(t,r)){let o=t[r];typeof o=="number"?e[r]=BigInt(o):MR.types.isDate(o)&&(e[r]=new Date(o))}return e.atimeNs=e.atimeMs*BigInt(1e6),e.mtimeNs=e.mtimeMs*BigInt(1e6),e.ctimeNs=e.ctimeMs*BigInt(1e6),e.birthtimeNs=e.birthtimeMs*BigInt(1e6),e}function _R(t,e){if(t.atimeMs!==e.atimeMs||t.birthtimeMs!==e.birthtimeMs||t.blksize!==e.blksize||t.blocks!==e.blocks||t.ctimeMs!==e.ctimeMs||t.dev!==e.dev||t.gid!==e.gid||t.ino!==e.ino||t.isBlockDevice()!==e.isBlockDevice()||t.isCharacterDevice()!==e.isCharacterDevice()||t.isDirectory()!==e.isDirectory()||t.isFIFO()!==e.isFIFO()||t.isFile()!==e.isFile()||t.isSocket()!==e.isSocket()||t.isSymbolicLink()!==e.isSymbolicLink()||t.mode!==e.mode||t.mtimeMs!==e.mtimeMs||t.nlink!==e.nlink||t.rdev!==e.rdev||t.size!==e.size||t.uid!==e.uid)return!1;let r=t,o=e;return!(r.atimeNs!==o.atimeNs||r.mtimeNs!==o.mtimeNs||r.ctimeNs!==o.ctimeNs||r.birthtimeNs!==o.birthtimeNs)}var MR,UR,OR,ey,ty,HR=Et(()=>{MR=$e(ve("util")),UR=33188,OR=class{constructor(){this.name="";this.path="";this.mode=0}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&61440)===16384}isFIFO(){return!1}isFile(){return(this.mode&61440)===32768}isSocket(){return!1}isSymbolicLink(){return(this.mode&61440)===40960}},ey=class{constructor(){this.uid=0;this.gid=0;this.size=0;this.blksize=0;this.atimeMs=0;this.mtimeMs=0;this.ctimeMs=0;this.birthtimeMs=0;this.atime=new Date(0);this.mtime=new Date(0);this.ctime=new Date(0);this.birthtime=new Date(0);this.dev=0;this.ino=0;this.mode=UR;this.nlink=1;this.rdev=0;this.blocks=1}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&61440)===16384}isFIFO(){return!1}isFile(){return(this.mode&61440)===32768}isSocket(){return!1}isSymbolicLink(){return(this.mode&61440)===40960}},ty=class{constructor(){this.uid=BigInt(0);this.gid=BigInt(0);this.size=BigInt(0);this.blksize=BigInt(0);this.atimeMs=BigInt(0);this.mtimeMs=BigInt(0);this.ctimeMs=BigInt(0);this.birthtimeMs=BigInt(0);this.atimeNs=BigInt(0);this.mtimeNs=BigInt(0);this.ctimeNs=BigInt(0);this.birthtimeNs=BigInt(0);this.atime=new Date(0);this.mtime=new Date(0);this.ctime=new Date(0);this.birthtime=new Date(0);this.dev=BigInt(0);this.ino=BigInt(0);this.mode=BigInt(UR);this.nlink=BigInt(1);this.rdev=BigInt(0);this.blocks=BigInt(1)}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&BigInt(61440))===BigInt(16384)}isFIFO(){return!1}isFile(){return(this.mode&BigInt(61440))===BigInt(32768)}isSocket(){return!1}isSymbolicLink(){return(this.mode&BigInt(61440))===BigInt(40960)}}});function C_e(t){let e,r;if(e=t.match(y_e))t=e[1];else if(r=t.match(E_e))t=`\\\\${r[1]?".\\":""}${r[2]}`;else return t;return t.replace(/\//g,"\\")}function w_e(t){t=t.replace(/\\/g,"/");let e,r;return(e=t.match(d_e))?t=`/${e[1]}`:(r=t.match(m_e))&&(t=`/unc/${r[1]?".dot/":""}${r[2]}`),t}function DD(t,e){return t===ue?R7(e):jR(e)}var Gw,Bt,dr,ue,V,F7,d_e,m_e,y_e,E_e,jR,R7,Ca=Et(()=>{Gw=$e(ve("path")),Bt={root:"/",dot:".",parent:".."},dr={home:"~",nodeModules:"node_modules",manifest:"package.json",lockfile:"yarn.lock",virtual:"__virtual__",pnpJs:".pnp.js",pnpCjs:".pnp.cjs",pnpData:".pnp.data.json",pnpEsmLoader:".pnp.loader.mjs",rc:".yarnrc.yml",env:".env"},ue=Object.create(Gw.default),V=Object.create(Gw.default.posix);ue.cwd=()=>process.cwd();V.cwd=process.platform==="win32"?()=>jR(process.cwd()):process.cwd;process.platform==="win32"&&(V.resolve=(...t)=>t.length>0&&V.isAbsolute(t[0])?Gw.default.posix.resolve(...t):Gw.default.posix.resolve(V.cwd(),...t));F7=function(t,e,r){return e=t.normalize(e),r=t.normalize(r),e===r?".":(e.endsWith(t.sep)||(e=e+t.sep),r.startsWith(e)?r.slice(e.length):null)};ue.contains=(t,e)=>F7(ue,t,e);V.contains=(t,e)=>F7(V,t,e);d_e=/^([a-zA-Z]:.*)$/,m_e=/^\/\/(\.\/)?(.*)$/,y_e=/^\/([a-zA-Z]:.*)$/,E_e=/^\/unc\/(\.dot\/)?(.*)$/;jR=process.platform==="win32"?w_e:t=>t,R7=process.platform==="win32"?C_e:t=>t;ue.fromPortablePath=R7;ue.toPortablePath=jR});async function SD(t,e){let r="0123456789abcdef";await t.mkdirPromise(e.indexPath,{recursive:!0});let o=[];for(let a of r)for(let n of r)o.push(t.mkdirPromise(t.pathUtils.join(e.indexPath,`${a}${n}`),{recursive:!0}));return await Promise.all(o),e.indexPath}async function T7(t,e,r,o,a){let n=t.pathUtils.normalize(e),u=r.pathUtils.normalize(o),A=[],p=[],{atime:h,mtime:E}=a.stableTime?{atime:Og,mtime:Og}:await r.lstatPromise(u);await t.mkdirpPromise(t.pathUtils.dirname(e),{utimes:[h,E]}),await GR(A,p,t,n,r,u,{...a,didParentExist:!0});for(let I of A)await I();await Promise.all(p.map(I=>I()))}async function GR(t,e,r,o,a,n,u){let A=u.didParentExist?await N7(r,o):null,p=await a.lstatPromise(n),{atime:h,mtime:E}=u.stableTime?{atime:Og,mtime:Og}:p,I;switch(!0){case p.isDirectory():I=await B_e(t,e,r,o,A,a,n,p,u);break;case p.isFile():I=await S_e(t,e,r,o,A,a,n,p,u);break;case p.isSymbolicLink():I=await P_e(t,e,r,o,A,a,n,p,u);break;default:throw new Error(`Unsupported file type (${p.mode})`)}return(u.linkStrategy?.type!=="HardlinkFromIndex"||!p.isFile())&&((I||A?.mtime?.getTime()!==E.getTime()||A?.atime?.getTime()!==h.getTime())&&(e.push(()=>r.lutimesPromise(o,h,E)),I=!0),(A===null||(A.mode&511)!==(p.mode&511))&&(e.push(()=>r.chmodPromise(o,p.mode&511)),I=!0)),I}async function N7(t,e){try{return await t.lstatPromise(e)}catch{return null}}async function B_e(t,e,r,o,a,n,u,A,p){if(a!==null&&!a.isDirectory())if(p.overwrite)t.push(async()=>r.removePromise(o)),a=null;else return!1;let h=!1;a===null&&(t.push(async()=>{try{await r.mkdirPromise(o,{mode:A.mode})}catch(v){if(v.code!=="EEXIST")throw v}}),h=!0);let E=await n.readdirPromise(u),I=p.didParentExist&&!a?{...p,didParentExist:!1}:p;if(p.stableSort)for(let v of E.sort())await GR(t,e,r,r.pathUtils.join(o,v),n,n.pathUtils.join(u,v),I)&&(h=!0);else(await Promise.all(E.map(async x=>{await GR(t,e,r,r.pathUtils.join(o,x),n,n.pathUtils.join(u,x),I)}))).some(x=>x)&&(h=!0);return h}async function v_e(t,e,r,o,a,n,u,A,p,h){let E=await n.checksumFilePromise(u,{algorithm:"sha1"}),I=420,v=A.mode&511,x=`${E}${v!==I?v.toString(8):""}`,C=r.pathUtils.join(h.indexPath,E.slice(0,2),`${x}.dat`),R;(ce=>(ce[ce.Lock=0]="Lock",ce[ce.Rename=1]="Rename"))(R||={});let L=1,U=await N7(r,C);if(a){let ae=U&&a.dev===U.dev&&a.ino===U.ino,fe=U?.mtimeMs!==I_e;if(ae&&fe&&h.autoRepair&&(L=0,U=null),!ae)if(p.overwrite)t.push(async()=>r.removePromise(o)),a=null;else return!1}let J=!U&&L===1?`${C}.${Math.floor(Math.random()*4294967296).toString(16).padStart(8,"0")}`:null,te=!1;return t.push(async()=>{if(!U&&(L===0&&await r.lockPromise(C,async()=>{let ae=await n.readFilePromise(u);await r.writeFilePromise(C,ae)}),L===1&&J)){let ae=await n.readFilePromise(u);await r.writeFilePromise(J,ae);try{await r.linkPromise(J,C)}catch(fe){if(fe.code==="EEXIST")te=!0,await r.unlinkPromise(J);else throw fe}}a||await r.linkPromise(C,o)}),e.push(async()=>{U||(await r.lutimesPromise(C,Og,Og),v!==I&&await r.chmodPromise(C,v)),J&&!te&&await r.unlinkPromise(J)}),!1}async function D_e(t,e,r,o,a,n,u,A,p){if(a!==null)if(p.overwrite)t.push(async()=>r.removePromise(o)),a=null;else return!1;return t.push(async()=>{let h=await n.readFilePromise(u);await r.writeFilePromise(o,h)}),!0}async function S_e(t,e,r,o,a,n,u,A,p){return p.linkStrategy?.type==="HardlinkFromIndex"?v_e(t,e,r,o,a,n,u,A,p,p.linkStrategy):D_e(t,e,r,o,a,n,u,A,p)}async function P_e(t,e,r,o,a,n,u,A,p){if(a!==null)if(p.overwrite)t.push(async()=>r.removePromise(o)),a=null;else return!1;return t.push(async()=>{await r.symlinkPromise(DD(r.pathUtils,await n.readlinkPromise(u)),o)}),!0}var Og,I_e,qR=Et(()=>{Ca();Og=new Date(456789e3*1e3),I_e=Og.getTime()});function PD(t,e,r,o){let a=()=>{let n=r.shift();if(typeof n>"u")return null;let u=t.pathUtils.join(e,n);return Object.assign(t.statSync(u),{name:n,path:void 0})};return new qw(e,a,o)}var qw,L7=Et(()=>{BD();qw=class{constructor(e,r,o={}){this.path=e;this.nextDirent=r;this.opts=o;this.closed=!1}throwIfClosed(){if(this.closed)throw LR()}async*[Symbol.asyncIterator](){try{let e;for(;(e=await this.read())!==null;)yield e}finally{await this.close()}}read(e){let r=this.readSync();return typeof e<"u"?e(null,r):Promise.resolve(r)}readSync(){return this.throwIfClosed(),this.nextDirent()}close(e){return this.closeSync(),typeof e<"u"?e(null):Promise.resolve()}closeSync(){this.throwIfClosed(),this.opts.onClose?.(),this.closed=!0}}});function O7(t,e){if(t!==e)throw new Error(`Invalid StatWatcher status: expected '${e}', got '${t}'`)}var M7,ry,U7=Et(()=>{M7=ve("events");HR();ry=class extends M7.EventEmitter{constructor(r,o,{bigint:a=!1}={}){super();this.status="ready";this.changeListeners=new Map;this.startTimeout=null;this.fakeFs=r,this.path=o,this.bigint=a,this.lastStats=this.stat()}static create(r,o,a){let n=new ry(r,o,a);return n.start(),n}start(){O7(this.status,"ready"),this.status="running",this.startTimeout=setTimeout(()=>{this.startTimeout=null,this.fakeFs.existsSync(this.path)||this.emit("change",this.lastStats,this.lastStats)},3)}stop(){O7(this.status,"running"),this.status="stopped",this.startTimeout!==null&&(clearTimeout(this.startTimeout),this.startTimeout=null),this.emit("stop")}stat(){try{return this.fakeFs.statSync(this.path,{bigint:this.bigint})}catch{let o=this.bigint?new ty:new ey;return vD(o)}}makeInterval(r){let o=setInterval(()=>{let a=this.stat(),n=this.lastStats;_R(a,n)||(this.lastStats=a,this.emit("change",a,n))},r.interval);return r.persistent?o:o.unref()}registerChangeListener(r,o){this.addListener("change",r),this.changeListeners.set(r,this.makeInterval(o))}unregisterChangeListener(r){this.removeListener("change",r);let o=this.changeListeners.get(r);typeof o<"u"&&clearInterval(o),this.changeListeners.delete(r)}unregisterAllChangeListeners(){for(let r of this.changeListeners.keys())this.unregisterChangeListener(r)}hasChangeListeners(){return this.changeListeners.size>0}ref(){for(let r of this.changeListeners.values())r.ref();return this}unref(){for(let r of this.changeListeners.values())r.unref();return this}}});function ny(t,e,r,o){let a,n,u,A;switch(typeof r){case"function":a=!1,n=!0,u=5007,A=r;break;default:({bigint:a=!1,persistent:n=!0,interval:u=5007}=r),A=o;break}let p=bD.get(t);typeof p>"u"&&bD.set(t,p=new Map);let h=p.get(e);return typeof h>"u"&&(h=ry.create(t,e,{bigint:a}),p.set(e,h)),h.registerChangeListener(A,{persistent:n,interval:u}),h}function Mg(t,e,r){let o=bD.get(t);if(typeof o>"u")return;let a=o.get(e);typeof a>"u"||(typeof r>"u"?a.unregisterAllChangeListeners():a.unregisterChangeListener(r),a.hasChangeListeners()||(a.stop(),o.delete(e)))}function Ug(t){let e=bD.get(t);if(!(typeof e>"u"))for(let r of e.keys())Mg(t,r)}var bD,YR=Et(()=>{U7();bD=new WeakMap});function b_e(t){let e=t.match(/\r?\n/g);if(e===null)return H7.EOL;let r=e.filter(a=>a===`\r `).length,o=e.length-r;return r>o?`\r diff --git a/public/svgs/btn_filter.svg b/public/svgs/btn_filter.svg new file mode 100644 index 00000000..abb22ac6 --- /dev/null +++ b/public/svgs/btn_filter.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/svgs/ic_delete.svg b/public/svgs/ic_delete.svg index 176caeb0..ba67c1cf 100644 --- a/public/svgs/ic_delete.svg +++ b/public/svgs/ic_delete.svg @@ -1,4 +1,3 @@ - - - + + diff --git a/public/svgs/ic_refresh.svg b/public/svgs/ic_refresh.svg new file mode 100644 index 00000000..bfba7c52 --- /dev/null +++ b/public/svgs/ic_refresh.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/svgs/icon_download.svg b/public/svgs/icon_download.svg new file mode 100644 index 00000000..60f11990 --- /dev/null +++ b/public/svgs/icon_download.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apis/domains/tickets/api.ts b/src/apis/domains/tickets/api.ts index 1bedb136..c0f4e965 100644 --- a/src/apis/domains/tickets/api.ts +++ b/src/apis/domains/tickets/api.ts @@ -3,6 +3,7 @@ import { components } from "@typings/api/schema"; import { ApiResponseType } from "@typings/commonType"; import { PatchFormDataProps } from "@typings/deleteBookerFormatProps"; import { AxiosResponse } from "axios"; +import { convertingScheduleNumber } from "@constants/convertingScheduleNumber"; // 예매자 목록 조회 API (GET) export interface getTicketReq { @@ -15,11 +16,42 @@ export interface getTicketReq { type TicketRetrieveResponse = components["schemas"]["TicketRetrieveResponse"]; export const getTicketRetrieve = async ( - formData: getTicketReq + formData: getTicketReq, + filterList ): Promise => { try { + const params = new URLSearchParams(); + filterList.scheduleNumber.map((item) => + params.append("scheduleNumbers", convertingScheduleNumber(item)) + ); + filterList.bookingStatus.map((item) => params.append("bookingStatuses", item)); + + const response: AxiosResponse> = await get( + `tickets/${formData.performanceId}?${params.toString()}` + ); + + return response.data.data; + } catch (error) { + console.log("error", error); + return null; + } +}; + +export const getTicketRetrieveSearch = async ( + formData: getTicketReq, + searchWord, + filterList +): Promise => { + try { + const params = new URLSearchParams(); + params.append("searchWord", searchWord); + filterList.scheduleNumber.map((item) => + params.append("scheduleNumbers", convertingScheduleNumber(item)) + ); + filterList.bookingStatus.map((item) => params.append("bookingStatuses", item)); + const response: AxiosResponse> = await get( - `tickets/${formData.performanceId}` + `tickets/search/${formData.performanceId}?${params.toString()}` ); return response.data.data; @@ -29,17 +61,11 @@ export const getTicketRetrieve = async ( } }; +type SuccessResponseVoid = components["schemas"]["SuccessResponseVoid"]; + // 예매자 입급 여부 수정 API (PUT) -/* -export interface putTicketReq { - performanceId: number; - performanceTitle: string; - totalScheduleCoun: number; - bookingList: BookingListProps[]; -} -*/ + export type TicketUpdateRequest = components["schemas"]["TicketUpdateRequest"]; -type SuccessResponseVoid = components["schemas"]["SuccessResponseVoid"]; //async 함수는 항상 promise로 감싸서 값을 리턴한다. //즉, 비동기 함수의 값을 사용하려면 추후 await이나 then 을 사용해야 한다던데.. @@ -49,7 +75,7 @@ export const putTicketUpdate = async ( ): Promise => { try { const response: AxiosResponse> = await put( - "tickets", + "tickets/update", formData ); @@ -60,18 +86,39 @@ export const putTicketUpdate = async ( } }; -// 예매자 취소 API (PATCH) -//이거 타입 잘못되었을 수도..? bookingList 가 number를 담은 배열로 되어 있는데, 실제로는 아니었음 -//export type TicketDeleteRequest = components["schemas"]["TicketDeleteRequest"]; +// 예매자 환불처리 (PUT) + +export type TicketRefundRequest = components["schemas"]["TicketRefundRequest"]; -export const patchTicketCancel = async ( - formData: PatchFormDataProps +export const putTicketRefund = async ( + formData: TicketRefundRequest ): Promise => { try { - const response: AxiosResponse> = await patch( - "tickets", + const response: AxiosResponse> = await put( + "tickets/refund", formData ); + + return response.data.data; + } catch (error) { + console.log("error", error); + return null; + } +}; + +// 예매자 삭제 (PUT) + +export type TicketDeleteRequest = components["schemas"]["TicketDeleteRequest"]; + +export const putTicketDelete = async ( + formData: TicketDeleteRequest +): Promise => { + try { + const response: AxiosResponse> = await put( + "tickets/delete", + formData + ); + return response.data.data; } catch (error) { console.log("error", error); diff --git a/src/apis/domains/tickets/queries.ts b/src/apis/domains/tickets/queries.ts index f4b1b5cb..1088484b 100644 --- a/src/apis/domains/tickets/queries.ts +++ b/src/apis/domains/tickets/queries.ts @@ -1,51 +1,72 @@ import { QueryClient, useMutation, useQuery } from "@tanstack/react-query"; -import { PatchFormDataProps } from "@typings/deleteBookerFormatProps"; import { getTicketReq, getTicketRetrieve, - patchTicketCancel, + getTicketRetrieveSearch, + putTicketDelete, + putTicketRefund, putTicketUpdate, + TicketDeleteRequest, + TicketRefundRequest, TicketUpdateRequest, } from "./api"; -import { BOOKING_QUERY_KEY } from "../bookings/queries"; - // 예매자 목록 조회 API (GET)를 위한 쿼리 작성 const QUERY_KEY = { - LIST: "list", + SELLER_BOOKING_LIST: "sellerBookingList", }; -export const useTicketRetrive = (formData: getTicketReq) => { +export const useTicketRetrive = (formData: getTicketReq, filterList) => { + return useQuery({ + queryKey: [QUERY_KEY.SELLER_BOOKING_LIST], + queryFn: () => getTicketRetrieve(formData, filterList), + staleTime: 1000 * 60 * 60, + gcTime: 1000 * 60 * 60 * 24, + }); +}; + +// 예매자 목록 검색 API (GET)를 위한 쿼리 작성 + +export const useTicketRetriveSearch = (formData: getTicketReq, searchWord, filterList) => { return useQuery({ - queryKey: [QUERY_KEY.LIST, BOOKING_QUERY_KEY.BOOKING_LIST], - queryFn: () => getTicketRetrieve(formData), - // staleTime: 1000 * 60 * 60, + queryKey: [QUERY_KEY.SELLER_BOOKING_LIST], + queryFn: () => getTicketRetrieveSearch(formData, searchWord, filterList), + staleTime: 1000 * 60 * 60, gcTime: 1000 * 60 * 60 * 24, }); }; +const queryClient = new QueryClient(); + // 예매자 입급 여부 수정 API (PUT)를 위한 쿼리 작성 export const useTicketUpdate = () => { - const queryClient = new QueryClient(); - return useMutation({ mutationFn: (formData: TicketUpdateRequest) => putTicketUpdate(formData), onSuccess: (res) => { - queryClient.invalidateQueries({ queryKey: [QUERY_KEY.LIST, BOOKING_QUERY_KEY.BOOKING_LIST] }); - queryClient.refetchQueries({ queryKey: [QUERY_KEY.LIST, BOOKING_QUERY_KEY.BOOKING_LIST] }); + queryClient.invalidateQueries({ queryKey: [QUERY_KEY.SELLER_BOOKING_LIST] }); + queryClient.refetchQueries({ queryKey: [QUERY_KEY.SELLER_BOOKING_LIST] }); }, }); }; -// 예매자를 취소하는 API (PATCH)를 위한 쿼리 작성 -export const useTicketPatch = () => { - const queryClient = new QueryClient(); +// 예매자 환불 여부 수정 API (PUT)를 위한 쿼리 작성 +export const useTicketRefund = () => { + return useMutation({ + mutationFn: (formData: TicketRefundRequest) => putTicketRefund(formData), + onSuccess: (res) => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEY.SELLER_BOOKING_LIST] }); + queryClient.refetchQueries({ queryKey: [QUERY_KEY.SELLER_BOOKING_LIST] }); + }, + }); +}; +// 예매자 삭제 여부 수정 API (PUT)를 위한 쿼리 작성 +export const useTicketDelete = () => { return useMutation({ - mutationFn: (formData: PatchFormDataProps) => patchTicketCancel(formData), + mutationFn: (formData: TicketDeleteRequest) => putTicketDelete(formData), onSuccess: (res) => { - queryClient.invalidateQueries({ queryKey: [QUERY_KEY.LIST, BOOKING_QUERY_KEY.BOOKING_LIST] }); - queryClient.refetchQueries({ queryKey: [QUERY_KEY.LIST, BOOKING_QUERY_KEY.BOOKING_LIST] }); + queryClient.invalidateQueries({ queryKey: [QUERY_KEY.SELLER_BOOKING_LIST] }); + queryClient.refetchQueries({ queryKey: [QUERY_KEY.SELLER_BOOKING_LIST] }); }, }); }; diff --git a/src/assets/svgs/BtnFilter.tsx b/src/assets/svgs/BtnFilter.tsx new file mode 100644 index 00000000..aa0cc980 --- /dev/null +++ b/src/assets/svgs/BtnFilter.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import type { SVGProps } from "react"; +const SvgBtnFilter = (props: SVGProps) => ( + + + + + +); +export default SvgBtnFilter; diff --git a/src/assets/svgs/IcDelete.tsx b/src/assets/svgs/IcDelete.tsx index 76f8668a..08e96642 100644 --- a/src/assets/svgs/IcDelete.tsx +++ b/src/assets/svgs/IcDelete.tsx @@ -1,14 +1,13 @@ import * as React from "react"; import type { SVGProps } from "react"; const SvgIcDelete = (props: SVGProps) => ( - - + ); diff --git a/src/assets/svgs/IcRefresh.tsx b/src/assets/svgs/IcRefresh.tsx new file mode 100644 index 00000000..c6324986 --- /dev/null +++ b/src/assets/svgs/IcRefresh.tsx @@ -0,0 +1,11 @@ +import * as React from "react"; +import type { SVGProps } from "react"; +const SvgIcRefresh = (props: SVGProps) => ( + + + +); +export default SvgIcRefresh; diff --git a/src/assets/svgs/IconDownload.tsx b/src/assets/svgs/IconDownload.tsx new file mode 100644 index 00000000..6a448b89 --- /dev/null +++ b/src/assets/svgs/IconDownload.tsx @@ -0,0 +1,11 @@ +import * as React from "react"; +import type { SVGProps } from "react"; +const SvgIconDownload = (props: SVGProps) => ( + + + +); +export default SvgIconDownload; diff --git a/src/assets/svgs/IconSearch.tsx b/src/assets/svgs/IconSearch.tsx index 9ee48803..6305f24d 100644 --- a/src/assets/svgs/IconSearch.tsx +++ b/src/assets/svgs/IconSearch.tsx @@ -3,7 +3,7 @@ import type { SVGProps } from "react"; const SvgIconSearch = (props: SVGProps) => ( ` border-radius: 2rem 2rem 0 0; `; -export const Title = styled.h1` - margin-bottom: 2.8rem; +export const Title = styled.h1<{ $title?: string }>` + margin-bottom: ${({ $title }) => ($title ? "2.8rem" : "0")}; color: ${({ theme }) => theme.colors.white}; ${({ theme }) => theme.fonts.heading4}; diff --git a/src/components/commons/bottomSheet/BottomSheet.tsx b/src/components/commons/bottomSheet/BottomSheet.tsx index 98da34b9..c4600f51 100644 --- a/src/components/commons/bottomSheet/BottomSheet.tsx +++ b/src/components/commons/bottomSheet/BottomSheet.tsx @@ -13,7 +13,7 @@ const BottomSheet = ({ isOpen, title, paddingTop, children }: BottomSheetPropTyp return ( e.stopPropagation()}> - {title} + {title} {children} diff --git a/src/components/commons/navigation/Navigation.styled.ts b/src/components/commons/navigation/Navigation.styled.ts index 6ff7038d..c2d03ed3 100644 --- a/src/components/commons/navigation/Navigation.styled.ts +++ b/src/components/commons/navigation/Navigation.styled.ts @@ -1,4 +1,11 @@ -import { IcHamburgar, IconArrowLeft, IconArrowRight, IconLogo, IconXButton } from "@assets/svgs"; +import { + IcHamburgar, + IconArrowLeft, + IconArrowRight, + IconLogo, + IconXButton, + IconDownload, +} from "@assets/svgs"; import styled from "styled-components"; export const NavigationWrapper = styled.div` @@ -38,6 +45,13 @@ export const HamburgarButton = styled(IcHamburgar)` cursor: pointer; `; +export const DownloadButton = styled(IconDownload)` + width: 2rem; + height: 2rem; + + cursor: pointer; +`; + export const NavigationLeftButton = styled(IconArrowLeft)` box-sizing: content-box; width: 2.4rem; @@ -67,7 +81,7 @@ export const NavigationXButton = styled(IconXButton)` // TODO: 뷰에 띄워보니 padding이 없어 스타일링 이상함 디자이너 분께 물어보기 export const SubTextButton = styled.button` - width: 3.2rem; + display: flex; margin: 0 0.4rem 0 0; ${({ theme }) => theme.fonts["body1-normal-semi"]}; diff --git a/src/components/commons/navigation/Navigation.tsx b/src/components/commons/navigation/Navigation.tsx index 4e832e8d..7e03ad4f 100644 --- a/src/components/commons/navigation/Navigation.tsx +++ b/src/components/commons/navigation/Navigation.tsx @@ -83,6 +83,19 @@ const Navigation = () => { ); } + if (headerStyle === NAVIGATION_STATE.ICON_TITLE_DOWNLOAD) { + return ( + + + {title} + + {subText} + + + + ); + } + if (!headerStyle) { return null; } diff --git a/src/constants/convertingBookingStatus.ts b/src/constants/convertingBookingStatus.ts new file mode 100644 index 00000000..968965a7 --- /dev/null +++ b/src/constants/convertingBookingStatus.ts @@ -0,0 +1,20 @@ +type PaymentType = + | "CHECKING_PAYMENT" + | "BOOKING_CONFIRMED" + | "BOOKING_CANCELLED" + | "REFUND_REQUESTED"; + +export const convertingBookingStatus = (_bookingStatus: PaymentType): string => { + switch (_bookingStatus) { + case "CHECKING_PAYMENT": + return "미입금"; + case "BOOKING_CONFIRMED": + return "입금 완료"; + case "BOOKING_CANCELLED": + return "취소 완료"; + case "REFUND_REQUESTED": + return "환불 요청"; + default: + throw new Error("알 수 없는 상태입니다."); + } +}; diff --git a/src/constants/convertingScheduleNumber.ts b/src/constants/convertingScheduleNumber.ts new file mode 100644 index 00000000..502b6104 --- /dev/null +++ b/src/constants/convertingScheduleNumber.ts @@ -0,0 +1,26 @@ +export const convertingScheduleNumber = (_scheduleNumber: number): string => { + switch (_scheduleNumber) { + case 1: + return "FIRST"; + case 2: + return "SECOND"; + case 3: + return "THIRD"; + case 4: + return "FOURTH"; + case 5: + return "FIFTH"; + case 6: + return "SIXTH"; + case 7: + return "SEVENTH"; + case 8: + return "EIGHTH"; + case 9: + return "NINTH"; + case 10: + return "TENTH"; + default: + return "UNKNOWN"; + } +}; diff --git a/src/constants/navigationState.ts b/src/constants/navigationState.ts index c4673cc4..36bc9239 100644 --- a/src/constants/navigationState.ts +++ b/src/constants/navigationState.ts @@ -7,6 +7,7 @@ export const NAVIGATION_STATE = { ICON: "icon", LOGO_HAMBURGAR: "logoHamburgar", ICON_ICON: "iconIcon", + ICON_TITLE_DOWNLOAD: "iconTitleDownload", } as const; export type NavigationState = (typeof NAVIGATION_STATE)[keyof typeof NAVIGATION_STATE]; diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts new file mode 100644 index 00000000..b6f68617 --- /dev/null +++ b/src/hooks/useDebounce.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from "react"; + +const useDebounce = (value: string, delay: number) => { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(timer); + }; + }, [value]); + + return debouncedValue; +}; + +export default useDebounce; diff --git a/src/pages/cancel/Cancel.styled.ts b/src/pages/cancel/Cancel.styled.ts index 3f8323cc..052c83b8 100644 --- a/src/pages/cancel/Cancel.styled.ts +++ b/src/pages/cancel/Cancel.styled.ts @@ -34,6 +34,7 @@ export const PerformBox = styled.div` table { width: 100%; + margin: -0.4rem; border-collapse: separate; border-spacing: 0.4rem 0.4rem; @@ -96,7 +97,7 @@ export const RadioWrapper = styled.div` `; export const ButtonWrapper = styled.div` - position: absolute; + position: fixed; bottom: 2.4rem; `; diff --git a/src/pages/cancel/Cancel.tsx b/src/pages/cancel/Cancel.tsx index 20aa30e3..7801dd14 100644 --- a/src/pages/cancel/Cancel.tsx +++ b/src/pages/cancel/Cancel.tsx @@ -8,7 +8,7 @@ import { Toast, } from "@components/commons"; import { NAVIGATION_STATE } from "@constants/navigationState"; -import { useHeader, useModal, useToast } from "@hooks"; +import { useHeader, useModal } from "@hooks"; import React, { useEffect, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { convertingNumber } from "@constants/convertingNumber"; @@ -16,12 +16,17 @@ import * as S from "./Cancel.styled"; import RadioButton from "./../cancel/components/select/RadioButton"; import { handleChange, handleBankClick, isFormValid } from "./utils"; import { useCancelBooking } from "../../hooks/useCancelBooking"; +import { numericFilter } from "@utils/useInputFilter"; const Cancel = () => { const { setHeader } = useHeader(); const { openAlert } = useModal(); const { state } = useLocation(); - const { confirmCancelAction } = useCancelBooking(state.bookerName, state.number, state.password); + const { confirmCancelAction } = useCancelBooking( + state?.bookerName, + state?.number, + state?.password + ); const navigate = useNavigate(); const [isDeposit, setIsDeposit] = useState(null); const [bankOpen, setBankOpen] = useState(false); @@ -29,6 +34,21 @@ const Cancel = () => { const [accountNumber, setAccountNumber] = useState(""); const [accountHolder, setAccountHolder] = useState(""); + useEffect(() => { + if (!state) { + const user = localStorage.getItem("user"); + openAlert({ + title: "잘못된 접근입니다.", + okText: "확인", + okCallback: () => navigate(user ? "/lookup" : "/main"), + }); + } + }, []); + + if (!state) { + return; + } + const performanceDateArray = state.bookingDetails.performanceDate.split("-"); const performanceDataDate = performanceDateArray[2].split("T"); @@ -48,17 +68,6 @@ const Cancel = () => { }); }, [setHeader]); - useEffect(() => { - if (!state) { - openAlert({ - title: "잘못된 접근입니다.", - subTitle: "이전 페이지로 이동합니다.", - okText: "확인", - okCallback: () => navigate(-1), - }); - } - }, [state, openAlert, navigate]); - const handleCancelClick = (isDeposit) => { const requestData = { bookingId: state.bookingDetails.bookingId, @@ -133,7 +142,7 @@ const Cancel = () => { name="accountNumber" value={accountNumber} onChange={(e) => handleChange(e, setAccountNumber)} - filter={(value: string) => value.replace(/[^0-9]/g, "")} // 숫자만 허용 + filter={numericFilter} placeholder="입금 받으실 계좌번호를 (-) 제외 숫자만 입력해주세요." inputMode="numeric" /> diff --git a/src/pages/cancel/utils/index.ts b/src/pages/cancel/utils/index.ts index 29299cbe..0d32c13f 100644 --- a/src/pages/cancel/utils/index.ts +++ b/src/pages/cancel/utils/index.ts @@ -1,17 +1,17 @@ -import React from "react"; +import React, { ChangeEvent, Dispatch, SetStateAction } from "react"; export const handleChange = ( - e: React.ChangeEvent, - setState: React.Dispatch> + e: ChangeEvent, + setState: Dispatch> ) => { setState(e.target.value); }; export const handleBankClick = ( value: string, - setState: React.Dispatch>, - setbankName: React.Dispatch>, - setOpen: React.Dispatch> + setState: Dispatch>, + setbankName: Dispatch>, + setOpen: Dispatch> ) => { setbankName(value); setState(value); diff --git a/src/pages/gig/components/peopleCard/PeopleCard.tsx b/src/pages/gig/components/peopleCard/PeopleCard.tsx index e5cb0691..42060668 100644 --- a/src/pages/gig/components/peopleCard/PeopleCard.tsx +++ b/src/pages/gig/components/peopleCard/PeopleCard.tsx @@ -9,7 +9,7 @@ interface PeopleCardProps { const PeopleCard = ({ photo, role, name }: PeopleCardProps) => { return ( - + {photo && } {role} {name} diff --git a/src/pages/lookup/Lookup.tsx b/src/pages/lookup/Lookup.tsx index 35d572d0..0e17cdd9 100644 --- a/src/pages/lookup/Lookup.tsx +++ b/src/pages/lookup/Lookup.tsx @@ -14,7 +14,6 @@ import { useHeader } from "@hooks"; import { useCancelBooking } from "src/hooks/useCancelBooking"; import { Toast } from "@components/commons"; import { IconCheck } from "@assets/svgs"; -import { ToastMessage } from "./../../components/commons/toast/Toast.styled"; interface LookupProps { userId: number; @@ -26,7 +25,7 @@ interface LookupProps { performanceVenue: string; purchaseTicketCount: number; scheduleNumber: string; - bookerName: string; + name: string; bookerPhoneNumber: string; bankName: string; performanceContact: string; @@ -45,14 +44,16 @@ const Lookup = () => { const navigate = useNavigate(); const { confirmCancelAction, toastMessage } = useCancelBooking( - state?.bookerName, - state?.number, + state?.name, + state?.phone, state?.password ); - + const [isFetching, setIsFetching] = useState(false); const { data: memberData, isLoading: isMemberLoading } = useGetMemberBookingList(); // 회원 예매 조회 const { getCachedBookingList, fetchBookingList } = useLazyPostGuestBookingList(); - const cachedData = getCachedBookingList(state?.bookerName, state?.number, state?.password); // 비회원 예매 조회 + const [cachedData, setCachedData] = useState( + getCachedBookingList(state?.name, state?.phone, state?.password) + ); // 비회원 예매 조회 const handleCancel = (bookingId: number, totalPaymentAmount: number) => { if (totalPaymentAmount === 0) { @@ -62,21 +63,19 @@ const Lookup = () => { const bookingDetails = (memberData ?? cachedData)?.find((item) => item.bookingId === bookingId); if (bookingDetails) { - navigate("/cancel", { state: { ...state, bookingDetails } }); + navigate("/lookup/cancel", { state: { ...state, bookingDetails } }); } }; useEffect(() => { - if (state?.bookerName && state?.number && state?.password && !cachedData) { + if (state?.name && state?.phone && state?.password && !cachedData && !isFetching) { // 비회원 데이터가 캐시에 없을 경우 새로 가져옴 - fetchBookingList({ - name: state.bookerName, - phone: state.number, - password: state.password, - birth: state.birth || "", + fetchBookingList(state).finally(() => { + setIsFetching(false); + setCachedData(getCachedBookingList(state.name, state.phone, state.password)); }); } - }, [state, cachedData, fetchBookingList]); + }, [state, cachedData, fetchBookingList, isFetching]); useEffect(() => { if (state?.toastMessage) { @@ -86,7 +85,7 @@ const Lookup = () => { return () => clearTimeout(timer); } - }, [state, navigate]); + }, [state]); const { setHeader } = useHeader(); useEffect(() => { diff --git a/src/pages/modifyManage/ModifyMaker.tsx b/src/pages/modifyManage/ModifyMaker.tsx index d3b3bb31..04bcf084 100644 --- a/src/pages/modifyManage/ModifyMaker.tsx +++ b/src/pages/modifyManage/ModifyMaker.tsx @@ -25,11 +25,11 @@ const ModifyManageMaker = ({ useEffect(() => { const allCastFieldsFilled = castModifyRequests.every( - (cast) => cast.castName && cast.castRole && cast.castPhoto + (cast) => cast.castName && cast.castRole ); const allStaffFieldsFilled = staffModifyRequests.every( - (staff) => staff.staffName && staff.staffRole && staff.staffPhoto + (staff) => staff.staffName && staff.staffRole ); setIsButtonDisabled( diff --git a/src/pages/modifyManage/ModifyManage.tsx b/src/pages/modifyManage/ModifyManage.tsx index d6d374ad..31822a40 100644 --- a/src/pages/modifyManage/ModifyManage.tsx +++ b/src/pages/modifyManage/ModifyManage.tsx @@ -333,11 +333,16 @@ const ModifyManage = () => { const extractUrls = (data: PresignedResponse) => { //앞부분(유효한 부분)만 떼어내서 저장(뒷 부분은 사진이 뜨는 url이 아님) posterUrls = Object.values(data.poster).map((url) => url.split("?")[0]); - castUrls = Object.values(data.cast).map((url) => url.split("?")[0]); - staffUrls = Object.values(data.staff).map((url) => url.split("?")[0]); + castUrls = Object.values(data.cast).map((url) => (url !== "" ? url.split("?")[0] : null)); + staffUrls = Object.values(data.staff).map((url) => (url !== "" ? url.split("?")[0] : null)); performanceUrls = Object.values(data.performance).map((url) => url.split("?")[0]); - return [...posterUrls, ...castUrls, ...staffUrls, ...performanceUrls]; + return [ + ...posterUrls, + ...castUrls.filter((url) => url !== null), + ...staffUrls.filter((url) => url !== null), + ...performanceUrls, + ]; }; //배열 형태로 추출된 모든 presignedUrls @@ -346,8 +351,12 @@ const ModifyManage = () => { //기존에 갖고 있던 이미지들의 주소들 -> files const files = [ dataState.posterImage, - ...dataState.castModifyRequests.map((cast) => cast.castPhoto), - ...dataState.staffModifyRequests.map((staff) => staff.staffPhoto), + ...dataState.castModifyRequests + .map((cast) => cast.castPhoto) + .filter((photo) => photo !== ""), + ...dataState.staffModifyRequests + .map((staff) => staff.staffPhoto) + .filter((photo) => photo !== ""), ...dataState.performanceImageModifyRequests.map((obj) => obj.performanceImage), ]; @@ -400,7 +409,7 @@ const ModifyManage = () => { castModifyRequests: dataState.castModifyRequests.map((cast, index) => { const modifiedCast = { ...cast, - castPhoto: castUrls[index] || cast.castPhoto, + castPhoto: cast.castPhoto === "" ? "" : castUrls[index] || cast.castPhoto, }; if (modifiedCast.castId === -1) { delete modifiedCast.castId; // castId가 -1인 경우 castId를 삭제(새롭게 추가된 경우에는 id 안보내야 함) @@ -410,7 +419,7 @@ const ModifyManage = () => { staffModifyRequests: dataState.staffModifyRequests.map((staff, index) => { const modifiedStaff = { ...staff, - staffPhoto: staffUrls[index] || staff.staffPhoto, + staffPhoto: staff.staffPhoto === "" ? "" : staffUrls[index] || staff.staffPhoto, }; if (modifiedStaff.staffId === -1) { delete modifiedStaff.staffId; // staffId가 -1인 경우 staffId를 삭제(새롭게 추가된 경우에는 id 안보내야 함) @@ -661,7 +670,7 @@ const ModifyManage = () => { value={dataState.performanceDescription} onChange={(e) => handleInputChange("performanceDescription", e.target.value)} placeholder="공연을 예매할 예매자들에게 공연을 소개해주세요." - maxLength={500} + maxLength={1500} /> @@ -747,7 +756,7 @@ const ModifyManage = () => { value={dataState.performanceAttentionNote} onChange={(e) => handleInputChange("performanceAttentionNote", e.target.value)} placeholder="입장 안내, 공연 중 인터미션, 공연장 반입금지 물품, 촬영 가능 여부, 주차 안내 등 예매자들이 꼭 알고 있어야할 유의사항을 입력해주세요." - maxLength={250} + maxLength={1500} /> diff --git a/src/pages/nonMbLookup/NonMbLookup.tsx b/src/pages/nonMbLookup/NonMbLookup.tsx index 56e94281..c30138bf 100644 --- a/src/pages/nonMbLookup/NonMbLookup.tsx +++ b/src/pages/nonMbLookup/NonMbLookup.tsx @@ -17,9 +17,9 @@ const NonMbLookup = () => { const { fetchBookingList } = useLazyPostGuestBookingList(); const [formData, setFormData] = useState({ - bookerName: "", + name: "", birth: "", - number: "", + phone: "", password: "", }); const [btnActive, setBtnActive] = useState(false); @@ -32,8 +32,8 @@ const NonMbLookup = () => { // 버튼 상태 활성화/비활성화 useEffect(() => { - const { bookerName, birth, number, password } = formData; - if (bookerName && birth.length === 6 && number.length === 13 && password.length === 4) { + const { name, birth, phone, password } = formData; + if (name && birth.length === 6 && phone.length === 13 && password.length === 4) { setBtnActive(true); } else { setBtnActive(false); @@ -42,26 +42,14 @@ const NonMbLookup = () => { // 버튼 클릭 시 요청 실행 const handleBtnClick = async () => { - const { bookerName, birth, number, password } = formData; - try { setIsFetching(true); - const bookingData = await fetchBookingList({ - name: bookerName, - phone: number, - password, - birth, - }); + const bookingData = await fetchBookingList(formData); if (bookingData) { navigate("/lookup", { - state: { - bookerName, - number, - password, - birth, - }, + state: formData, }); } else { openAlert({ diff --git a/src/pages/nonMbLookup/components/InputWrapper.tsx b/src/pages/nonMbLookup/components/InputWrapper.tsx index 6b89b246..d2ee12c0 100644 --- a/src/pages/nonMbLookup/components/InputWrapper.tsx +++ b/src/pages/nonMbLookup/components/InputWrapper.tsx @@ -6,16 +6,16 @@ import { nameFilter, numericFilter, phoneNumberFilter } from "@utils/useInputFil interface InputWrapperProps { formData: { - bookerName: string; + name: string; birth: string; - number: string; + phone: string; password: string; }; onInputChange: (field: string, value: string) => void; } const InputWrapper = ({ formData, onInputChange }: InputWrapperProps) => { - const { bookerName, birth, number, password } = formData; + const { name, birth, phone, password } = formData; const handleChange = (e: React.ChangeEvent) => { const { name: fieldName, value } = e.target; @@ -25,9 +25,9 @@ const InputWrapper = ({ formData, onInputChange }: InputWrapperProps) => { return ( { /> { if (isSuccess) { const extractUrls = (data: PresignedResponse) => { posterUrls = Object.values(data.poster).map((url) => url.split("?")[0]); - castUrls = Object.values(data.cast).map((url) => url.split("?")[0]); - staffUrls = Object.values(data.staff).map((url) => url.split("?")[0]); + castUrls = Object.values(data.cast).map((url) => (url !== "" ? url.split("?")[0] : null)); + staffUrls = Object.values(data.staff).map((url) => (url !== "" ? url.split("?")[0] : null)); performanceUrls = Object.values(data.performance).map((url) => url.split("?")[0]); - return [...posterUrls, ...castUrls, ...staffUrls, ...performanceUrls]; + return [ + ...posterUrls, + ...castUrls.filter((url) => url !== null), + ...staffUrls.filter((url) => url !== null), + ...performanceUrls, + ]; }; const S3Urls = extractUrls(data); const files = [ gigInfo.posterImage, - ...gigInfo.castList.map((cast) => cast.castPhoto), - ...gigInfo.staffList.map((staff) => staff.staffPhoto), + ...gigInfo.castList.map((cast) => cast.castPhoto).filter((photo) => photo !== ""), + ...gigInfo.staffList.map((staff) => staff.staffPhoto).filter((photo) => photo !== ""), ...gigInfo.performanceImageList.map((image) => image.performanceImage), ]; @@ -228,11 +233,11 @@ const Register = () => { posterImage: posterUrls[0], castList: gigInfo.castList.map((cast, index) => ({ ...cast, - castPhoto: castUrls[index] || cast.castPhoto, + castPhoto: cast.castPhoto === "" ? "" : castUrls[index] || cast.castPhoto, })), staffList: gigInfo.staffList.map((staff, index) => ({ ...staff, - staffPhoto: staffUrls[index] || staff.staffPhoto, + staffPhoto: staff.staffPhoto === "" ? "" : staffUrls[index] || staff.staffPhoto, })), scheduleList: gigInfo.scheduleList.map((schedule) => { const date = dayjs(schedule.performanceDate).toDate(); @@ -448,7 +453,7 @@ const Register = () => { value={performanceDescription} onChange={(e) => handleChange(e, setGigInfo)} placeholder="공연을 예매할 예매자들에게 공연을 소개해주세요." - maxLength={500} + maxLength={1500} /> @@ -512,7 +517,7 @@ const Register = () => { value={performanceAttentionNote} onChange={(e) => handleChange(e, setGigInfo)} placeholder="입장 안내, 공연 중 인터미션, 공연장 반입금지 물품, 촬영 가능 여부, 주차 안내 등 예매자들이 꼭 알고 있어야할 유의사항을 입력해주세요." - maxLength={500} + maxLength={1500} /> diff --git a/src/pages/register/RegisterMaker.tsx b/src/pages/register/RegisterMaker.tsx index c76a7055..3a73c0ba 100644 --- a/src/pages/register/RegisterMaker.tsx +++ b/src/pages/register/RegisterMaker.tsx @@ -24,13 +24,9 @@ const RegisterMaker = ({ const [isButtonDisabled, setIsButtonDisabled] = useState(true); useEffect(() => { - const allCastFieldsFilled = castList.every( - (cast) => cast.castName && cast.castRole && cast.castPhoto - ); + const allCastFieldsFilled = castList.every((cast) => cast.castName && cast.castRole); - const allStaffFieldsFilled = staffList.every( - (staff) => staff.staffName && staff.staffRole && staff.staffPhoto - ); + const allStaffFieldsFilled = staffList.every((staff) => staff.staffName && staff.staffRole); setIsButtonDisabled( !( diff --git a/src/pages/test/TestPage.tsx b/src/pages/test/TestPage.tsx index 5a8485c2..0d962e63 100644 --- a/src/pages/test/TestPage.tsx +++ b/src/pages/test/TestPage.tsx @@ -1,4 +1,4 @@ -import { useGuestBook } from "@apis/domains/bookings/queries"; +// import { useGuestBook } from "@apis/domains/bookings/queries"; import { IconCheck, IconTextfiedlDelete } from "@assets/svgs"; import Button from "@components/commons/button/Button"; import Chip from "@components/commons/chip/Chip"; @@ -38,7 +38,7 @@ const TestPage = () => { }; // 3. 훅 불러와서 사용 - const { mutate, mutateAsync } = useGuestBook(); + // const { mutate, mutateAsync } = useGuestBook(); const handleClick = () => { const formData = { diff --git a/src/pages/ticketholderlist/TicketHolderList.styled.ts b/src/pages/ticketholderlist/TicketHolderList.styled.ts index d5004c78..2b594afc 100644 --- a/src/pages/ticketholderlist/TicketHolderList.styled.ts +++ b/src/pages/ticketholderlist/TicketHolderList.styled.ts @@ -1,121 +1,38 @@ -import { IconToggleOff, IconToggleOn } from "@assets/svgs"; import styled from "styled-components"; -export const BodyWrapper = styled.div` +export const TicketHolderListWrpper = styled.section` display: flex; - align-items: flex-start; - justify-content: center; - width: 37.4rem; - height: auto; - min-height: 60.8rem; /* 60.8rem(body의 높이) + 5.6rem(버튼의 높이) */ - margin-bottom: 10.4rem; - padding: 2.4rem; -`; - -export const BodyLayout = styled.section` - display: flex; - flex: 1 0 0; flex-direction: column; - gap: 1.2rem; - align-items: flex-start; + align-items: center; + padding: 0 2.4rem; `; -export const LayoutHeaderBox = styled.div` - display: flex; - gap: 3.2rem; - align-items: flex-end; - align-self: stretch; -`; +export const TitleSticky = styled.section` + position: sticky; + top: 5.6rem; -export const LayoutFilterBox = styled.div` - display: flex; - gap: 0.6rem; - align-items: center; + background-color: ${({ theme }) => theme.colors.gray_900}; `; -export const ToggleWrapper = styled.div` +export const ManageCardList = styled.section` display: flex; - gap: 0.4rem; - align-items: center; -`; - -export const ToggleText = styled.span` - color: ${({ theme }) => theme.colors.gray_400}; - ${({ theme }) => theme.fonts["body2-normal-medi"]}; + flex-direction: column; + gap: 1.2rem; `; -export const ToggleButton = styled.button<{ $detail: boolean }>` - position: relative; +export const ManageCardContainer = styled.section` display: flex; align-items: center; - justify-content: space-between; - width: 5.6rem; - height: 3.3rem; - - background-color: ${({ theme, $detail }) => - $detail ? theme.colors.pink_500 : theme.colors.gray_700}; - cursor: pointer; - border: none; - border-radius: 36px; -`; - -export const Circle = styled.span<{ $detail: boolean }>` - position: absolute; - top: 50%; - left: ${({ $detail }) => ($detail ? "4rem" : "1.6rem")}; - width: 2.7rem; - height: 2.7rem; - - background-color: ${({ theme }) => theme.colors.white}; - transform: translate(-50%, -50%); - border-radius: 50%; - - transition: left 0.25s ease-in-out; /* left 속성은 변하게 된다면 이 애니메이션 적용 */ -`; - -export const ToggleOnIcon = styled(IconToggleOn)<{ $width: string; $height: string }>` - width: ${({ $width }) => $width}; - height: ${({ $height }) => $height}; -`; - -export const ToggleOffIcon = styled(IconToggleOff)<{ $width: string; $height: string }>` - width: ${({ $width }) => $width}; - height: ${({ $height }) => $height}; -`; - -export const FooterButtonText = styled.span` - margin-top: 2.4rem; - - color: ${({ theme }) => theme.colors.gray_200}; - text-align: center; - ${({ theme }) => theme.fonts["body2-normal-semi"]}; + width: 100%; `; export const FooterButtonWrapper = styled.div` position: fixed; bottom: 0; - left: 50%; z-index: 1; display: flex; - flex-direction: column; - gap: 2.4rem; - align-items: center; - width: 37.4rem; - min-height: 13.8rem; + min-height: 10.4rem; + padding-top: 2.4rem; background-color: ${({ theme }) => theme.colors.gray_900}; - transform: translate(-50%, 0); - border-top: 1px solid #3e3e3e; -`; - -export const MarginBottom = styled.div<{ $value: string }>` - margin-bottom: ${({ $value }) => $value}; -`; - -export const TwoButtonWrapper = styled.div` - display: flex; - flex-direction: row; - gap: 1.1rem; - align-items: flex-start; - justify-content: center; `; diff --git a/src/pages/ticketholderlist/TicketHolderList.tsx b/src/pages/ticketholderlist/TicketHolderList.tsx index 23e9f394..1ae7b189 100644 --- a/src/pages/ticketholderlist/TicketHolderList.tsx +++ b/src/pages/ticketholderlist/TicketHolderList.tsx @@ -1,29 +1,41 @@ -import { useTicketPatch, useTicketRetrive, useTicketUpdate } from "@apis/domains/tickets/queries"; -import { IconCheck } from "@assets/svgs"; -import Button from "@components/commons/button/Button"; +import { + useTicketDelete, + useTicketRefund, + useTicketRetrive, + useTicketRetriveSearch, + useTicketUpdate, +} from "@apis/domains/tickets/queries"; import Loading from "@components/commons/loading/Loading"; import MetaTag from "@components/commons/meta/MetaTag"; -import Toast from "@components/commons/toast/Toast"; -import { convertingNumber } from "@constants/convertingNumber"; import { NAVIGATION_STATE } from "@constants/navigationState"; -import { useHeader, useModal, useToast } from "@hooks"; -import { PatchFormDataProps } from "@typings/deleteBookerFormatProps"; -import { useEffect, useState } from "react"; +import { useHeader, useModal } from "@hooks"; +import useDebounce from "src/hooks/useDebounce"; +import { useEffect, useState, ChangeEvent, useRef } from "react"; import { CSVLink } from "react-csv"; import { useNavigate, useParams } from "react-router-dom"; -import Banner from "./components/banner/Banner"; -import ManagerCard from "./components/managercard/ManagerCard"; -import NarrowDropDown from "./components/narrowDropDown/NarrowDropDown"; -import eximg from "./constants/silkagel.png"; -import { BookingListProps } from "./constants/ticketholderlist"; +import { convertingNumber } from "@constants/convertingNumber"; import * as S from "./TicketHolderList.styled"; +import { BottomSheet, Button, Spacing } from "@components/commons"; +import Title from "@pages/ticketholderlist/components/title/Title"; +import SearchBar from "./components/searchBar/SearchBar"; +import MenuBottomsheet from "./components/MenuBottomSheet/MenuBottomsheet"; +import FilterBottomSheet from "./components/FilterBottomSheet/FilterBottomSheet"; +import { BookingListProps } from "@pages/ticketholderlist/types/bookingListType"; +import { ManageCard } from "./components/manageCard"; +import { getBankNameKr } from "@utils/getBankName"; +import SelectedChips from "./components/selectedChips/SelectedChips"; +import { convertingBookingStatus } from "@constants/convertingBookingStatus"; +import { IconCheck } from "@assets/svgs"; +import Toast from "@components/commons/toast/Toast"; +import { useToast } from "@hooks"; +import NonExistent from "./components/nonExistent/NonExistent."; -type PaymentType = +export type PaymentType = | "CHECKING_PAYMENT" | "BOOKING_CONFIRMED" | "BOOKING_CANCELLED" - | "REFUND_REQUESTED" - | "BOOKING_DELETED"; + | "REFUND_REQUESTED"; + interface CSVDataType { createdAt: string; scheduleNumber: string; @@ -33,18 +45,11 @@ interface CSVDataType { bookingStatus: string; } -const convertingBookingStatus = (_bookingStatus: PaymentType): string => { - switch (_bookingStatus) { - case "CHECKING_PAYMENT": - return "미입금"; - case "BOOKING_CONFIRMED": - return "입금 완료"; - case "BOOKING_CANCELLED": - return "취소된 예매자"; - default: - throw new Error("알 수 없는 타입입니다."); - } -}; +export interface FilterListType { + scheduleNumber: number[]; + bookingStatus: string[]; +} + const headers = [ { label: "예매일시", key: "createdAt" }, { label: "회차", key: "scheduleNumber" }, @@ -55,254 +60,333 @@ const headers = [ ]; const TicketHolderList = () => { - const [CSVDataArr, setCSVDataArr] = useState([]); - - const { performanceId } = useParams(); - const [reservedCount, setReservedCount] = useState(0); - - //판매 완료 여부에 따라 배너 렌더링 달라질 지 고민 - const [isOutdated, setIsOutdated] = useState(false); - - // 0, undefined 일 때는 전체 렌더링 (필터링을 위한 state들) - const [schedule, setSchedule] = useState(0); //1,2,3 에 따라 필터링 - const [payment, setPayment] = useState(undefined); - - const [isEditMode, setIsEditMode] = useState(false); - - const { data, isLoading, refetch } = useTicketRetrive({ performanceId: Number(performanceId) }); const [paymentData, setPaymentData] = useState(); - const [alreadyPayments, setAlreadyPayments] = useState>({}); - const [initBookingStatuses, setInitBookingStatuses] = useState>({}); - const { showToast, isToastVisible } = useToast(); - useEffect(() => { - setPaymentData(data?.bookingList ?? []); + // DEFAULT, PAYMENT, REFUND, DELETE + const [status, setStatus] = useState("DEFAULT"); + const [buttonText, setButtonText] = useState("예매자 관리하기"); - if (data?.bookingList) { - const immutableAlreadyPayments = data.bookingList.reduce( - (acc, item) => { - acc[item.bookingId] = item.bookingStatus === "BOOKING_CONFIRMED"; - return acc; - }, - {} as Record - ); + const [filterList, setFilterList] = useState({ + scheduleNumber: [], + bookingStatus: [], + }); + const [searchWord, setSearchWord] = useState(""); - const immutableBookingStatuses = data.bookingList.reduce( - (acc, item) => { - acc[item.bookingId] = item.bookingStatus; - return acc; - }, - {} as Record - ); + const [openFilter, setOpenFilter] = useState(false); + const [openMenu, setOpenMenu] = useState(false); - setAlreadyPayments(immutableAlreadyPayments); - setInitBookingStatuses(immutableBookingStatuses); + const [CSVDataArr, setCSVDataArr] = useState([]); - //전체 데이터를 기반으로 csv 추출 데이터 구축 - const tempCSVDataArr: CSVDataType[] = []; + const csvLinkRef = useRef(null); - data.bookingList.map((item) => { - const date = item.createdAt.split("T")[0]; - const time = item.createdAt.split("T")[1].slice(0, 5); - const formattedDate = date?.replace(/-/g, "."); - const formattedCreateTime = `${formattedDate} ${time}`; + const { performanceId } = useParams(); - tempCSVDataArr.push({ - createdAt: formattedCreateTime, - scheduleNumber: `${convertingNumber(item.scheduleNumber)}회차`, - bookerName: item.bookerName, - purchaseTicketCount: `${item.purchaseTicketCount}매`, - bookerPhoneNumber: item.bookerPhoneNumber, - bookingStatus: convertingBookingStatus(item.bookingStatus), - }); - }); + const { data, isLoading, refetch } = useTicketRetrive( + { performanceId: Number(performanceId) }, + filterList + ); + const { data: searchData, refetch: searchRefetch } = useTicketRetriveSearch( + { performanceId: Number(performanceId) }, + searchWord, + filterList + ); + const { openConfirm, closeConfirm } = useModal(); - tempCSVDataArr.sort( - (obj1, obj2) => new Date(obj1.createdAt).getTime() - new Date(obj2.createdAt).getTime() - ); - setCSVDataArr(tempCSVDataArr); - } - }, [data]); + const [checkedBookingId, setCheckedBookingId] = useState([]); + const { showToast, isToastVisible } = useToast(); + // 체크된 리스트 확인 + const handleBookingIdCheck = (bookingId: number) => { + setCheckedBookingId((prev) => + prev.includes(bookingId) ? prev.filter((id) => id !== bookingId) : [...prev, bookingId] + ); + }; + + const { mutate: updateMutate, isPending: updateIsPending } = useTicketUpdate(); - const { openConfirm, closeConfirm } = useModal(); - const { mutate, mutateAsync } = useTicketUpdate(); - const { mutate: patchMutate, mutateAsync: patchMutateAsync, isPending } = useTicketPatch(); const handlePaymentFixAxiosFunc = () => { - if (isPending) { + if (updateIsPending) { return; } - //PUT API 요청 - mutate({ + // 예매 완료 PUT API 요청 + // paymentData에 accountHolder, accountNumber, bankName 제거 + const filteredPaymentData = paymentData.map( + ({ bankName, accountNumber, accountHolder, ...rest }) => ({ + ...rest, + bookingStatus: checkedBookingId.includes(rest.bookingId) + ? "BOOKING_CONFIRMED" + : rest.bookingStatus, + }) + ); + + updateMutate({ performanceId: Number(performanceId), performanceTitle: data?.performanceTitle, totalScheduleCount: data?.totalScheduleCount, - bookingList: paymentData, + bookingList: filteredPaymentData, }); closeConfirm(); - showToast(); setTimeout(() => { window.location.reload(); }, 1000); }; + const handlePaymentFixBtn = () => { openConfirm({ - title: "선택한 게스트를 입금 처리하겠습니까?", - subTitle: "입금 완료로 변경된 예매자에게\n 입금 확인 완료 웹발신이 발송돼요.", - okText: "저장할게요", + title: "입금 처리시 예매확정 문자가 발송돼요.", + subTitle: "예매자에게 입금이 확인되었음을 알려드릴게요!", + okText: "입금 처리하기", noText: "아니요", - okCallback: handlePaymentFixAxiosFunc, + okCallback: () => { + handlePaymentFixAxiosFunc(); + }, noCallback: closeConfirm, }); }; - const [patchFormData, setPatchFormData] = useState({ - performanceId: Number(performanceId), - bookingList: [], - }); - - const handleBookerPatchAxiosFunc = async () => { - await patchMutateAsync(patchFormData); - window.location.reload(); - - closeConfirm(); - - //window.location.reload(); - }; + // 환불 요청 + const { mutate: refundMutate, isPending: refundIsPending } = useTicketRefund(); - const handleDeleteBtn = () => { + const handlePaymentRefundBtn = () => { openConfirm({ - title: "선택한 게스트를 삭제하시겠어요?", - subTitle: "삭제된 게스트는 복구되지 않아요.", - okText: "삭제할게요", + title: "환불 처리 하시겠어요?", + subTitle: "예매자에게 환불 금액을 보낸 뒤 처리해 주세요.", + okText: "환불 처리하기", noText: "아니요", - okCallback: handleBookerPatchAxiosFunc, + okCallback: () => { + handlePaymentRefundAxiosFunc(); + }, noCallback: closeConfirm, }); }; - const navigate = useNavigate(); + const handlePaymentRefundAxiosFunc = () => { + if (refundIsPending) { + return; + } + // 환불 요청 PUT API 요청 + // bookingId만 전달 - const handleNavigateBack = () => { - navigate("/gig-manage"); - }; + const filteredPaymentData = paymentData + .filter(({ bookingId }) => checkedBookingId.includes(bookingId)) + .map(({ bookingId }) => ({ bookingId })); - const handleResetEditMode = () => { - setHeader({ - headerStyle: NAVIGATION_STATE.ICON_TITLE_SUB_TEXT, - title: "예매자 관리", - subText: "편집", - leftOnClick: handleNavigateBack, - rightOnClick: handleEditButton, + refundMutate({ + performanceId: Number(performanceId), + bookingList: filteredPaymentData, }); + + closeConfirm(); + setTimeout(() => { + window.location.reload(); + }, 1000); }; - const handleLeftButton = () => { + // 취소 요청 + const { mutate: deleteMutate, isPending: deleteIsPending } = useTicketDelete(); + + const handlePaymentDeleteBtn = () => { openConfirm({ - title: "화면을 나갈까요?", - subTitle: "'상태 저장' 없이 화면을 나갈 경우,\n 수정 내용이 저장되지 않아요.", - okText: "계속할게요", - noText: "나갈게요", - okCallback: closeConfirm, - noCallback: handleResetEditMode, + title: "예매자를 삭제하시겠어요?", + subTitle: "한 번 삭제한 예매자 정보는 다시 복구할 수 없어요.", + okText: "삭제하기", + noText: "아니요", + okCallback: () => { + handlePaymentDeleteAxiosFunc(); + }, + noCallback: closeConfirm, }); }; - const { setHeader } = useHeader(); + const handlePaymentDeleteAxiosFunc = () => { + if (deleteIsPending) { + return; + } + // 취소 요청 PUT API 요청 + // bookingId만 전달 - const handleCloseButton = async () => { - setIsEditMode(false); + const filteredPaymentData = paymentData + .filter(({ bookingId }) => checkedBookingId.includes(bookingId)) + .map(({ bookingId }) => ({ bookingId })); - //원 상태도 되돌림 (입금 여부 수정, 삭제용 체크) - //Todo : 새로고침 후 편집 -> 닫기 반복 클릭하면 에러 발생(빈 배열로 설정되던 에러 + 이상한 렌더링) -> 해결 - const refetchData = await refetch(); - setPaymentData(refetchData?.data?.bookingList ?? []); - setPatchFormData({ + deleteMutate({ performanceId: Number(performanceId), - bookingList: [], + bookingList: filteredPaymentData, }); + closeConfirm(); + setTimeout(() => { + window.location.reload(); + }, 1000); + }; - setHeader({ - headerStyle: NAVIGATION_STATE.ICON_TITLE_SUB_TEXT, - title: "예매자 관리", - subText: "편집", - leftOnClick: handleLeftButton, - rightOnClick: handleEditButton, - }); + const actions = { + PAYMENT: { + text: "입금 처리하기", + action: () => { + handlePaymentFixBtn(); + }, + }, + REFUND: { + text: "환불 처리하기", + action: () => { + handlePaymentRefundBtn(); + }, + }, + DELETE: { + text: "예매자 삭제하기", + action: () => { + handlePaymentDeleteBtn(); + }, + }, + DEFAULT: { + text: "예매자 관리하기", + action: () => setOpenMenu(true), + }, }; - const handleEditButton = () => { - setIsEditMode(true); - setHeader({ - headerStyle: NAVIGATION_STATE.TITLE_SUB_TEXT, - title: "예매자 편집", - subText: "닫기", - rightOnClick: handleLeftButton, + // 상태 변경 시 버튼 텍스트 설정 + useEffect(() => { + setButtonText(actions[status]?.text || "예매자 관리하기"); + }, [status]); + + const handleButtonClick = () => { + actions[status]?.action?.(); + }; + + const handleStatus = (status: string) => { + setStatus(status); + setOpenMenu(false); + switch (status) { + case "PAYMENT": + setFilterList({ + scheduleNumber: [], + bookingStatus: ["CHECKING_PAYMENT"], + }); + break; + case "REFUND": + setFilterList({ + scheduleNumber: [], + bookingStatus: ["REFUND_REQUESTED"], + }); + break; + case "DELETE": + setFilterList({ + scheduleNumber: [], + bookingStatus: ["CHECKING_PAYMENT", "BOOKING_CONFIRMED", "REFUND_REQUESTED"], + }); + break; + default: + setFilterList({ + scheduleNumber: [], + bookingStatus: [], + }); + break; + } + }; + + // 바텀시트 닫기 + const closeBottomSheet = () => { + setOpenMenu(false); + setOpenFilter(false); + }; + + // 필터 바텀시트 + const handleFilterSheet = () => { + setOpenFilter((prev) => !prev); + }; + + const handleFilter = async (scheduleNumber: number[], bookingStatus: string[]) => { + setFilterList({ + scheduleNumber, + bookingStatus, }); }; + const debouncedQuery = useDebounce(searchWord, 500); + + const handleInputChange = (event: ChangeEvent) => { + setSearchWord(event.target.value); + }; + + // 필터 변경될 때마다 GET API 요청 + // 검색될 때마다 GET API 요청 + useEffect(() => { + const fetchData = async () => { + const refetchData = await refetch(); + setPaymentData(refetchData?.data?.bookingList ?? []); + }; + + const fetchSearchData = async () => { + const refetchSearchData = await searchRefetch(); + setPaymentData(refetchSearchData?.data?.bookingList ?? []); + }; + + // TODO : 서버에서 검색어 2글자 이상으로 넘겨줬는데, 기-디에 화면에 어떻게 표현할지 물어보기 + searchWord.length >= 2 ? fetchSearchData() : fetchData(); + }, [filterList, status, debouncedQuery]); + + useEffect(() => { + setPaymentData(data?.bookingList ?? []); + + if (data?.bookingList) { + //전체 데이터를 기반으로 csv 추출 데이터 구축 + const tempCSVDataArr: CSVDataType[] = []; + + data.bookingList.map((item) => { + const date = item.createdAt.split("T")[0]; + const time = item.createdAt.split("T")[1].slice(0, 5); + const formattedDate = date?.replace(/-/g, "."); + const formattedCreateTime = `${formattedDate} ${time}`; + + tempCSVDataArr.push({ + createdAt: formattedCreateTime, + scheduleNumber: `${convertingNumber(item.scheduleNumber)}회차`, + bookerName: item.bookerName, + purchaseTicketCount: `${item.purchaseTicketCount}매`, + bookerPhoneNumber: item.bookerPhoneNumber, + bookingStatus: convertingBookingStatus(item.bookingStatus as PaymentType), + }); + }); + + tempCSVDataArr.sort( + (obj1, obj2) => new Date(obj1.createdAt).getTime() - new Date(obj2.createdAt).getTime() + ); + setCSVDataArr(tempCSVDataArr); + } + }, [data, paymentData]); + + const navigate = useNavigate(); + + const handleNavigateBack = () => { + if (status !== "DEFAULT") { + setStatus("DEFAULT"); + } else { + navigate("/gig-manage"); + } + }; + + const handleCSVDownload = () => { + if (csvLinkRef.current) { + csvLinkRef.current.link.click(); + } + }; + + const { setHeader } = useHeader(); useEffect(() => { setHeader({ - headerStyle: NAVIGATION_STATE.ICON_TITLE_SUB_TEXT, + headerStyle: NAVIGATION_STATE.ICON_TITLE_DOWNLOAD, title: "예매자 관리", - subText: "편집", + subText: "리스트", leftOnClick: handleNavigateBack, - rightOnClick: handleEditButton, + rightOnClick: handleCSVDownload, }); }, [setHeader]); - const count = data?.totalScheduleCount; //api로 받아온 값 (동적 회차 수) - - //최대 10회차로 렌더링 될 수 있도록 변경 필요 - //schedule ===0 -> 전체 회차, payment === undefined -> 전체 입금 여부 - const filteredData = paymentData?.filter((obj) => { - const isScheduleMatched = - schedule === 0 || - (obj.scheduleNumber === "FIRST" && schedule === 1) || - (obj.scheduleNumber === "SECOND" && schedule === 2) || - (obj.scheduleNumber === "THIRD" && schedule === 3) || - (obj.scheduleNumber === "FOURTH" && schedule === 4) || - (obj.scheduleNumber === "FIFTH" && schedule === 5) || - (obj.scheduleNumber === "SIXTH" && schedule === 6) || - (obj.scheduleNumber === "SEVENTH" && schedule === 7) || - (obj.scheduleNumber === "EIGHTH" && schedule === 8) || - (obj.scheduleNumber === "NINTH" && schedule === 9) || - (obj.scheduleNumber === "TENTH" && schedule === 10); - - const isPaymentMatched = - obj.bookingStatus !== "BOOKING_CANCELLED" && - (payment === undefined || payment === initBookingStatuses[obj.bookingId]); - - return isScheduleMatched && isPaymentMatched; - }); + const handleCopyClipBoard = (text: string) => { + navigator.clipboard.writeText(text); - useEffect(() => { - //총 매수 계산 - const totalCount = filteredData?.reduce( - (totalSum, obj) => (obj.purchaseTicketCount as number) + totalSum, - 0 - ) as number; - setReservedCount(totalCount); - }, [filteredData]); - - const handlePaymentToggle = (_isEditMode: boolean, bookingId?: number) => { - //Edit(편집) 모드 일때만 바뀌도록 - if (_isEditMode) { - setPaymentData((arr) => - arr?.map((item) => - item.bookingId === bookingId - ? { - ...item, - //예매 확정(입금 완료) <-> 입금 확인 중(미입금) 변경되도록 - bookingStatus: - item.bookingStatus === "BOOKING_CONFIRMED" - ? "CHECKING_PAYMENT" - : "BOOKING_CONFIRMED", - } - : item - ) - ); - } + showToast(); }; + return ( <> @@ -310,91 +394,104 @@ const TicketHolderList = () => { ) : ( <> - {isPending && } - - - - - - {/*set 함수 직접 넘기는 거 안좋다고 했지만, 내부에서 감싸야 하므로 넘김 */} - - 모든 회차 - - - 입금 상태 - - - - {filteredData?.map((obj, index) => ( - handlePaymentToggle(isEditMode, obj.bookingId)} - bookername={obj.bookerName} - purchaseTicketeCount={obj.purchaseTicketCount} - scheduleNumber={obj.scheduleNumber} - bookerPhoneNumber={obj.bookerPhoneNumber} - createAt={obj.createdAt} - alreadyBookingConfirmed={alreadyPayments[obj.bookingId]} + + + + + 0 || filterList.bookingStatus.length > 0 + } + /> + {status === "DEFAULT" && ( + + handleFilter(scheduleNumber, bookingStatus) + } /> - ))} - - {isEditMode ? ( - - 저장 후, 입금 상태 재변경은 불가능합니다. - - - - - - ) : ( - <> - - - 예매자 정보를 CSV 파일로 저장할 수 있어요. - - - - - - } isVisible={isToastVisible} toastBottom={37}> - 예매 확정 WEB 발신 문자가 전송되었습니다. - - )} - - + + + + {paymentData?.length ? ( + + {paymentData?.map((item) => { + const date = item.createdAt.split("T")[0]; + const formattedDate = `${date.replace(/-/g, ". ")}`; + const bookingStatus = convertingBookingStatus(item.bookingStatus as PaymentType); + + return ( + + + {status !== "DEFAULT" && ( + + )} + + + {status === "REFUND" && ( + + )} + + ); + })} + + ) : ( + + )} + + + + + + + handleFilter(scheduleNumber, bookingStatus) + } + /> + + } isVisible={isToastVisible} toastBottom={30}> + 클립보드에 복사되었습니다! + + )} diff --git a/src/pages/ticketholderlist/components/FilterBottomSheet/FilterBottomSheet.styled.ts b/src/pages/ticketholderlist/components/FilterBottomSheet/FilterBottomSheet.styled.ts new file mode 100644 index 00000000..0c94eca0 --- /dev/null +++ b/src/pages/ticketholderlist/components/FilterBottomSheet/FilterBottomSheet.styled.ts @@ -0,0 +1,91 @@ +import styled from "styled-components"; +import { IcRefresh, IconCheckboxSelectedOn, IconCheckboxUnselectedOn } from "@assets/svgs"; + +export const FilterBottomSheetWrapper = styled.section<{ $isOpen: boolean }>` + position: fixed; + bottom: 0; + z-index: 30; + display: flex; + justify-content: center; + width: 100%; + height: 100%; + + background-color: rgb(0 0 0 / 50%); + visibility: ${({ $isOpen }) => ($isOpen ? "visible" : "hidden")}; + opacity: ${({ $isOpen }) => ($isOpen ? 1 : 0)}; + + transition: + opacity 250ms ease-in-out, + visibility 250ms ease-in-out; +`; + +export const TitleWrapper = styled.section` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + margin-bottom: 1.6rem; + + ${({ theme }) => theme.fonts.heading4}; + color: ${({ theme }) => theme.colors.white}; +`; + +export const RefreshBtn = styled.button` + display: flex; + align-items: center; + justify-content: center; + width: 3.2rem; + height: 3.2rem; + + background-color: ${({ theme }) => theme.colors.gray_700}; + border-radius: 50%; +`; + +export const RefreshIcon = styled(IcRefresh)` + display: flex; + width: 2.1rem; + height: 2.1rem; +`; + +export const BoxDivider = styled.div` + display: flex; + width: 32.7rem; + margin: 2rem 0; + + color: ${({ theme }) => theme.colors.gray_700}; + + border-top: 0.1rem solid; +`; + +export const CheckBoxContainer = styled.section` + display: grid; + grid-gap: 1.4rem 8.1rem; + grid-template-columns: repeat(2, 9rem); +`; + +export const CheckBoxLabel = styled.label` + display: flex; + align-items: center; + width: 9rem; +`; + +export const CheckBox = styled.input` + display: none; +`; + +export const SelectIcon = styled(IconCheckboxSelectedOn)` + width: 2rem; + height: 2rem; + margin-right: 0.8rem; +`; + +export const UnSelectIcon = styled(IconCheckboxUnselectedOn)` + width: 2rem; + height: 2rem; + margin-right: 0.8rem; +`; + +export const CheckBoxText = styled.div` + color: ${({ theme }) => theme.colors.gray_200}; + ${({ theme }) => theme.fonts["body1-normal-medi"]}; +`; diff --git a/src/pages/ticketholderlist/components/FilterBottomSheet/FilterBottomSheet.tsx b/src/pages/ticketholderlist/components/FilterBottomSheet/FilterBottomSheet.tsx new file mode 100644 index 00000000..05776658 --- /dev/null +++ b/src/pages/ticketholderlist/components/FilterBottomSheet/FilterBottomSheet.tsx @@ -0,0 +1,139 @@ +import { useState, ReactNode, useEffect } from "react"; +import * as S from "./FilterBottomSheet.styled"; +import { BottomSheet, Button, Spacing } from "@components/commons"; +import { FilterListType, PaymentType } from "@pages/ticketholderlist/TicketHolderList"; +import { convertingBookingStatus } from "@constants/convertingBookingStatus"; + +interface FilterBottomSheetProps { + isOpen: boolean; + totalScheduleCount: number; + children?: ReactNode; + onClickOutside?: () => void; + filterList: FilterListType; + handleFilter: (scheduleNumber: number[], bookingStatus: string[]) => void; +} + +const bookingStatusList = [ + "CHECKING_PAYMENT", + "BOOKING_CONFIRMED", + "REFUND_REQUESTED", + "BOOKING_CANCELLED", +]; + +const FilterBottomSheet = ({ + isOpen, + onClickOutside, + totalScheduleCount, + handleFilter, + filterList, +}: FilterBottomSheetProps) => { + const [checkedStatusList, setCheckedStatusList] = useState(filterList.bookingStatus); + const [checkedScheduleList, setCheckedScheduleList] = useState( + filterList.scheduleNumber + ); + + const initList = filterList; + + const handleWrapperClick = () => { + onClickOutside(); + }; + + const scheduleNumberArray = (arrayLength: number): number[] => { + const array = Array.from({ length: arrayLength }, (_, idx) => idx + 1); + + return array; + }; + + const scheduleArray = scheduleNumberArray(totalScheduleCount); + + useEffect(() => { + const newScheduleNumbers = filterList.scheduleNumber.map((item) => item); + const newBookingStatuses = filterList.bookingStatus.map((item) => item); + + setCheckedScheduleList(newScheduleNumbers); + setCheckedStatusList(newBookingStatuses); + }, [filterList]); + + // 선택된 회차 확인 + const handleScheduleCheck = (schedule: number) => { + setCheckedScheduleList((prev) => + prev.includes(schedule) ? prev.filter((item) => item !== schedule) : [...prev, schedule] + ); + }; + + // 선택된 상태 확인 + const handleStatusCheck = (status: string) => { + setCheckedStatusList((prev) => + prev.includes(status) ? prev.filter((item) => item !== status) : [...prev, status] + ); + }; + + const handleCilckBtn = () => { + onClickOutside(); + + handleFilter(checkedScheduleList, checkedStatusList); + }; + + const handleClickRefresh = () => { + setCheckedScheduleList([]); + setCheckedStatusList([]); + }; + + const isAllEmpty = + initList.scheduleNumber.length === 0 && + initList.bookingStatus.length === 0 && + checkedStatusList.length === 0 && + checkedScheduleList.length === 0; + + return ( + + + + 회차 + + + + + + + {scheduleArray.map((scheduleNumber) => ( + + handleScheduleCheck(scheduleNumber)} + /> + {checkedScheduleList.includes(scheduleNumber) ? : } + {scheduleNumber}회차 + + ))} + + + + + 입금 상태 + + {bookingStatusList.map((status) => ( + + handleStatusCheck(status)} + /> + {checkedStatusList.includes(status) ? : } + {convertingBookingStatus(status as PaymentType)} + + ))} + + + {isAllEmpty ? ( + + ) : ( + + )} + + + ); +}; + +export default FilterBottomSheet; diff --git a/src/pages/ticketholderlist/components/MenuBottomSheet/MenuBottomSheet.styled.ts b/src/pages/ticketholderlist/components/MenuBottomSheet/MenuBottomSheet.styled.ts new file mode 100644 index 00000000..30975f99 --- /dev/null +++ b/src/pages/ticketholderlist/components/MenuBottomSheet/MenuBottomSheet.styled.ts @@ -0,0 +1,19 @@ +import styled from "styled-components"; + +export const MenuBottomSheetWrapper = styled.section<{ $isOpen: boolean }>` + position: fixed; + bottom: 0; + z-index: 30; + display: flex; + justify-content: center; + width: 100%; + height: 100%; + + background-color: rgb(0 0 0 / 50%); + visibility: ${({ $isOpen }) => ($isOpen ? "visible" : "hidden")}; + opacity: ${({ $isOpen }) => ($isOpen ? 1 : 0)}; + + transition: + opacity 250ms ease-in-out, + visibility 250ms ease-in-out; +`; diff --git a/src/pages/ticketholderlist/components/MenuBottomSheet/MenuBottomsheet.tsx b/src/pages/ticketholderlist/components/MenuBottomSheet/MenuBottomsheet.tsx new file mode 100644 index 00000000..0248b61c --- /dev/null +++ b/src/pages/ticketholderlist/components/MenuBottomSheet/MenuBottomsheet.tsx @@ -0,0 +1,44 @@ +import { Children, isValidElement, ReactNode, useEffect } from "react"; +import * as S from "./MenuBottomSheet.styled"; +import BottomSheet from "@components/commons/bottomSheet/BottomSheet"; +import OuterLayout from "@components/commons/bottomSheet/OuterLayout"; +import { Button, Spacing } from "@components/commons"; + +interface MenuBottomSheetProps { + isOpen: boolean; + children?: ReactNode; + onClickOutside: () => void; + handleStatus: (status: string) => void; +} + +const MenuBottomsheet = ({ + isOpen, + onClickOutside, + children, + handleStatus, + ...rest +}: MenuBottomSheetProps) => { + const handleWrapperClick = () => { + onClickOutside(); + }; + + return ( + + + + + + + + + + ); +}; + +export default MenuBottomsheet; diff --git a/src/pages/ticketholderlist/components/banner/Banner.styled.ts b/src/pages/ticketholderlist/components/banner/Banner.styled.ts deleted file mode 100644 index f100fe48..00000000 --- a/src/pages/ticketholderlist/components/banner/Banner.styled.ts +++ /dev/null @@ -1,50 +0,0 @@ -import styled from "styled-components"; - -export const BannerWrapper = styled.div<{ $image: string }>` - width: 37.5rem; - height: 7.2rem; - - /* 사용자가 입력한 이미지 background 적당히 잘라서 사용하도록 */ - background-image: url(${({ $image }) => $image}); - background-repeat: no-repeat; - background-position: center; - background-size: cover; -`; - -export const BannerTextLayout = styled.div` - display: flex; - flex-shrink: 0; - align-items: center; - justify-content: center; - width: 37.5rem; - height: 7.2rem; - - background: rgb(0 0 0 / 60%); -`; - -export const BannerTextBox = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - width: 32.7rem; -`; - -export const BannerTitleText = styled.span` - width: 17.7rem; - overflow: hidden; - - color: ${({ theme }) => theme.colors.white}; - ${({ theme }) => theme.fonts.heading4}; - white-space: nowrap; - text-overflow: ellipsis; -`; - -export const BannerStateTextBox = styled.span` - color: ${({ theme }) => theme.colors.white}; - ${({ theme }) => theme.fonts["body1-normal-semi"]}; -`; - -export const CountTextSpan = styled.span` - color: ${({ theme }) => theme.colors.pink_400}; - ${({ theme }) => theme.fonts["body1-normal-semi"]}; -`; diff --git a/src/pages/ticketholderlist/components/banner/Banner.tsx b/src/pages/ticketholderlist/components/banner/Banner.tsx deleted file mode 100644 index 2f5da94f..00000000 --- a/src/pages/ticketholderlist/components/banner/Banner.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import * as S from "./Banner.styled"; - -interface BannerProps { - title?: string; - image: string; - reservedCount: number; - isOutdated: boolean; -} - -const Banner = ({ title, image, reservedCount, isOutdated }: BannerProps) => { - return ( - - - - - {title} - {isOutdated ? ( - - 총 {reservedCount}매 - 판매 됨 - - ) : ( - - {reservedCount}매 - 예매됨 - - )} - - - - - ); -}; - -export default Banner; diff --git a/src/pages/ticketholderlist/components/manageCard/ManageAccount.styled.ts b/src/pages/ticketholderlist/components/manageCard/ManageAccount.styled.ts new file mode 100644 index 00000000..46b7939e --- /dev/null +++ b/src/pages/ticketholderlist/components/manageCard/ManageAccount.styled.ts @@ -0,0 +1,28 @@ +import styled from "styled-components"; +import { IcomCopy } from "@assets/svgs"; + +export const ManageAccountWrapper = styled.button` + display: flex; + align-items: center; + justify-content: space-between; + width: 29.9rem; + height: 4.8rem; + margin: 0.8rem 0 0.4rem 2.8rem; + padding: 0.8rem 1.6rem; + + color: ${({ theme }) => theme.colors.gray_300}; + ${({ theme }) => theme.fonts["caption1-medi"]}; + + border: 0.1rem solid; + border-color: ${({ theme }) => theme.colors.gray_700}; + border-radius: 0.6rem; +`; + +export const CopyIcon = styled(IcomCopy)` + width: 2.4rem; + height: 2.4rem; + + path { + fill: ${({ theme }) => theme.colors.gray_300}; + } +`; diff --git a/src/pages/ticketholderlist/components/manageCard/ManageAccount.tsx b/src/pages/ticketholderlist/components/manageCard/ManageAccount.tsx new file mode 100644 index 00000000..eb6b4b9f --- /dev/null +++ b/src/pages/ticketholderlist/components/manageCard/ManageAccount.tsx @@ -0,0 +1,24 @@ +import * as S from "./ManageAccount.styled"; + +interface ManageAccountProps { + bankName: string; + accountNumber: string; + accountHolder: string; + handleCopyClipBoard: (number) => void; +} + +export default function ManageAccount({ + bankName, + accountNumber, + accountHolder, + handleCopyClipBoard, +}: ManageAccountProps) { + return ( + <> + handleCopyClipBoard(accountNumber)}> + {bankName} ({accountHolder}) {accountNumber} + + + + ); +} diff --git a/src/pages/ticketholderlist/components/manageCard/ManageCardContainer.styled.ts b/src/pages/ticketholderlist/components/manageCard/ManageCardContainer.styled.ts new file mode 100644 index 00000000..abd1d468 --- /dev/null +++ b/src/pages/ticketholderlist/components/manageCard/ManageCardContainer.styled.ts @@ -0,0 +1,85 @@ +import styled, { css } from "styled-components"; + +export const ManageCardWrapper = styled.section` + display: flex; + width: 100%; + height: 10rem; +`; + +export const InfoBox = styled.section` + display: flex; + width: 100%; + height: 100%; + padding: 1.6rem; + + background-color: ${({ theme }) => theme.colors.gray_800}; + border-radius: 0.6rem; +`; + +export const StatusBox = styled.section<{ $status: string }>` + display: flex; + justify-content: center; + min-width: 7.5rem; + height: 100%; + margin-left: 0.2rem; + padding: 4.2rem 1.6rem; + + ${({ $status }) => { + switch ($status) { + case "미입금": + return css` + color: ${({ theme }) => theme.colors.pink_200}; + `; + case "입금 완료": + return css` + color: ${({ theme }) => theme.colors.gray_400}; + `; + case "취소 완료": + return css` + color: ${({ theme }) => theme.colors.gray_400}; + `; + case "환불 요청": + return css` + color: ${({ theme }) => theme.colors.red}; + `; + } + }} + + ${({ theme }) => theme.fonts["caption2-semi"]}; + + background-color: ${({ theme }) => theme.colors.gray_800}; + border-radius: 0.6rem; +`; + +export const TextContainer = styled.section` + display: flex; + flex-direction: column; + gap: 0.4rem; +`; + +export const InfoText = styled.div<{ $status: string }>` + ${({ theme }) => theme.fonts["body2-normal-medi"]}; + color: ${({ theme }) => theme.colors.white}; + + ${({ $status }) => { + if ($status === "취소 완료") { + return css` + color: ${({ theme }) => theme.colors.gray_400}; + text-decoration: line-through; + `; + } + }} +`; + +export const DateText = styled.div<{ $status: string }>` + ${({ theme }) => theme.fonts["caption1-medi"]}; + color: ${({ theme }) => theme.colors.gray_500}; + + ${({ $status }) => { + if ($status === "취소 완료") { + return css` + color: ${({ theme }) => theme.colors.gray_600}; + `; + } + }} +`; diff --git a/src/pages/ticketholderlist/components/manageCard/ManageCardContainer.tsx b/src/pages/ticketholderlist/components/manageCard/ManageCardContainer.tsx new file mode 100644 index 00000000..329ffdee --- /dev/null +++ b/src/pages/ticketholderlist/components/manageCard/ManageCardContainer.tsx @@ -0,0 +1,36 @@ +import * as S from "./ManageCardContainer.styled"; + +interface ManagerCardContainerProps { + name: string; + phoneNumber: string; + ticketCount: number; + scheduleNumber: number; + date: string; + status: string; +} + +export default function ManageCardContainer({ + name, + phoneNumber, + ticketCount, + scheduleNumber, + date, + status, +}: ManagerCardContainerProps) { + return ( + + + + + {name} ({phoneNumber}) + + + {scheduleNumber}회차 / {ticketCount}매 + + {date} + + + {status} + + ); +} diff --git a/src/pages/ticketholderlist/components/manageCard/ManageCardMain.styled.ts b/src/pages/ticketholderlist/components/manageCard/ManageCardMain.styled.ts new file mode 100644 index 00000000..31b94720 --- /dev/null +++ b/src/pages/ticketholderlist/components/manageCard/ManageCardMain.styled.ts @@ -0,0 +1,7 @@ +import styled from "styled-components"; + +export const ManageCardMainWrapper = styled.section` + display: flex; + flex-direction: column; + width: 32.7rem; +`; diff --git a/src/pages/ticketholderlist/components/manageCard/ManageCardMain.tsx b/src/pages/ticketholderlist/components/manageCard/ManageCardMain.tsx new file mode 100644 index 00000000..03c95de9 --- /dev/null +++ b/src/pages/ticketholderlist/components/manageCard/ManageCardMain.tsx @@ -0,0 +1,12 @@ +import { ReactNode } from "react"; +import * as S from "./ManageCardMain.styled"; + +export interface ManageCardMainProps { + children: ReactNode; +} + +const ManageCardMain = ({ children }: ManageCardMainProps) => { + return {children}; +}; + +export default ManageCardMain; diff --git a/src/pages/ticketholderlist/components/manageCard/ManageCheckBox.styled.ts b/src/pages/ticketholderlist/components/manageCard/ManageCheckBox.styled.ts new file mode 100644 index 00000000..8204392a --- /dev/null +++ b/src/pages/ticketholderlist/components/manageCard/ManageCheckBox.styled.ts @@ -0,0 +1,27 @@ +import styled from "styled-components"; +import { IconCheckboxSelectedOn, IconCheckboxUnselectedOn } from "@assets/svgs"; + +export const CheckBoxWrapper = styled.section` + display: flex; +`; + +export const CheckBox = styled.input` + position: absolute; + + width: 1.8rem; + height: 1.8rem; + + opacity: 0; +`; + +export const SelectIcon = styled(IconCheckboxSelectedOn)` + width: 1.8rem; + height: 1.8rem; + margin-right: 1rem; +`; + +export const UnSelectIcon = styled(IconCheckboxUnselectedOn)` + width: 1.8rem; + height: 1.8rem; + margin-right: 1rem; +`; diff --git a/src/pages/ticketholderlist/components/manageCard/ManageCheckBox.tsx b/src/pages/ticketholderlist/components/manageCard/ManageCheckBox.tsx new file mode 100644 index 00000000..1075b967 --- /dev/null +++ b/src/pages/ticketholderlist/components/manageCard/ManageCheckBox.tsx @@ -0,0 +1,28 @@ +import * as S from "./ManageCheckBox.styled"; + +interface ManageCheckBoxProps { + bookingId: number; + checkedBookingId: number[]; + handleBookingIdCheck: (bookingId: number) => void; +} + +export default function ManageCheckBox({ + bookingId, + checkedBookingId, + handleBookingIdCheck, +}: ManageCheckBoxProps) { + const isChecked = checkedBookingId.includes(bookingId); + + return ( + + { + handleBookingIdCheck(bookingId); + }} + /> + {isChecked ? : } + + ); +} diff --git a/src/pages/ticketholderlist/components/manageCard/index.ts b/src/pages/ticketholderlist/components/manageCard/index.ts new file mode 100644 index 00000000..9785ca3e --- /dev/null +++ b/src/pages/ticketholderlist/components/manageCard/index.ts @@ -0,0 +1,10 @@ +import ManageCardMain from "./ManageCardMain"; +import ManageCardContainer from "./ManageCardContainer"; +import ManageCheckBox from "./ManageCheckBox"; +import ManageAccount from "./ManageAccount"; + +export const ManageCard = Object.assign(ManageCardMain, { + ManageCardContainer: ManageCardContainer, + ManageCheckBox: ManageCheckBox, + ManageAccount: ManageAccount, +}); diff --git a/src/pages/ticketholderlist/components/managercard/ManagerCard.styled.ts b/src/pages/ticketholderlist/components/managercard/ManagerCard.styled.ts deleted file mode 100644 index 51c91f2e..00000000 --- a/src/pages/ticketholderlist/components/managercard/ManagerCard.styled.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { IconCheckboxSelectedOn, IconCheckboxUnselectedOn } from "@assets/svgs"; -import styled, { keyframes } from "styled-components"; - -const expandAnimation = keyframes` - 0% { - height: 7.4rem; - } - - 100% { - height: 14.6rem; - } -`; - -const unexpandAnimation = keyframes` - 0% { - height: 14.6rem; - } - - 100% { - height: 7.4rem; - } -`; - -const revealAnimation = keyframes` - 0% { - transform: translateY(-20px); - opacity: 0; - } - - 100% { - transform: translateY(0); - opacity: 1; - } -`; - -const unrevealAnimation = keyframes` - 0% { - transform: translateY(0); - opacity: 0; - } - - 100% { - transform: translateY(-20px); - opacity: 0; - } -`; - -export const ManagerCardWrapper = styled.article` - display: flex; - flex-shrink: 0; - align-items: center; - justify-content: center; - width: 32.6rem; - height: 10rem; - padding-left: 0.4rem; -`; - -export const ManagerCardLayout = styled.div<{ $isEditMode: boolean }>` - display: flex; - flex-direction: column; - flex-shrink: 0; - gap: 1.6rem; - align-items: flex-start; - width: ${({ $isEditMode }) => ($isEditMode ? "22.2rem" : "25.2rem")}; - height: 10rem; - margin-right: 0.2rem; - padding: 1.6rem; - - background-color: ${({ theme }) => theme.colors.gray_800}; - border-right: 1px solid ${({ theme }) => theme.colors.black}; - border-radius: 6px; - - transition: max-height 0.5s ease; -`; - -export const ManagerCardBox = styled.div` - display: flex; - flex-direction: column; - gap: 0.4rem; - align-items: flex-start; - align-self: stretch; -`; - -/* -언제 사용할 지 모르니까 주석으로 남겨둠 -export const ManagerCardDetailBox = styled.div<{ $isDetail: boolean }>` - display: flex; - flex-direction: column; - gap: 0.4rem; - align-items: flex-start; - align-self: stretch; - - background-color: ${({ theme, $isDetail }) => $isDetail && theme.colors.gray_800}; - opacity: ${({ $isDetail }) => ($isDetail ? "1" : "0")}; - - transition: background-color 0.25s ease; - animation: ${({ $isDetail }) => ($isDetail ? revealAnimation : unrevealAnimation)} 0.7s ease; -`; - - -export const ManagerCardDetailLayout = styled.div<{ $isDetail: boolean; $isEditMode: boolean }>` - - display: flex; - flex-direction: column; - flex-shrink: 0; - gap: 1.6rem; - align-items: flex-start; - width: ${({ $isEditMode }) => ($isEditMode ? "22.2rem" : "25.2rem")}; - - height: ${({ $isDetail }) => ($isDetail ? "14.6rem" : "7.4rem")}; - padding: 1.6rem; - - background-color: ${({ theme }) => theme.colors.gray_800}; - opacity: ${({ $isDetail }) => ($isDetail ? "1" : "0")}; - border-right: 1px solid ${({ theme }) => theme.colors.black}; - border-radius: 6px; -`; -*/ - -export const ManagerCardTextBox = styled.div` - display: flex; - gap: 0.6rem; - align-items: center; - justify-content: flex-start; -`; - -export const ManagerCardTextTitle = styled.span` - color: ${({ theme }) => theme.colors.gray_400}; - ${({ theme }) => theme.fonts["body2-normal-medi"]}; - white-space: nowrap; -`; - -export const ManagerCardTextContent = styled.span` - color: ${({ theme }) => theme.colors.white}; - ${({ theme }) => theme.fonts["body2-normal-medi"]}; - white-space: nowrap; -`; - -export const ManagerCardRadioLayout = styled.div` - display: flex; - flex-shrink: 0; - align-items: center; - justify-content: center; - width: 7.4rem; - height: 10rem; - padding: 1.5rem 1.5rem 1.5rem 1.6rem; - - background-color: ${({ theme }) => theme.colors.gray_800}; - border-radius: 6px; -`; - -export const ManagerCardRadioBox = styled.div<{ $isEditMode: boolean }>` - display: flex; - flex-direction: column; - flex-shrink: 0; - gap: 1rem; - align-items: center; - width: 4.3rem; - - cursor: ${({ $isEditMode }) => ($isEditMode ? "default" : "pointer")}; -`; - -export const SelectedIcon = styled(IconCheckboxSelectedOn)` - width: 1.8rem; - height: 1.8rem; -`; - -export const UnselectedIcon = styled(IconCheckboxUnselectedOn)` - width: 1.8rem; - height: 1.8rem; -`; - -export const ManagerCardRadioText = styled.span<{ $isPaid }>` - color: ${({ theme, $isPaid }) => ($isPaid ? theme.colors.pink_400 : theme.colors.gray_300)}; - text-align: center; - - ${({ theme }) => theme.fonts["caption2-semi"]}; -`; diff --git a/src/pages/ticketholderlist/components/managercard/ManagerCard.tsx b/src/pages/ticketholderlist/components/managercard/ManagerCard.tsx deleted file mode 100644 index 5fe87414..00000000 --- a/src/pages/ticketholderlist/components/managercard/ManagerCard.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { PatchFormDataProps } from "@typings/deleteBookerFormatProps"; -import { Dispatch, SetStateAction } from "react"; -import SelectIcon from "../selectIcon/SelectIcon"; -import * as S from "./ManagerCard.styled"; - -export const convertingNumber = (_scheduleNumber: string) => { - switch (_scheduleNumber) { - case "FIRST": - return 1; - case "SECOND": - return 2; - case "THIRD": - return 3; - case "FOURTH": - return 4; - case "FIFTH": - return 5; - case "SIXTH": - return 6; - case "SEVENTH": - return 7; - case "EIGHTH": - return 8; - case "NINTH": - return 9; - case "TENTH": - return 10; - default: - throw new Error("없는 회차"); - } -}; - -const ManagerCard = ({ - patchFormData, - setPatchFormData, - isEditMode, - bookingId, - isPaid, - setPaid, - bookername, - purchaseTicketeCount, - scheduleNumber, - bookerPhoneNumber, - createAt, - alreadyBookingConfirmed, -}: { - patchFormData: PatchFormDataProps; - setPatchFormData: Dispatch>; - isEditMode: boolean; - bookingId?: number; - isPaid?: "CHECKING_PAYMENT" | "BOOKING_CONFIRMED" | "BOOKING_CANCELLED"; - setPaid: () => void; - bookername?: string; - purchaseTicketeCount?: number; - scheduleNumber?: string; - bookerPhoneNumber?: string; - createAt?: string; - alreadyBookingConfirmed: boolean; -}) => { - const handleCheckBox = (managerBookingId: number) => { - //삭제할 데이터 form에 추가하는 로직 - setPatchFormData((prevFormData) => { - const isAlreadyChecked = prevFormData.bookingList.some( - (_bookingId) => _bookingId === managerBookingId - ); - - const updateBookingList = isAlreadyChecked - ? prevFormData.bookingList.filter((_bookingId) => _bookingId !== managerBookingId) - : [...prevFormData.bookingList, managerBookingId]; - return { ...prevFormData, bookingList: updateBookingList }; - }); - - //입금 여부 변경될 거 추가하는 로직 - setPaid(); - }; - - const date = createAt?.split("T")[0]; - const formattedDate = date?.replace(/-/g, ". "); - - return ( - - {isEditMode && ( - handleCheckBox(bookingId as number)} - isChecked={patchFormData.bookingList.some((_bookingId) => _bookingId === bookingId)} - alreadyBookingConfirmed={alreadyBookingConfirmed} - /> - )} - { - if (alreadyBookingConfirmed) { - return; - } - isEditMode && handleCheckBox(bookingId as number); - }} - > - - - {bookername} - {`(${bookerPhoneNumber})`} - - - {`${convertingNumber(scheduleNumber as string)}회차`} - {`/ ${purchaseTicketeCount}매`} - - - {formattedDate} - - - - - - - - {isPaid === "BOOKING_CONFIRMED" - ? "입금 완료" - : isPaid === "CHECKING_PAYMENT" - ? "미입금" - : "예매 취소"} - - - - - ); -}; - -export default ManagerCard; diff --git a/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.styled.ts b/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.styled.ts deleted file mode 100644 index 4b37b8bc..00000000 --- a/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.styled.ts +++ /dev/null @@ -1,107 +0,0 @@ -import SvgIconChevronBack from "@assets/svgs/IconChevronBack"; -import styled, { css } from "styled-components"; - -export const DropdownWrapper = styled.div` - position: relative; -`; - -export const DropdownButton = styled.button<{ $isChoosed: boolean }>` - display: inline-flex; - flex-shrink: 0; - gap: 0.6rem; - align-items: center; - min-width: 9.3rem; - height: 4rem; - padding: 0.8rem 0.8rem 0.8rem 1.2rem; - - /* border: 1px solid - ${({ theme, $isChoosed }) => ($isChoosed ? theme.colors.gray_0 : theme.colors.gray_700)}; */ - border-radius: 0.4rem; -`; - -export const DropDownButtonContent = styled.div` - display: flex; - gap: 0.4rem; - align-items: center; - justify-content: space-between; - width: 100%; -`; - -export const ButtonContentSpan = styled.span` - color: ${({ theme }) => theme.colors.gray_0}; - ${({ theme }) => theme.fonts["body2-normal-medi"]}; -`; - -export const SvgIcon = styled(SvgIconChevronBack)<{ $rotate: boolean }>` - width: 16px; - height: 16px; - - transform: rotate(180deg); - - ${(prop) => - prop.$rotate && - css` - transform: rotate(360deg); - `} -`; - -export const DropdownContentWrapper = styled.div<{ $show: boolean }>` - position: absolute; - top: 4.06rem; /* 버튼의 높이 3.26rem + 간격 0.8rem */ - z-index: 1; - display: ${(props) => (props.$show ? "flex" : "none")}; - flex-shrink: 0; - gap: 0.6rem; - align-items: center; - width: 9.2rem; - margin-top: 0.8rem; - padding: 0.8rem 0.8rem 0.8rem 1.2rem; - - background-color: ${({ theme }) => theme.colors.black}; - border: 1px solid ${({ theme }) => theme.colors.gray_400}; - border-radius: 0.4rem; -`; - -export const DropdownContentLayout = styled.div` - display: flex; - flex-direction: column; - gap: 0.6rem; - align-items: flex-start; - justify-content: center; -`; - -export const DropdownContentButton = styled.button<{ $isLast: boolean }>` - display: flex; - flex-direction: column; - gap: 0.8rem; - align-items: flex-start; - width: 6.8rem; - - /* 피그마에서는 겹치는 부분이 제대로 계산 안되서, 개발하면서 적당히 줄여둠 */ - padding: 0.2rem 0 0.6rem; - - ${({ $isLast }) => - !$isLast && - css` - border-bottom: 1px solid ${({ theme }) => theme.colors.gray_700}; - `} -`; - -export const DropdownContentText = styled.span<{ - $selected: boolean; -}>` - display: flex; - gap: 0.4rem; - align-items: center; - align-self: stretch; - justify-content: flex-start; - - color: ${({ theme }) => theme.colors.gray_0}; - ${({ theme }) => theme.fonts["body2-normal-medi"]}; - - ${({ $selected }) => - $selected && - css` - color: ${({ theme }) => theme.colors.pink_400}; - `} -`; diff --git a/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.tsx b/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.tsx deleted file mode 100644 index b99ec93a..00000000 --- a/src/pages/ticketholderlist/components/narrowDropDown/NarrowDropDown.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { ReactNode, useState } from "react"; -import * as S from "./NarrowDropDown.styled"; - -interface DropdownProps { - children: ReactNode; - totalScheduleCount: number; - schedule: number; - payment: - | "CHECKING_PAYMENT" - | "BOOKING_CONFIRMED" - | "BOOKING_CANCELLED" - | "REFUND_REQUESTED" - | "BOOKING_DELETED" - | undefined; - setSchedule?: (param: number) => void; - setPayment?: ( - param: - | "CHECKING_PAYMENT" - | "BOOKING_CONFIRMED" - | "BOOKING_CANCELLED" - | "REFUND_REQUESTED" - | "BOOKING_DELETED" - | undefined - ) => void; -} - -const NarrowDropDown = ({ - children, - totalScheduleCount, - schedule, - payment, - setSchedule, - setPayment, -}: DropdownProps) => { - const [showDropdown, setShowDropdown] = useState(false); - const [isOneChoosed, setIsOneChoosed] = useState(false); - - const handleToggle = () => { - setShowDropdown(!showDropdown); - }; - - const renderDropdownContent = (count: number, childrenNode: ReactNode) => { - const items = []; - const childrenString = childrenNode?.toString(); - if (childrenString === "모든 회차") { - const handleScheduleAll = () => { - setSchedule?.(0); - setIsOneChoosed(false); - setShowDropdown(false); - }; - items.push( - - 전체 - - ); - for (let i = 1; i <= count; i++) { - const handleSchedule = () => { - setSchedule?.(i); - setIsOneChoosed(true); - setShowDropdown(false); - }; - - items.push( - - {i}회차 - - ); - } - } else if (childrenString === "입금 상태") { - const handlePaymentUndefined = () => { - setPayment?.(undefined); - setIsOneChoosed(false); - setShowDropdown(false); - }; - - const handlePaymentFalse = () => { - setPayment?.("CHECKING_PAYMENT"); - setIsOneChoosed(true); - setShowDropdown(false); - }; - - const handlePaymentTrue = () => { - setPayment?.("BOOKING_CONFIRMED"); - setIsOneChoosed(true); - setShowDropdown(false); - }; - - items.push( - - 전체 - - ); - - items.push( - - - 미입금 - - - ); - items.push( - - - 입금완료 - - - ); - } - - return items; - }; - - //공연별로 총 회차 값으로 넘겨주기로 함. - //각 공연별 회차는, DB 테이블 구조에 따라 enum값으로 넘겨주기로 함 - //필터링은 회차 번호(scheduleNumber : FIRST, SECOND, THIRD), 입금 완료 여부는 paymentStatus에 따라 나뉠거임 - // - - let changedChildren; - if (children === "입금 상태" && payment !== undefined) { - changedChildren = payment === "BOOKING_CONFIRMED" ? "입금완료" : "미입금"; - } else if (children === "모든 회차" && schedule !== 0) { - changedChildren = `${schedule}회차`; - } - - return ( - - - - - {changedChildren === undefined ? children : changedChildren} - - - - - - - {renderDropdownContent(totalScheduleCount, children)} - - - - ); -}; - -export default NarrowDropDown; diff --git a/src/pages/ticketholderlist/components/nonExistent/NonExistent..tsx b/src/pages/ticketholderlist/components/nonExistent/NonExistent..tsx new file mode 100644 index 00000000..9c8a6ce7 --- /dev/null +++ b/src/pages/ticketholderlist/components/nonExistent/NonExistent..tsx @@ -0,0 +1,25 @@ +import React from "react"; +import * as S from "./NonExistent.styled"; + +interface NonExistentProps { + status: string; +} + +const NonExistent = ({ status }: NonExistentProps) => { + return ( + + + + + {status === "PAYMENT" + ? "미입금한 예매자가 없어요." + : status === "REFUND" + ? "환불 요청이 없어요." + : "아직 예매자가 없어요."} + + + + ); +}; + +export default NonExistent; diff --git a/src/pages/ticketholderlist/components/nonExistent/NonExistent.styled.ts b/src/pages/ticketholderlist/components/nonExistent/NonExistent.styled.ts new file mode 100644 index 00000000..8bb851ac --- /dev/null +++ b/src/pages/ticketholderlist/components/nonExistent/NonExistent.styled.ts @@ -0,0 +1,35 @@ +import styled from "styled-components"; +import { Empty } from "@assets/svgs"; + +export const NonExistentWrapper = styled.section` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 37.4rem; + height: 100%; + padding: 2.4rem; + + background-color: ${({ theme }) => theme.colors.gray_900}; +`; + +export const NonExistenLayout = styled.section` + top: 19.2rem; + display: flex; + flex-direction: column; + gap: 2.4rem; + align-items: center; + width: 32.6rem; + margin-top: 14.2rem; +`; + +export const EmptyImg = styled(Empty)` + width: 15rem; + height: 15rem; +`; + +export const EmptyText = styled.div` + color: ${({ theme }) => theme.colors.gray_300}; + ${({ theme }) => theme.fonts["body2-normal-medi"]}; + text-align: center; +`; diff --git a/src/pages/ticketholderlist/components/searchBar/SearchBar.styled.ts b/src/pages/ticketholderlist/components/searchBar/SearchBar.styled.ts new file mode 100644 index 00000000..51a9defd --- /dev/null +++ b/src/pages/ticketholderlist/components/searchBar/SearchBar.styled.ts @@ -0,0 +1,50 @@ +import styled from "styled-components"; +import { BtnFilter, IconSearch } from "@assets/svgs"; + +export const SearchBarWrapper = styled.section` + display: flex; + flex-direction: row; + align-items: center; + width: 32.7rem; +`; +export const SearchBarLayout = styled.section` + display: flex; + flex-grow: 1; + align-items: center; + height: 4.8rem; + padding: 1.2rem 1.3rem; + + background-color: ${({ theme }) => theme.colors.gray_800}; + border: 0.1rem solid; + border-color: ${({ theme }) => theme.colors.gray_700}; + border-radius: 4rem; +`; + +export const SearchIcon = styled(IconSearch)` + width: 2.4rem; + height: 2.4rem; + + path { + fill: ${({ theme }) => theme.colors.gray_300}; + } +`; + +export const SearchBar = styled.input` + height: 2.4rem; + + color: ${({ theme }) => theme.colors.white}; + ${({ theme }) => theme.fonts["body2-normal-medi"]}; + + background-color: ${({ theme }) => theme.colors.gray_800}; +`; + +export const FilterBtn = styled(BtnFilter)<{ $isFilter: boolean }>` + width: 4.8rem; + height: 4.8rem; + margin-left: 1.2rem; + + background-color: ${({ theme }) => theme.colors.gray_800}; + border: 0.1rem solid + ${({ theme, $isFilter }) => ($isFilter ? theme.colors.white : theme.colors.gray_700)}; + border-radius: 50%; +`; diff --git a/src/pages/ticketholderlist/components/searchBar/SearchBar.tsx b/src/pages/ticketholderlist/components/searchBar/SearchBar.tsx new file mode 100644 index 00000000..88f66bf2 --- /dev/null +++ b/src/pages/ticketholderlist/components/searchBar/SearchBar.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import * as S from "./SearchBar.styled"; + +interface SearchBarProps { + status: string; + searchWord: string; + handleFilterSheet: () => void; + handleInputChange: (event: React.ChangeEvent) => void; + isFilter: boolean; +} + +const SearchBar = ({ + handleFilterSheet, + status, + handleInputChange, + searchWord, + isFilter, +}: SearchBarProps) => { + return ( + + + + + + {status === "DEFAULT" && } + + ); +}; + +export default SearchBar; diff --git a/src/pages/ticketholderlist/components/selectIcon/SelectIcon.styled.ts b/src/pages/ticketholderlist/components/selectIcon/SelectIcon.styled.ts deleted file mode 100644 index b17452d9..00000000 --- a/src/pages/ticketholderlist/components/selectIcon/SelectIcon.styled.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - IconCheckboxDisabledOn, - IconCheckboxSelectedOn, - IconCheckboxUnselectedOn, -} from "@assets/svgs"; -import styled from "styled-components"; - -export const DeleteSelectedIcon = styled(IconCheckboxSelectedOn)` - width: 1.8rem; - height: 1.8rem; - margin-right: 0.8rem; - - cursor: pointer; -`; - -export const DeleteUnselectedIcon = styled(IconCheckboxUnselectedOn)` - width: 1.8rem; - height: 1.8rem; - margin-right: 0.8rem; - - cursor: pointer; -`; - -export const CantSelectedIcon = styled(IconCheckboxDisabledOn)` - width: 1.8rem; - height: 1.8rem; - margin-right: 0.8rem; - - cursor: not-allowed; -`; diff --git a/src/pages/ticketholderlist/components/selectIcon/SelectIcon.tsx b/src/pages/ticketholderlist/components/selectIcon/SelectIcon.tsx deleted file mode 100644 index 457c0539..00000000 --- a/src/pages/ticketholderlist/components/selectIcon/SelectIcon.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as S from "./SelectIcon.styled"; - -const SelectIcon = ({ - onClick, - isChecked, - alreadyBookingConfirmed, -}: { - onClick: () => void; - isChecked: boolean; - alreadyBookingConfirmed: boolean; -}) => { - return ( - <> - {alreadyBookingConfirmed ? ( - - ) : isChecked ? ( - - ) : ( - - )} - - ); -}; - -export default SelectIcon; diff --git a/src/pages/ticketholderlist/components/selectedChips/SelectedChips.styled.ts b/src/pages/ticketholderlist/components/selectedChips/SelectedChips.styled.ts new file mode 100644 index 00000000..e2ef1015 --- /dev/null +++ b/src/pages/ticketholderlist/components/selectedChips/SelectedChips.styled.ts @@ -0,0 +1,40 @@ +import styled from "styled-components"; +import { IcDelete } from "@assets/svgs"; + +export const SelectedChipsWrapper = styled.section` + display: flex; + gap: 0.8rem; + width: 32.7rem; + overflow-x: scroll; + + scrollbar-width: none; + -ms-overflow-style: none; + + &::-webkit-scrollbar { + display: none; + } + white-space: nowrap; +`; + +export const Chip = styled.section` + display: flex; + gap: 0.2rem; + align-items: center; + justify-content: center; + width: auto; + margin-top: 1.6rem; + padding: 0.8rem 1.2rem; + + ${({ theme }) => theme.fonts["body2-normal-medi"]}; + color: ${({ theme }) => theme.colors.gray_200}; + + border: 0.1rem solid; + border-color: ${({ theme }) => theme.colors.gray_700}; + border-radius: 9.9rem; +`; + +// delete icon 확인하기 +export const DeleteIcon = styled(IcDelete)` + width: 1.6rem; + height: 1.6rem; +`; diff --git a/src/pages/ticketholderlist/components/selectedChips/SelectedChips.tsx b/src/pages/ticketholderlist/components/selectedChips/SelectedChips.tsx new file mode 100644 index 00000000..0e326c38 --- /dev/null +++ b/src/pages/ticketholderlist/components/selectedChips/SelectedChips.tsx @@ -0,0 +1,41 @@ +import * as S from "./SelectedChips.styled"; +import { FilterListType, PaymentType } from "@pages/ticketholderlist/TicketHolderList"; +import { convertingBookingStatus } from "@constants/convertingBookingStatus"; + +interface SelectedChipsProps { + filterList: FilterListType; + handleFilter: (scheduleNumber: number[], bookingStatus: string[]) => void; +} + +const SelectedChips = ({ filterList, handleFilter }: SelectedChipsProps) => { + const handleClickDelete = (scheduleNumberToDelete: number, bookingStatusToDelete: string) => { + const updatedScheduleNumbers = filterList.scheduleNumber + .filter((item) => item !== scheduleNumberToDelete) + .map((item) => item); + + const updatedBookingStatuses = filterList.bookingStatus + .filter((item) => item !== bookingStatusToDelete) + .map((item) => item); + + handleFilter(updatedScheduleNumbers, updatedBookingStatuses); + }; + + return ( + + {filterList.scheduleNumber.map((filter, index) => ( + + {filter}회차 {/* scheduleNumber 사용 */} + handleClickDelete(filter, "")} /> + + ))} + {filterList.bookingStatus.map((filter, index) => ( + + {convertingBookingStatus(filter as PaymentType)} + handleClickDelete(0, filter)} /> + + ))} + + ); +}; + +export default SelectedChips; diff --git a/src/pages/ticketholderlist/components/title/Title.styled.ts b/src/pages/ticketholderlist/components/title/Title.styled.ts new file mode 100644 index 00000000..4afe4882 --- /dev/null +++ b/src/pages/ticketholderlist/components/title/Title.styled.ts @@ -0,0 +1,48 @@ +import styled from "styled-components"; + +export const TitleWrapper = styled.section` + display: flex; + flex-direction: column; +`; + +export const PerformanceTeamName = styled.div` + display: inline; + width: 32.7rem; + overflow: hidden; + + ${({ theme }) => theme.fonts["caption1-semi"]}; + color: ${({ theme }) => theme.colors.gray_400}; + white-space: nowrap; + text-overflow: ellipsis; +`; + +export const PerformanceTitleWrapper = styled.section` + display: flex; + justify-content: space-between; + width: 32.8rem; +`; + +export const PerformanceTitle = styled.div` + display: inline; + width: 25.8rem; + overflow: hidden; + + ${({ theme }) => theme.fonts.heading2}; + color: ${({ theme }) => theme.colors.white}; + white-space: nowrap; + text-overflow: ellipsis; +`; + +export const TicketCountWrapper = styled.section` + display: flex; +`; + +export const PurchaseTicketCount = styled.div` + ${({ theme }) => theme.fonts["body1-normal-medi"]}; + color: ${({ theme }) => theme.colors.pink_200}; +`; + +export const TotalTicketCount = styled.div` + ${({ theme }) => theme.fonts["body1-normal-medi"]}; + color: ${({ theme }) => theme.colors.gray_200}; +`; diff --git a/src/pages/ticketholderlist/components/title/Title.tsx b/src/pages/ticketholderlist/components/title/Title.tsx new file mode 100644 index 00000000..0afb8082 --- /dev/null +++ b/src/pages/ticketholderlist/components/title/Title.tsx @@ -0,0 +1,26 @@ +import * as S from "./Title.styled"; + +interface BookingInfoProps { + title: string; + teamName: string; + totalSolidCount: number; + totalCount: number; +} + +const Title = ({ title, teamName, totalSolidCount, totalCount }: BookingInfoProps) => { + return ( + + {teamName} + + {title} + + {/* TODO : 필터링 받아올 때마다 값 다르게 설정 */} + {totalSolidCount} + /{totalCount}매 + + + + ); +}; + +export default Title; diff --git a/src/pages/ticketholderlist/constants/silkagel.png b/src/pages/ticketholderlist/constants/silkagel.png deleted file mode 100644 index a5295d7b..00000000 Binary files a/src/pages/ticketholderlist/constants/silkagel.png and /dev/null differ diff --git a/src/pages/ticketholderlist/types/bookingListType.ts b/src/pages/ticketholderlist/types/bookingListType.ts new file mode 100644 index 00000000..7028d775 --- /dev/null +++ b/src/pages/ticketholderlist/types/bookingListType.ts @@ -0,0 +1,20 @@ +type PaymentType = + | "CHECKING_PAYMENT" + | "BOOKING_CONFIRMED" + | "BOOKING_CANCELLED" + | "REFUND_REQUESTED" + | "BOOKING_DELETED"; + +export interface BookingListProps { + bookingId?: number; + bookerName?: string; + bookerPhoneNumber?: string; + scheduleId?: number; + purchaseTicketCount?: number; + createdAt?: string; + bookingStatus?: PaymentType; + scheduleNumber?: string; + bankName?: string; + accountNumber?: string; + accountHolder?: string; +} diff --git a/src/routes/LookupRoutes.tsx b/src/routes/LookupRoutes.tsx index cb44b268..579c4912 100644 --- a/src/routes/LookupRoutes.tsx +++ b/src/routes/LookupRoutes.tsx @@ -1,9 +1,11 @@ import Lookup from "@pages/lookup/Lookup"; import NonMbLookup from "@pages/nonMbLookup/NonMbLookup"; +import Cancel from "@pages/cancel/Cancel"; const LOOKUP_ROUTES = [ { path: "nonmb-lookup", element: }, { path: "lookup", element: }, + { path: "lookup/cancel", element: }, ]; -export default LOOKUP_ROUTES; \ No newline at end of file +export default LOOKUP_ROUTES; diff --git a/src/routes/Router.tsx b/src/routes/Router.tsx index 33ea7758..44115650 100644 --- a/src/routes/Router.tsx +++ b/src/routes/Router.tsx @@ -12,7 +12,6 @@ import ADMIN_ROUTES from "./AdminRoutes"; import Admin from "@admin/pages/admin/Admin"; import DesktopGlobalStyle from "@styles/desktop"; import TokenRefresher from "src/hooks/useTokenRefresher"; -import Cancel from "@pages/cancel/Cancel"; const router = createBrowserRouter([ { @@ -34,7 +33,6 @@ const router = createBrowserRouter([ ...LOOKUP_ROUTES, ...MANAGE_ROUTES, ...REGISTER_ROUTES, - { path: "cancel", element: }, { path: "*", element: }, ], }, diff --git a/src/typings/api/schema/index.ts b/src/typings/api/schema/index.ts index 8efa350c..67e0a120 100644 --- a/src/typings/api/schema/index.ts +++ b/src/typings/api/schema/index.ts @@ -283,7 +283,7 @@ export interface paths { * access token 재발급 API * @description refresh token으로 access token을 재발급하는 GET API입니다. */ - get: operations["refreshToken"]; + get: operations["issueAccessTokenUsingRefreshToken"]; put?: never; post?: never; delete?: never; @@ -1475,7 +1475,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["TicketUpdateRequest"]; + "application/json;charset=UTF-8": components["schemas"]["TicketUpdateRequest"]; }; }; responses: { @@ -1485,7 +1485,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseVoid"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseVoid"]; }; }; /** @description 이미 결제가 완료된 티켓의 상태는 변경할 수 없습니다. */ @@ -1494,16 +1494,16 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; - /** @description 공연 정보를 찾을 수 없습니다. */ + /** @description 회차 정보를 찾을 수 없습니다. */ 404: { headers: { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1517,7 +1517,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["TicketRefundRequest"]; + "application/json;charset=UTF-8": components["schemas"]["TicketRefundRequest"]; }; }; responses: { @@ -1527,16 +1527,16 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseVoid"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseVoid"]; }; }; - /** @description 해당 예매 내역을 찾을 수 없습니다. */ + /** @description 회차 정보를 찾을 수 없습니다. */ 404: { headers: { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1550,7 +1550,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["TicketDeleteRequest"]; + "application/json;charset=UTF-8": components["schemas"]["TicketDeleteRequest"]; }; }; responses: { @@ -1560,16 +1560,16 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseVoid"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseVoid"]; }; }; - /** @description 해당 예매 내역을 찾을 수 없습니다. */ + /** @description 회차 정보를 찾을 수 없습니다. */ 404: { headers: { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1583,7 +1583,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["PerformanceModifyRequest"]; + "application/json;charset=UTF-8": components["schemas"]["PerformanceModifyRequest"]; }; }; responses: { @@ -1593,7 +1593,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponsePerformanceModifyResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponsePerformanceModifyResponse"]; }; }; /** @description 예매자가 존재하여 가격을 수정할 수 없습니다. */ @@ -1602,7 +1602,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; /** @description 해당 공연의 소유자가 아닙니다. */ @@ -1611,7 +1611,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; /** @description 공연 정보를 찾을 수 없습니다. */ @@ -1620,7 +1620,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1634,7 +1634,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["PerformanceRequest"]; + "application/json;charset=UTF-8": components["schemas"]["PerformanceRequest"]; }; }; responses: { @@ -1644,7 +1644,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponsePerformanceResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponsePerformanceResponse"]; }; }; /** @description 필수 데이터가 누락되었습니다. */ @@ -1653,7 +1653,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; /** @description 공연 정보를 찾을 수 없습니다. */ @@ -1662,7 +1662,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1682,7 +1682,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseCarouselFindAllResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseCarouselFindAllResponse"]; }; }; /** @description 회원이 없습니다. */ @@ -1691,7 +1691,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1705,7 +1705,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["CarouselHandleRequest"]; + "application/json;charset=UTF-8": components["schemas"]["CarouselHandleRequest"]; }; }; responses: { @@ -1715,16 +1715,16 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseCarouselHandleAllResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseCarouselHandleAllResponse"]; }; }; - /** @description 해당 홍보 정보를 찾을 수 없습니다. */ + /** @description 회원이 없습니다. */ 404: { headers: { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1740,7 +1740,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["MemberLoginRequest"]; + "application/json;charset=UTF-8": components["schemas"]["MemberLoginRequest"]; }; }; responses: { @@ -1750,7 +1750,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseLoginSuccessResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseLoginSuccessResponse"]; }; }; /** @description 로그인 요청이 유효하지 않습니다. */ @@ -1759,7 +1759,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; /** @description 회원 정보를 찾을 수 없습니다. */ @@ -1768,7 +1768,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1788,7 +1788,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseVoid"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseVoid"]; }; }; /** @description 회원 정보를 찾을 수 없습니다. */ @@ -1797,7 +1797,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1811,7 +1811,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["MemberBookingRequest"]; + "application/json;charset=UTF-8": components["schemas"]["MemberBookingRequest"]; }; }; responses: { @@ -1821,7 +1821,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseMemberBookingResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseMemberBookingResponse"]; }; }; /** @description 잘못된 데이터 형식입니다. */ @@ -1830,16 +1830,16 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; - /** @description 회원 정보를 찾을 수 없습니다. */ + /** @description 회차 정보를 찾을 수 없습니다. */ 404: { headers: { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1853,7 +1853,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["GuestBookingRequest"]; + "application/json;charset=UTF-8": components["schemas"]["GuestBookingRequest"]; }; }; responses: { @@ -1863,7 +1863,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseGuestBookingResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseGuestBookingResponse"]; }; }; /** @description 잘못된 데이터 형식입니다. */ @@ -1872,16 +1872,16 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; - /** @description 공연 정보를 찾을 수 없습니다. */ + /** @description 회차 정보를 찾을 수 없습니다. */ 404: { headers: { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1895,7 +1895,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["GuestBookingRetrieveRequest"]; + "application/json;charset=UTF-8": components["schemas"]["GuestBookingRetrieveRequest"]; }; }; responses: { @@ -1905,7 +1905,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseListGuestBookingRetrieveResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseListGuestBookingRetrieveResponse"]; }; }; /** @description 입력하신 정보와 일치하는 예매 내역이 없습니다. 확인 후 다시 조회해주세요. */ @@ -1914,7 +1914,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1928,7 +1928,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["BookingRefundRequest"]; + "application/json;charset=UTF-8": components["schemas"]["BookingRefundRequest"]; }; }; responses: { @@ -1938,7 +1938,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseBookingRefundResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseBookingRefundResponse"]; }; }; /** @description 입력하신 정보와 일치하는 예매 내역이 없습니다. 확인 후 다시 조회해주세요. */ @@ -1947,7 +1947,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -1961,7 +1961,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["BookingCancelRequest"]; + "application/json;charset=UTF-8": components["schemas"]["BookingCancelRequest"]; }; }; responses: { @@ -1971,7 +1971,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseBookingCancelResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseBookingCancelResponse"]; }; }; /** @description 입력하신 정보와 일치하는 예매 내역이 없습니다. 확인 후 다시 조회해주세요. */ @@ -1980,7 +1980,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2000,17 +2000,17 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": string; + "application/json;charset=UTF-8": string; }; }; }; }; - refreshToken: { + issueAccessTokenUsingRefreshToken: { parameters: { - query: { - refreshToken: string; + query?: never; + header: { + Authorization_Refresh: string; }; - header?: never; path?: never; cookie?: never; }; @@ -2022,7 +2022,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseAccessTokenGetSuccess"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseAccessTokenGetSuccess"]; }; }; /** @description 유효하지 않은 토큰입니다. */ @@ -2031,7 +2031,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2039,8 +2039,8 @@ export interface operations { getTickets: { parameters: { query?: { - scheduleNumber?: "FIRST" | "SECOND" | "THIRD" | "FOURTH" | "FIFTH" | "SIXTH" | "SEVENTH" | "EIGHTH" | "NINTH" | "TENTH"; - bookingStatus?: "CHECKING_PAYMENT" | "BOOKING_CONFIRMED" | "BOOKING_CANCELLED" | "REFUND_REQUESTED" | "BOOKING_DELETED"; + scheduleNumbers?: ("FIRST" | "SECOND" | "THIRD" | "FOURTH" | "FIFTH" | "SIXTH" | "SEVENTH" | "EIGHTH" | "NINTH" | "TENTH")[]; + bookingStatuses?: ("CHECKING_PAYMENT" | "BOOKING_CONFIRMED" | "BOOKING_CANCELLED" | "REFUND_REQUESTED" | "BOOKING_DELETED")[]; }; header?: never; path: { @@ -2056,7 +2056,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseTicketRetrieveResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseTicketRetrieveResponse"]; }; }; /** @description 입력하신 정보와 일치하는 예매자 목록이 없습니다. */ @@ -2065,7 +2065,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2074,8 +2074,8 @@ export interface operations { parameters: { query: { searchWord: string; - scheduleNumber?: "FIRST" | "SECOND" | "THIRD" | "FOURTH" | "FIFTH" | "SIXTH" | "SEVENTH" | "EIGHTH" | "NINTH" | "TENTH"; - bookingStatus?: "CHECKING_PAYMENT" | "BOOKING_CONFIRMED" | "BOOKING_CANCELLED" | "REFUND_REQUESTED" | "BOOKING_DELETED"; + scheduleNumbers?: ("FIRST" | "SECOND" | "THIRD" | "FOURTH" | "FIFTH" | "SIXTH" | "SEVENTH" | "EIGHTH" | "NINTH" | "TENTH")[]; + bookingStatuses?: ("CHECKING_PAYMENT" | "BOOKING_CONFIRMED" | "BOOKING_CANCELLED" | "REFUND_REQUESTED" | "BOOKING_DELETED")[]; }; header?: never; path: { @@ -2091,7 +2091,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseTicketRetrieveResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseTicketRetrieveResponse"]; }; }; /** @description 입력하신 정보와 일치하는 예매자 목록이 없습니다. */ @@ -2100,7 +2100,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2124,7 +2124,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseTicketAvailabilityResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseTicketAvailabilityResponse"]; }; }; /** @description 잘못된 데이터 형식입니다. */ @@ -2133,7 +2133,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; /** @description 회차 정보를 찾을 수 없습니다. */ @@ -2142,7 +2142,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; /** @description 요청한 티켓 수량이 잔여 티켓 수를 초과했습니다. */ @@ -2151,7 +2151,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2173,7 +2173,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponsePerformanceModifyDetailResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponsePerformanceModifyDetailResponse"]; }; }; /** @description 공연 정보를 찾을 수 없습니다. */ @@ -2182,7 +2182,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2204,7 +2204,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseVoid"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseVoid"]; }; }; /** @description 공연의 소유자가 아니거나 예매자가 있어 삭제할 수 없습니다. */ @@ -2213,7 +2213,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; /** @description 공연 정보를 찾을 수 없습니다. */ @@ -2222,7 +2222,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2242,7 +2242,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseMakerPerformanceResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseMakerPerformanceResponse"]; }; }; /** @description 회원 정보를 찾을 수 없습니다. */ @@ -2251,7 +2251,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2273,7 +2273,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponsePerformanceDetailResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponsePerformanceDetailResponse"]; }; }; /** @description 공연 정보를 찾을 수 없습니다. */ @@ -2282,7 +2282,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2304,7 +2304,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseBookingPerformanceDetailResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseBookingPerformanceDetailResponse"]; }; }; /** @description 공연 정보를 찾을 수 없습니다. */ @@ -2313,7 +2313,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2335,7 +2335,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseHomeFindAllResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseHomeFindAllResponse"]; }; }; }; @@ -2360,7 +2360,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponsePerformanceMakerPresignedUrlFindAllResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponsePerformanceMakerPresignedUrlFindAllResponse"]; }; }; /** @description S3 PreSigned url을 받아오기에 실패했습니다. */ @@ -2369,7 +2369,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2389,7 +2389,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseListMemberBookingRetrieveResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseListMemberBookingRetrieveResponse"]; }; }; /** @description 입력하신 정보와 일치하는 예매 내역이 없습니다. 확인 후 다시 조회해주세요. */ @@ -2398,7 +2398,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2418,7 +2418,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseUserFindAllResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseUserFindAllResponse"]; }; }; /** @description 회원이 없습니다 */ @@ -2427,7 +2427,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2449,7 +2449,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseCarouselPresignedUrlFindAllResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseCarouselPresignedUrlFindAllResponse"]; }; }; /** @description 회원이 없습니다. */ @@ -2458,7 +2458,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; }; @@ -2480,7 +2480,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["SuccessResponseBannerPresignedUrlFindResponse"]; + "application/json;charset=UTF-8": components["schemas"]["SuccessResponseBannerPresignedUrlFindResponse"]; }; }; /** @description 회원이 없습니다. */ @@ -2489,7 +2489,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["ErrorResponse"]; + "application/json;charset=UTF-8": components["schemas"]["ErrorResponse"]; }; }; };