๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿฟ Project

[SidePJ] Issue-Cracker 4๋ถ€ - v0.1.0๐Ÿช ๋ฐ๊ตด๋ฐ๊ตด ๊ตด๋Ÿฌ๊ฐ€๋Š” ์ด์Šˆํฌ๋ž˜์ปค

by Tamii 2021. 7. 10.
๋ฐ˜์‘ํ˜•

 ๐Ÿ“Œ Component ๊ณต์œ ์™€ ์ƒํƒœ๊ด€๋ฆฌ

 

FrontState ์™€ BackEnd State๋ฅผ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ•ด์•ผ ํ• ๊นŒ?

 

์ฒ˜์Œ์—๋Š” ๋‹ด๋‹น์ž์™€ ๋ ˆ์ด๋ธ”๋งˆ์ผ์Šคํ†ค ์„ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ•ด์•ผ ํ• ๊นŒ ๊ณ ๋ฏผํ–ˆ๋‹ค.

1) ๋ˆŒ๋Ÿฌ์„œ ์ถ”๊ฐ€ํ•  ๋•Œ๋งˆ๋‹ค ์„œ๋ฒ„์— ์ „์†ก

2) FrontState์—์„œ ๊ด€๋ฆฌํ•˜๊ณ  ์ด์Šˆ ์ž‘์„ฑ์„ ๋ˆ„๋ฅผ ๋•Œ ์ „์†ก

GitHub์˜ ๋‹ด๋‹น์ž ์ƒํƒœ๊ด€๋ฆฌ๋Š”?

GitHub์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ณด์•˜๋Š๋ฐ ๋ˆ„๋ฅด๋•Œ๋งˆ๋‹ค ์„œ๋ฒ„๋กœ ์ „์†ก์ด ๋˜๋Š”๊ฑด๊ฐ€? ํ–ˆ์ง€๋งŒ ๋กœ์น˜์˜ ๋ง๋กœ๋Š” ์•„ํด๋กœ ์„œ๋ฒ„๊ฐ™์ด ๋™์ž‘ํ•˜๋Š”๊ฒƒ์ด์ง€ ์‹ค์ œ๋กœ ๊ทธ๋Ÿด๋ฆฌ๊ฐ€ ์—†์„ ๊ฒƒ์ด๋ผ ํ–ˆ๋‹ค. ใ…Žใ…Ž

 

๊ฒฐ๊ณผ์ ์œผ๋กœ 2๋ฒˆ์„ ์„ ํƒ

์ด์Šˆ ์ƒ์„ฑํ•˜๊ธฐ ์ „๊นŒ์ง€ ์ € ์„ธ๊ฐ€์ง€ ์ •๋ณด๋Š” ํ•„์š” ์—†๊ธฐ ๋•Œ๋ฌธ์— FrontState๋กœ ๊ด€๋ฆฌํ•˜๋‹ค ์ด์Šˆ ์ž‘์„ฑ ์‹œ ์ „์†กํ•œ๋Š”๊ฒƒ์ด ๊ฒฝ์ œ์ ์ด๋ผ ์ƒ๊ฐํ–ˆ๋‹ค.

 

 

 

์ฒ˜์Œ SideBar๋ฅผ ๋งŒ๋“ค ๋•Œ IssueAdd ์—์„œ ๋™์ž‘ํ•˜๊ฒŒ๋งŒ ๋งŒ๋“ค๋‹ค ๋ณด๋‹ˆ ์ƒํƒœ๊ฐ’์„ ๋‹ค ๊ฐ€์ง€๊ณ  ์žˆ๊ป˜ ๋˜์—‡๋‹ค.

IssueDetail์—์„œ๋„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ƒํƒœ๊ฐ’์„ ์ „๋ถ€ ๋นผ์„œ props๋กœ ์ „๋‹ฌ๋ฐ›์•„์„œ ๋ฟŒ๋ฆฌ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฐ”๊พธ์ž

 

 

IssueDetail ๊ณผ IssueAdd ์—์„œ ๋™์ผํ•˜๊ฒŒ SideBar๋ฅผ ๊ณต์œ ํ•˜๋ฉด์„œ

 

 

 


๐Ÿ“Œ Issue ์ƒ์„ธํŽ˜์ด์ง€ ์ƒํƒœ๊ด€๋ฆฌ ๋Œ€๊ฒฉ๋™ 

 

 

๊ธฐ์กด ์ฝ”๋“œ

IssueList์˜ Cell์„ ํด๋ฆญํ•˜๋ฉด Link To ๋กœ ํด๋ฆญํ•œ ํ•ด๋‹น data(state) ๋ฅผ ์ญ‰~~~~ ๋‚ด๋ ค์ฃผ๊ณ  SideBar์—์„œ ๋ณ€๊ฒฝํ•˜๋ฉด ๊ทธ ๊ฐ’์„ CheckedData๋กœ ๊ด€๋ฆฌํ•œ๋Š ๋ฐฉ์‹ 

Recoil์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ์™œ ๊ณ„์† ์ƒํƒœ๋ฅผ ๋‚ด๋ ค์ค˜์•ผ ํ•˜๋Š”์ง€ ์˜๋ฌธ์ด ๋“ค๊ธด ํ–ˆ๋‹ค.

 

IssueDetail

const IssueDetail = (): JSX.Element => {
  const { state } = useLocation<IssueDataProps>();
  const setCheckData = useSetRecoilState(dropCheckState);

  useEffect(() => {
    setCheckData({
      assignee: state.assignees,
      label: state.labels,
      milestone: [state.milestoneInfo],
    });
  }, [state]);

IssueList - IssueCell

const IssueCell = ({ issues }: { issues: IssueDataProps[] }): JSX.Element => {
  const decoded = decodedToken && useRecoilValue(decodedToken);
  const profileURL = decoded && decoded.profileImageUrl;

  const openIssue = getIssue(issues, 'OPEN');
  const closedIssue = getIssue(issues, 'CLOSED');

  return (
    <>
      {openIssue.map((issue) => {
        const {
          assignees,
          content,
          createdDateTime,
          issueId,
          labels,
          milestoneInfo,
          status,
          title,
          writer,
        } = issue;
        const elapsedTime = getElapsedTime(createdDateTime);
        return (
          <S.IssueCell key={uuidv4()}>
            <>
              <LeftBox>
                <CheckBoxes />
                <IssueCellContent>
                  <Link
                    to={{
                      pathname: `/main/issue-detail/${issueId}`,
                      state: issue,
                    }}
                  >

 

SideBar์˜ Dropdown ๋ฉ”๋‰ด ํด๋ฆญ ์‹œ ์ƒํƒœ ๊ด€๋ฆฌ

Recoil

export const dropCheckState = atom<dropCheckStateProps>({
  key: 'dropCheckState',
  default: {
    assignee: [],
    label: [],
    milestone: [],
  },
});

SideBar์—์„œ ์ฒดํฌํ•œ ์ •๋ณด๋“ค์€ ๋ฐ”๋กœ Recoil๋กœ ๋„ฃ์–ด์„œ ๊ด€๋ฆฌํ–ˆ๋‹ค.

 const handleClickAssignee = () => {

      setDropCheck({
        ...dropCheck,
        assignee: [...dropCheck.assignee, data],
      });

ํด๋ฆญ ์‹œ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ๋ณด์ด๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค.

 

 

 

 


 

๐Ÿ“Œ ํ™œ๊ธฐ์ฐฌ ์›”์š”์ผ์˜ ์ฝ”๋“œ ์ปจ๋ฒค์…˜ 

1) useRecoilValue ๋„ค์ด๋ฐ ๊ทœ์น™ ์„ค์ • 

๊ธฐ์กด ๋ฐฉ์‹

//SideBar
export const issueForm = selector({
  key: 'issueForm',
  get: async () => {
    const response = await fetch(U.FORM);
    const data = await response.json();
    return data;
  },
});

export const dropAssigneeState = atom({
  key: 'dropAssigneeState',
  default: false,
});

๊ธฐ์กด์—๋Š” useRecoilValue์˜ state์™€ ๋ถˆ๋Ÿฌ์™€์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ณ€์ˆ˜๊ฐ„์˜ ๊ทœ์น™์ด ์—†์—ˆ๋‹ค.  

์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ๋งˆ๋‹ค ๊ทธ๋•Œ๊ทธ๋•Œ ๋‹ค๋ฅด๊ฒŒ ์‚ฌ์šฉํ–ˆ์—ˆ๋Š๋ฐ, ํŒŒ์ผ์ด ๋งŽ์•„์ง€๊ณ  ๋ณต์žกํ•ด์ง€๋‹ˆ ๊ทธ ์•ˆ์— ๊ทœ์น™์ด ์—†์–ด์„œ ํ˜ผ๋ž€์Šค๋Ÿฌ์› ๊ณ  ๋„ค์ด๋ฐ๊ณ ๋ฏผ์ด ๊ธธ์–ด์กŒ๋‹ค..

 

 

์ปจ๋ฒค์…˜ ๋ณ€๊ฒฝ

useRecoilValue๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ณณ์€ ์ƒํƒœ์ด๋‹ˆ state๋ฅผ ๋ถ™์ด๊ณ  

๊ฐ€์ ธ์™€ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ์—์„  ์ด๋ฆ„ ๊ทธ๋Œ€๋กœ์—์„œ state๋ฅผ ๋นผ๊ธฐ! 

๊ฐ„๋‹จํ•œ ๊ทœ์น™์ด์ง€๋งŒ ๋ณ€๊ฒฝํ•˜๋Š”๋ฐ ์‹œ๊ฐ„์ด ๋งŽ์ด ์†Œ์š”๋˜์—ˆ๊ณ , ๋Œ€์‹ ์— ํ•ด๋‹น ํŒจํ„ด์„ ๋งŒ๋‚ซ์„๋•Œ ๊ณ ๋ฏผํ•˜๊ฑฐ๋‚˜ ํ—ท๊ฐˆ๋ฆฌ๋Š”๋ถ€๋ถ„์ด ์‚ฌ๋ผ์ ธ ๋ณด๋‹ค ์ง๊ด€์ ์ด๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๊ฒŒ ๋œ ์žฅ์ ์ด ์ƒ๊ฒผ๋‹ค.

 

2) import ์ปจ๋ฒค์…˜ ์„ค์ •

import๋ฅผ ๋งŽ์ดํ•˜๊ณ  ๊นŠ์ด๊ฐ€ ๊นŠ์–ด์งˆ ์ˆ˜๋ก ๊ทœ์น™์ด ํ•„์š”ํ•˜๋‹ค๊ณ  ํŒ๋‹จํ•ด, ์•„๋ž˜์™€ ๊ฐ™์ด import ์ˆœ์„œ๋ฅผ ์ •ํ–ˆ๋‹ค.

 

๊ธฐ๋ณธ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

component

์ƒํƒœ

์Šคํƒ€์ผ๊ด€๋ฆฌ

์ด๋กœ ์ธํ•œ ์žฅ์ ์€ ํŒŒ์ผ์„ ๋”ฑ ์—ด์—ˆ์„ ๋•Œ ์ง๊ด€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ํŒŒ์ผ๋“ค์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ 

๋‹ค๋งŒ ์ ˆ๋Œ€๊ฒฝ๋กœ๋ฅผ ์•„์ง ์„ค์ •ํ•˜์ง€ ์•Š์•„์„œ ../ ../ ์ด ๋‚จ๋ฐœํ•˜๋Š”๋ฐ ์ถ”๊ฐ€ํ•  ์˜ˆ์ •์ด๋‹ค.

 

๐Ÿ“ŒBE์™€  ๊ณต๋ถ€ํ•œ REST API ์™€ ์„ ์ • ์ด์œ ๋“ค

์–ด๋–ค data๋“ค์€ id๊ฐ€ number ์ด๊ณ  (๋‹จ์ˆœ idx๋Š๋‚Œ) 

์–ด๋–ค data๋“ค์€ id ๊ฐ€ string ์ด๋‹ค. 

number๋กœ ๋‹ค ๋งž์ถ”๋ฉด ๋˜๋Š”๋ฐ ์™œ ๊ตณ์ด string์ด ๋˜๋Š” ๋ณ€์ˆ˜๋“ค์—๊ฒ ๊ทธ๋ ‡๊ฒŒ ์„ค์ •ํ•˜์˜€์„๊นŒ?

 

๊ทธ ์ด์œ ๋Š” id ๊ฐ€ ์‹๋ณ„์ž ์ž์ฒด์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ!! 

comment data 

์‹ค์ œ comment(๋Œ“๊ธ€) data๋ฅผ ๋ณด๋ฉด 

๋Œ“๊ธ€์˜ id = 1 number

writer์˜ id = string์ธ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ๋Š๋ฐ, 

 

์ด๋Š” ๋Œ“๊ธ€์€ ์ž์ฒด๋งŒ์˜ ๊ณ ์œ ํ•œ ์ •๋ณด๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— idx๊ฐ™์€ ์ˆซ์ž๋ฅผ ํ•œ ๊ฒƒ์ด๊ณ  

writer์€ ์ด๋ฆ„์ด๋ผ๋Š” ์ž์‹ ์˜ ๊ณ ์œ ํ•œ ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— string์„ ํ•œ ๊ฒƒ! 

 

 

data์˜  ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ ์ •๋ง ๋งŽ์€ ๊ณ ๋ฏผ์„ ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๋Š๊ผˆ๋‹ค.

 

 

 

POST์™€ PUT์˜ ์ฐจ์ด์ ์€?

POST X ์ƒ์„ฑ Issue ์ถ”๊ฐ€ ์š”์ฒญํ•  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด data๊ฐ€ ์Œ“์ธ๋‹ค
PUT O ์ˆ˜์ • Issue ์ˆ˜์ • ์š”์ฒญํ•  ๋•Œ๋งˆ๋‹ค ์ด์ „์˜ data๋ฅผ ์—†์• ๊ณ  ๊ทธ ์ž๋ฆฌ๋ฅผ ๋Œ€์‹ ํ•œ๋‹ค
DELETE O - -  

PUT์˜ ์žฅ์ 

  • ๋ Œ๋”๋ง์œผ๋กœ ์ธํ•ด ์—ฌ๋Ÿฌ๋ฒˆ ํ˜ธ์ถœํ•ด๋„ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์žฅํ•œ๋‹ค.
  • ๋ถ€๋ถ„์ ์ธ ๋น„๋™๊ธฐ API ์š”์ฒญ์ด ์ „ํ•ด์ง„๋‹ค.

POST์˜ ์ฃผ์˜์ 

  • ์•ˆ์ „ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— disabled ๊ฐ™์€ UI ์š”์†Œ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
  • ๋ฌดํ•œ๋Œ€๋กœ ๋ Œ๋”๋ง ๋˜๊ณ  API์š”์ฒญ์ด ์—ฌ๋Ÿฌ๋ฒˆ ๋  ์ˆ˜ ์žˆ๋‹ค.

POSTMAN ๊ณต์œ  ๋ฐ ์‚ฌ์šฉ๋ฒ•

BE์˜ API๋ฅผ ํ™•์ธํ•˜๊ณ  ์‚ฌ์šฉํ•  ๋•Œ POSTMAN์„ ์‚ฌ์šฉํ•˜๋ฉด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ž์„ธํ•˜๊ฒŒ ์š”์ฒญ์„ ํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์—ฌํƒœ๊ป ๋งํฌ๋กœ๋งŒ ๊ณต์œ ํ•˜๋‹ค๊ฐ€ ๊ทธ๋ฃน์ด ๋˜์–ด์„œ ์‚ฌ์šฉํ–ˆ๋‹ค. ์ •๋ง ํŽธ๋ฆฌํ•ด์„œ ์•ž์œผ๋กœ ๋งˆ์ฃผํ•  ๋•Œ๋งˆ๋‹ค ์‚ฌ์šฉํ•  ๋ฐฉ๋ฒ•์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌ! 

1) import - Link

2) New - Environment 

 

์ •๋ฆฌ ๋งํฌ : https://github.com/ghojeong/issue-tracker/wiki/%EC%A0%95%EA%B8%B0%EB%AF%B8%ED%8C%85-%ED%9A%8C%EC%9D%98%EB%A1%9D


๐Ÿ“Œ TypeScrit ์—์„œ ๊ฐ์ฒด(jsonํƒ€์ž…)  ์ „๋‹ฌ ์‹œtype ์ง€์ •

๊ฐ™์€ ์ƒํ™ฉ ํ•ด๊ฒฐ ๊ณผ์ • :https://rrecoder.tistory.com/155

โ– ์›ํ•˜๋Š” ๋™์ž‘ 

LabelTabel์—์„œ labelList  ๋ฐฐ์—ด์„ map ๋Œ๋ฉฐ LabelCell ์ปดํฌ๋„ŒํŠธ๋ฅผ์—ฌ๋Ÿฌ๊ฐœ ๋ Œ๋”๋ง ํ•˜๋Š” ์ž‘์—…

์—ฌ๊ธฐ์„œ label์„ ๋„˜๊ฒจ์ค„ ๋•Œ label์˜ ํƒ€์ž…๋“ค์„ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค.

 

โ–  ์ด์ „ ์ฝ”๋“œ 

 

label ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ๊ฒผ๊ณ  

label = {
  backgroundColor: "#1E4174"
  description: "๋ผ๋ฒจ2 ์„ค๋ช…"
  id: 2
  textColor: "#FFFFF"
  title: "๋ผ๋ฒจ2 ์ œ๋ชฉ"
 }

LabelProps

export interface LabelProps {
  id: number;
  title: string;
  description: string;
  backgroundColor: string;
  textColor: string;
}

LabelProps๋Š” ์ด๋ ‡๊ฒŒ ๋”ฐ๋กœ ์ง€์ •ํ•ด ์คฌ๊ธฐ ๋•Œ๋ฌธ์— 

 

const LabelCell = ({ label }: LabelProps): JSX.Element => {
  return (

LabelTable

const LabelTable = (): JSX.Element => {
  const labelList = useRecoilValue(labelListState);
  const labelListArr = labelList?.labels;

  return (
    <IssueTableContainer>
      <LabelTableHeader />
      {labelListArr?.map((label: LabelProps) => (
        <LabelCell key={uuidv4()} {...{ label }} />
      ))}
    </IssueTableContainer>
  );
};

 

 

โ– ์ˆ˜์ • ์ฝ”๋“œ

๊ทธ๋ ‡๋‹ค๋ฉด json์„ ๊ฐ์ฒด๋ฅผ ์†์„ฑ์œผ๋กœ ๋„˜๊ธธ ๋•Œ ํƒ€์ž…์ง€์ •ํ•˜๋Š” ๋‘๊ฐ€์ง€ ๋ฐฉ๋ฒ•

 

1) ์†์„ฑ ์ž์ฒด๋ฅผ props ์ด๋ฆ„์œผ๋กœ ๋„˜๊ธด๋‹ค

interface LabelPropss {
  label: {
    id: number;
    title: string;
    description: string;
    backgroundColor: string;
    textColor: string;
  };
}

const LabelCell = ({ label }: LabelPropss): JSX.Element => {
  return ( //์ค‘๋žต

LabelPropss ๋ฅผ ๋ณด๋ฉด LabelCell์— ๋„˜๊ธธ props๋ช…์„ ๋ฏธ๋ฆฌ type์ง€์ •์„ ํ•ด์ฃผ๋ฉด ์ƒ๊ฐํ–ˆ๋˜ ๋Œ€๋กœ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋‹ค.

 

 

2) type ์„ ์–ธ ์‹œ props๋ช… ํ• ๋‹นํ•ด์ฃผ๊ธฐ

export interface LabelProps {
  id: number;
  title: string;
  description: string;
  backgroundColor: string;
  textColor: string;
}

const LabelCell = ({ label }: { label: LabelProps })
//์ค‘๋žต

type ์„ ์–ธ ์‹œ์— LableProps ๊ฐ€ label์ด๋ผ๋Š” ๊ฒƒ์„ ์ง€์ •ํ•˜๋ฉด์„œ ํƒ€์ž… ์ง€์ •์„ ํ•  ์ˆ˜ ์žˆ๋‹ค. 

 


๐Ÿ“Œ ๋ฒ„ํŠผ ์™ธ ์˜์—ญ ํด๋ฆญ ์‹œ ๋‹ซํžˆ๋Š” ๋กœ์ง ๊ตฌํ˜„

1) ref ๊ฑธ component๊ฐ€ ์˜๊ตฌ์ ์ด์ง€ ์•Š์€ ๋ฌธ์ œ 

ButtonBox

๊ธฐ์กด ์ฝ”๋“œ

์ผ๋‹จ ref๋ฅผ ๊ฑฐ๋Š” ๋ถ€๋ถ„์ด ์‚ผํ•ญ์—ฐ์‚ฐ์ž๋กœ ์ฒ˜๋ฆฌํ•ด๋†“์•˜๋”๋‹ˆ

 ๊ฐ€๋…์„ฑ๋„ ๋–จ์–ด์ง€๊ณ  ์กฐ๊ฑด์— ๋”ฐ๋ผ ref๊ฐ€ ์—†์„ ์ˆ˜ ๋„ ์žˆ์–ด์„œ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ–ˆ๋‹ค.

<ButtonBox ref={editRef}>
          {issueEditTitle ? (
            <TitleEditButton
              startIcon={<TitleEditIcon />}
              color="primary"
              onClick={handleClickCompleteButton}
            >
              <TextGroup
                type={T.SMALL}
                content={TT.EDIT_COMPLETE}
                color="#007AFF"
              />
            </TitleEditButton>
          ) : (
            <TitleEditButton
              startIcon={<TitleEditIcon />}
              color="primary"
              onClick={handleClickEditButton}
              id={'editButton'}
            >
              <TextGroup
                type={T.SMALL}
                content={TT.EDIT_TITLE}
                color="#007AFF"
              />
            </TitleEditButton>
          )}

 

๋ณ€๊ฒฝ ์ฝ”๋“œ

ButtonBox์— ๋“ค์–ด๊ฐ€์•ผ ํ•  Component๋ฅผ ๋ถ„๋ฆฌํ•œ ํ›„์—

Component ๋‚ด๋ถ€์—์„œ ์‚ผํ•ญ์—ฐ์‚ฐ์ž์— ์˜ํ•ด ๋‚˜๋‰˜๋„๋ก ํ–ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๋ฐ”๊ฟˆ์œผ๋กœ์จ ์–ด๋–ค ์กฐ๊ฑด์ด ์žˆ์–ด๋„ ButtonBox๋Š” ๊ณ ์ •๋˜์–ด ๋ Œ๋”๋ง ๋˜๋‹ˆ useRef ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

 <ButtonBox ref={editRef}>
          <IssueDetailHeaderButtonSection
            state={issueEditTitle}
            id={issueDetails?.issueId}
          />
          <IssueDetailIssueCloseButton callback={setIssueState} />
        </ButtonBox>
const IssueDetailHeaderButtonSection = ({
  state,
  id,
}: {
  state: boolean;
  id: number;
}): JSX.Element => {
  return (
    <>
      {state ? (
        <IssueDetailTitleEditCompleteButton {...{ id }} />
      ) : (
        <IssueDetailTitleEditButton />
      )}
    </>
  );
};

 

 

2) ๋ฒ„ํŠผ ์˜์—ญ ์™ธ ํด๋ฆญ์‹œ

event์˜ ํƒ€์ž… ๋ฌธ์ œ

๋ฒ„ํŠผ ์™ธ ์˜์—ญ์„ ๋ˆŒ๋ €์„ ์‹œ ํŽธ์ง‘ ์ฐฝ์ด ์ข…๋ฃŒ๋˜์–ด์•ผ ํ•œ๋‹ค.

ํ˜„์žฌ๋Š” ํŽธ์ง‘๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์•ผํ•จ ํŽธ์ง‘์ด ์ข…๋ฃŒ๋œ๋‹ค.

 useEffect(() => {
    const handleClickBody = (e: { target: HTMLInputElement }) => {
      if (editRef.current && !editRef.current.contains(e.target)) {
        setIssueEditTitle((prev) => !prev);
      }
      console.log(e.target);
      console.log('current', editRef.current);
      // setIssueEditTitle((prev) => !prev);
    };
    window.addEventListener('mousedown', handleClickBody);

    return () => window.removeEventListener('mousedown', handleClickBody);
  }, [editRef]);

๐Ÿ“Œ SideBar ์˜ ์ƒํƒœ ํด๋ฆญ ์‹œ ์„œ๋ฒ„์— PUT์š”์ฒญ ๋ณด๋‚ด๊ธฐ

์‚ฌ์ด๋“œ๋ฐ”์—์„œ ๋‹ด๋‹น์ž๋ฅผ ๋ฐ”๊พธ๊ณ  ์‚ฌ์ด๋“œ๋ฐ”Drop์„ ๋‹ซ์„๋•Œ ์„œ๋ฒ„์— PUT์š”์ฒญ์„ ๋ณด๋‚ด๋ฉฐ ์ˆ˜์ •ํ•˜๋Š” ๋กœ์ง

์ผ๋‹จ ์ฒซ๋ฒˆ์งธ ๊ณ ๋ฏผํ–ˆ๋˜ ๊ฒƒ์€ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ํƒ€์ด

 

 

1) SideBarDrop์—์„œ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค PUT ํ•  ๊ฒƒ์ธ์ง€

2) ๋ชจ๋‹ฌ์„ ๋‹ซ์•˜์„ ๋•Œ ๋ณด๋‚ผ๊ฒƒ์ธ์ง€ ์ธ๋ฐ 

์„œ๋ฒ„์— ์ฒดํฌํ• ๋•Œ๋งˆ๋‹ค ๋ณด๋‚ด๋ฉด ๋ชจ๋‹ฌ์„ ๋‹ซ์„ ๋•Œ ๋ณด๋‚ด๋Š” ๊ฒƒ๋ณด๋‹ค ํ›จ์”ฌ ๋งŽ์€ ์„œ๋ฒ„ ์š”์ฒญ์ด ๋ฐœ์ƒํ•  ๊ฒƒ์ด๋ผ๊ณ  ์˜ˆ์ƒํ•ด,

๋ผ์ฟค๊ณผ ๋ชจ๋‹ฌ์ด ๋‹ซํž ๋•Œ ์š”์ฒญํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค!

 

๋‹ด๋‹น์ž ํŽธ์ง‘ ์š”์ฒญ ์ฝ”๋“œ

const dropCloseHandler = (e: MouseEvent): void => {
      if (dropAssigneeElement.current?.contains(e.target as Node)) return;
      if (dropLabelElement.current?.contains(e.target as Node)) return;
      if (dropMilestoneElement.current?.contains(e.target as Node)) return;

      setIsDropAssignee((prev: boolean) => {
        const assigneeUrl =
          U.ISSUES + '/' + issueDetailId.issueId + '/assignees';
        if (prev)
          getPut(assigneeUrl, userToken, {
            assigneeIds: getValueInJson(checkedAssignee, 'id'),
          });
        return false;
      });

      setIsDropLabel(false);
      setIsDropMilestone(false);
    };

SideBar ์™ธ ์˜์—ญ์„ ๋ˆŒ๋ €์„ ๋•Œ์—๋Š” return 

Assignee SideBar DropDown์™ธ ์˜์—ญ์„ ํด๋ฆญํ•ด ๋‹ซํžˆ๋Š” ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ ํ•˜๋Š” ๋กœ์ง (setIsDropAssignee) ์—์„œ 

PUT ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์žˆ๋‹ค.

PUT์š”์ฒญ body ๊ฐ€ ์ฒดํฌ๋œ ๋‹ด๋‹น์ž๋“ค์˜ id๋งŒ ๋ฐฐ์—ด๋กœ ์ „์†กํ•˜๋Š” ํ˜•์‹์ด๊ธฐ ๋•Œ๋ฌธ์— ex) ['ink-0', 'Raccoon']

getValueInJson๋กœ id๋งŒ ๋ถˆ๋Ÿฌ์˜ค๋Š” ํ•จ์ˆ˜๋ฅผ ์จ์„œ body๋กœ ๋„ฃ์–ด์ค€๋‹ค.

 

๋งž์ดํ•œ ์–ด๋ ค์›€

ํ˜„์žฌ ์ด ํ•จ์ˆ˜๋Š” useEffect ๋‚ด์— ๋“ค์–ด์žˆ๋Š”๋ฐ,

๊ทธ dependencyArray์— []๋นˆ๋ฐฐ์—ด ๋„ฃ์Œ -> ์ฒ˜์Œ ํ•œ๋ฒˆ๋งŒ ๋™์ž‘ -> ์ฒดํฌํ•œ ๋‹ด๋‹น์ž(checkedAssignee๊ฐ€ ์ดˆ๊ธฐ๊ฐ’) 0์œผ๋กœ ์š”์ฒญ 

์ด ๋กœ์ง์ด ์‹คํ–‰๋˜์„œ ์ž๊พธ๋งŒ ์š”์ฒญ์„ ํ•˜๋ฉด ๋‹ด๋‹น์ž๊ฐ€ ์‚ฌ๋ผ์ง€๋Š” ์–ด๋ ค์›€์„ ๊ฒช์—ˆ์ง€๋งŒ, 

 

dependencyArray์— chekedData( ์ฒดํฌํ•œ ๋‹ด๋‹น์ž ์™ธ ๋ ˆ์ด๋ธ”๊ณผ ๋งˆ์ผ์Šคํ†ค๋„ ํฌํ•จ)์„ ๋„ฃ์–ด์คŒ์œผ๋กœ์จ 

์‚ฌ์šฉ์ž๊ฐ€ ์ฒดํฌํ•œ ๋ฐ์ดํ„ฐ๋“ค์ด ๋ฐ”๋€”๋•Œ๋งˆ๋‹ค ๋‹ค์‹œ ์‹คํ–‰๋˜๋„๋ก ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ˆ˜์ • ์š”์ฒญ์— ์„ฑ๊ณตํ–ˆ๋‹ค.

 


๐Ÿ“Œ ํ˜„์žฌ๊นŒ์ง€์˜ ์ƒํ™ฉ

๊นƒํ—ˆ๋ธŒ ๋กœ๊ทธ์ธ์„ ํ•œ ํ›„์— ๋งŒ๋“ค๊ณ  ์‹ถ์€ ์ด์Šˆ๋“ค์„ ์ƒ์„ฑ, ํŽธ์ง‘ํ•˜๊ณ  

๋ ˆ์ด๋ธ” ๋งˆ์ผ์Šคํ†ค๋„๋งŒ๋“ค์–ด์„œ ๋‹ฌ ์ˆ˜ ์žˆ๋‹ค.

 

 

 

๋กœ๊ทธ์ธ  , ์ด์Šˆ๋ฆฌ์ŠคํŠธ (๋ฉ”์ธ ํ™”๋ฉด)

 

์ด์Šˆ ์ƒ์„ฑ ์ˆ˜์ • ์‚ญ์ œ

 

๋ ˆ์ด๋ธ” ์ƒ์„ฑ ์ˆ˜์ • ์‚ญ์ œ

 

 

๋งˆ์ผ์Šคํ†ค ์ƒ์„ฑ ์ˆ˜์ • ์‚ญ์ œ

 

 

 

 

์‹œ์—ฐ gif

๋กœ๊ทธ์ธ๊ณผ ์ด์Šˆ ์ƒ์„ฑ

 

๋งˆ์ผ์Šคํ†ค ์ƒ์„ฑ, ๋ ˆ์ด๋ธ” ์ƒ์„ฑ

 

 

๐Ÿ“Œ ์งง์€ ํšŒ๊ณ 

 

์ฒ˜์Œ ๋งŒ๋“ค ๋•Œ ์ƒํƒœ๊ด€๋ฆฌ๋•Œ๋ฌธ์— ์–ด์ง€๋Ÿฝ๊ณ  ํž˜๋“ค์—ˆ๋Š”๋ฐ 

์ •๋ง ์ž˜ ๋™์ž‘ํ•˜๋Š” ๊ฑธ ๋ณด๋‹ˆ ๋„ˆ๋ฌด ์‹ ๊ธฐํ•˜๊ณ  ์žฌ๋ฐŒ์–ด์„œ ๊ณ„์† ํด๋ฆญํ•˜๊ณ  ๋งŒ์ง€๊ณ  ์žˆ๋‹ค. 

์ง€๊ธˆ์€ ๋ฒ„์ „1์ด๊ณ  ์•„์ง ํ•ด๊ฒฐํ•ด์•ผํ•˜๋Š”๊ฒŒ ์‚ฐ๋”๋ฏธ์ง€๋งŒ, ์ผ๋‹จ์€ ์ œํ•œ๋œ ๋ฒ”์œ„ ๋‚ด์—์„œ ๋™์ž‘์ด ๊ฐ€๋Šฅํ•˜๋‹ค!!!! ๋„ˆ๋ฌด ํ–‰๋ณตํ•ด..

 

๋ฌผ๋ก  ์—ฌ๊ธฐ์„œ ๋ฉˆ์ถœ ์ƒ๊ฐ์€ ์—†๊ณ , ๋‚จ์€ Task๋“ค์„ ์ง„ํ–‰ํ•  ์˜ˆ์ •!

 

 

๋Œ“๊ธ€