apex 클래스를 만들고 lwc 임시 페이지를 만들어 데이터를 불러오는데

admin 상태에서는 데이터가 잘 불러와지지만

partner user 상태에서는 게시판에 모든 내용이 불러와지지 않았다.

 

이상하게도 호출부터가 문제였는데

어드민으로 접속할 때는 호출이 정상 작동하지만

어드민이 아닌 파트너 유저로 페이지에 접속할 경우

DML 관련 메서드 실행 자체 로그가 출력되지 않았다.

 

일단 빌더 페이지에 출력하기 위해서

하단의 코드를 메타 부분에 넣어서 어드민으로는 볼 수 있는데

롤 부분을 쿼리를 날릴 때 인지하지 못하는 것 같았다.

<masterLabel>BoardPostPage</masterLabel>
    <targets>
      <target>lightningCommunity__Page</target>
      <target>lightningCommunity__Default</target>
    </targets>
</LightningComponentBundle>

 

자러 가기 전 질문을 남기고 회고, 일기를 쓰려고 하는데

외국인들에게 질문을 남기는 사이에 한국 세일즈포스방에서 답변을 받을 수 있었다.

 

문제의 해결과는 관련없지만

디버그를 할 수 없는 문제는 계정 등록의 문제였는데

생각해보면 다른 계정에서 요청한 내용을 관리자에게 찍어줄 리가 없었다.

 

만약 그렇게 되면 수천명이 사용하는 사이트의 경우

관리자는 디버깅을 제대로 진행하기도 어려울 것이다.

 

문제를 해결하기 위해 setup의 Debug Logs에 들어가

해당 계정을 User Trace Flags에 등록했고 디버깅을 시작할 수 있었다.

 

하지만 디버깅을 시작했다고 해도 제대로 된 답변을 볼 수는 없었는데

아래의 내용이 내가 볼 수 있는 데이터의 전부였다.

57.0 APEX_CODE,DEBUG;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;NBA,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;WAVE,INFO;WORKFLOW,INFO
21:18:48.0 (263554)|USER_INFO|[EXTERNAL]|0055i00000A4fXv|rgc0582@gmail.com|(GMT+09:00) Korean Standard Time (Asia/Seoul)|GMT+09:00
21:18:48.0 (316900)|EXECUTION_STARTED
21:18:48.0 (324011)|CODE_UNIT_STARTED|[EXTERNAL]|apex://BoardPostController/ACTION$getBoardPosts
21:18:48.0 (734119)|CODE_UNIT_FINISHED|apex://BoardPostController/ACTION$getBoardPosts
21:18:48.0 (745423)|EXECUTION_FINISHED

 

 

답답한 마음에 모든 로그 세팅을 최대치까지 출력하게 만들고 확인하니

아래와 같이 조금 더 힌트를 얻을 수 있었는데

57.0 APEX_CODE,FINEST;APEX_PROFILING,FINEST;CALLOUT,FINEST;DB,FINEST;NBA,FINE;SYSTEM,FINE;VALIDATION,INFO;VISUALFORCE,FINER;WAVE,FINEST;WORKFLOW,FINER
21:24:31.0 (691841)|USER_INFO|[EXTERNAL]|0055i00000A4fXv|rgc0582@gmail.com|(GMT+09:00) Korean Standard Time (Asia/Seoul)|GMT+09:00
21:24:31.0 (766566)|EXECUTION_STARTED
21:24:31.0 (777448)|CODE_UNIT_STARTED|[EXTERNAL]|apex://BoardPostController/ACTION$getBoardPosts
21:24:31.0 (1285994)|CODE_UNIT_FINISHED|apex://BoardPostController/ACTION$getBoardPosts
21:24:31.0 (1298877)|EXECUTION_FINISHED
21:24:31.1 (1910907)|CUMULATIVE_PROFILING_BEGIN
21:24:31.1 (1910907)|CUMULATIVE_PROFILING|No profiling information for SOQL operations
21:24:31.1 (1910907)|CUMULATIVE_PROFILING|No profiling information for SOSL operations
21:24:31.1 (1910907)|CUMULATIVE_PROFILING|No profiling information for DML operations
21:24:31.1 (1910907)|CUMULATIVE_PROFILING|No profiling information for method invocations
21:24:31.1 (1910907)|CUMULATIVE_PROFILING_END

No profiling information for SOQL operations 부분을 보고 뭔가 이거라는 생각이 들었고

해당 문제를 다시 검색해봤다.

 

대부분 쓸대 없는 내용이 많았지만

운 좋게 한명이 비슷한 경험을 했는데 동료 중 하나가 해당 액세스를 실수로 전체 제거했다는 내용이었고

해당 글을 보고 쿼리가 문제가 아니고 클래스가 문제였고

해당 클래스에 대한 액세스 권한을 주지 않았다는 사실을 알았다.

(여태 클래스 엑세스를 줄 일이 한번도 없어서 줘야 하는지도 몰랐다)

 

어쩐지 메서드 실행 자체가 되지 않는 문제가 발생하더니

결론적으로는 메서드는 커녕 해당 클래스 접근 권한이 없었던 것이었고

해당 권한을 프로필에 들어가 Enabled Apex Class Access 부분에서

생성한 클래스의 권한을 부여하니 정상적으로 작동하는 것을 볼 수 있었다.

 

기분이 좋아서 까먹고 넘어갈 뻔 했지만

해당 내용을 정리하면서 VIP가 아닌 Partner에게도 권한을 부여해서

시연 때 에러가 생길 수 있는 문제를 차단할 수 있었다.

 

디버그 사소한 문제라고 생각했지만

문제를 해결할 수 있는 결정적인 키를 제공할 수 있는 부분이기 때문에

디버깅을 할 수 없는 경우 왜 안되는지에 대해 고민해보는 것이 중요하다는 교훈을 얻었다.

 

 

 

 

 

(1).백준 8371번 Dyslexia는 각 문자를 비교해 다른 점이 몇번이나 발견되는지를 출력하는 문제였다.

 

각 문자의 길이가 같음이 보장되기 때문에

간단하게 첫 번째 문장의 길이까지의 인덱스를 모두 비교하며

서로 다를 경우 count를 1씩 증가시킨 다음 최종적으로 count를 출력하는 방식으로 해결했다.

const input = `21
JASIOJESTDYSLEKTYKIEM
JAsIOJSSTDXSIEKTYKLEM`.split('\n')

let count = 0

for(let i = 0 ; i < input[1].length ; i++){
    if(input[1][i] !== input[2][i]){
        count++
    }
}

console.log(count)

'회고' 카테고리의 다른 글

[수습일지] - 66  (0) 2023.05.31
[수습일지] - 65  (0) 2023.05.30
[수습일지] - 63(주말)  (0) 2023.05.28
[수습일지] - 62(주말)  (0) 2023.05.27
[수습일지] - 61  (0) 2023.05.26

오늘은 물리적으로 차단되었을 경우에 대비하려고 했으나

구현한 코드는 발주 직전 재전송을 통해 확인했기 때문에

적용하기가 애매한 느낌이 들었다.

 

억지로 적용을 해본 뒤

해당 코드에 대한 리뷰를 받고 리펙토링을 진행하고

배치 작업을 진행한 다음 번재에 참여하느라 일찍 퇴근했다.

 

 

 

 

 

(1).백준 16479번 컵라면 측정하기는 해당 밑변과 높이가 주어졌을 때

해당 빗변의 제곱을 구하는 문제였기 때문에 

첫 번째 줄의 값을 제곱한 다음 a-b의 값의 제곱을 빼면 되는 문제였다,.

const input = `15
13 6`.split('\n')
const [a,b] = input[1].split(' ').map(Number)
console.log(Number(input[0])**2 - (a/2 - b/2)**2)

'회고' 카테고리의 다른 글

[수습일지] - 56(주말)  (0) 2023.05.21
[수습일지] - 55(주말)  (0) 2023.05.20
[수습일지] - 53  (0) 2023.05.18
[수습일지] - 52  (0) 2023.05.17
[수습일지] - 51  (0) 2023.05.16

일정을 정리하는 것만으로도 30분이나 사용했는데

세부적인 디테일을 정하다보니 그렇게 오래 지난 것 같다.

 

일단 ppt는 미리캔버스에서 괜찮아 보이는 것으로 선택한 다음

스케쥴러 확인을 진행했는데 당황스럽게도 4개 중 1개는 되지 않았고

원인을 파악해보니 401에러가 출력되는 것을 보니 인증 문제 같아서

다시 인증을 진행해주고 개별 테스트를 하니 정상 진행되었고

스케줄러로 예약 후 4개 모두 잘 들어간 모습을 보고 00, 15, 30, 45분으로 예약을 하고 마무리했다.

 

스케줄 예약 관련 클래스의 주석을 수정하려고 했는데

이미 스케줄을 걸어서 수정이 되지 않았고

스케줄 관련 주석만 문제라면 상관없지만 다른 메서드도 호출해서 작동하기 때문에

예약을 취소하지 않으면 전반적인 수정 과정이 막힐 것 같았기 때문에

어쩔 수 없이 예약된 스케줄들을 전부 취소할 수 밖에 없었다.

 

모든 내용을 ppt 작성 전 핵심 부분을 요약하니 아래와 같이 정리됐다.

초반 설명하면 좋은 내용 1.생성했던 필드와 사용한 필드(Stock__c(과제), ParentId__c, Family, ProductCode, Name, Id, LastModifiedDate)

2.B org와 4개 org 연결된 모습

3.사용된 클래스와 메서드

 

기능

1.동기화 확인 가능한 상품 페이지(생산 회사들의 재고를 유통 회사에서 받는다는 입장)

2.부분 동기화(전체 상품 동기화가 부담될 수 있어서)

해당 org 재고 확인 페이지 상품명, index, modifiedDate

3.4개 org 재고 확인 spa(유통회사가 1개와 연결되진 않았을 것 같아서)

4.스케줄 기능(과제)

5.전체 동기화(과제)

6.선택 내용 확인을 위한 선택 목록 테이블

7.상품검색

8.정렬

9.named credential

10.호버링

 

피드백 내용 추가

 

슈팅

1.5월 4일 pmd 미작동 원인이 경로상 한글(계정명)이 있음을 확인 후 해결

2.teams 레지스터 변경으로 인한 접속 오류 -> %appdata%\Microsoft\Teams 내부 파일 모두 삭제 후 재로그인

3.데이터 타입 처리 -> ()로 타입을 알려줘도 믿지 않아서 걸쳐서 처리한 내용

4.직렬직렬화 문제 => try catch로 이중 역직렬화 블레이크

5.직렬직렬화 문제 => rest api에서 객체가 아닌 JSON.serialize를 통해 사용한 문제 제로

6.체크박스 쿼리셀렉터 대안 찾기 => 우회해서 배열에 값 넣기 파이

7.named credential => null 문제 외국 도움? 이온

8.Access Restricted for API Only Users => vscode open browser 사용(부 관리자 등 설정 주의해야 함)

9.rest api 처리 안되는 문제 => mock 데이터 사용

10.cyclomatic complexity 문제 => wrapper 사용으로 부담 떠넘기기

11.id가 올바른 값이 아니라며 터지는 문제 => id 우회용 object 생성 후 삭제 천

12.wire에 값을 넣는 문제 => 일반, 객체형태 (컨스트럭터 1, wire2, 렌더xx등 순서)

(@wire(getProducts, {family : '$family', key:'$key'}) 등 여러개 가능)

(@wire(getProducts, {strObj : '$object'})

get object(){

return {family : this.showTemplate, searchKey : this.searchKey, order : this.order, sort:this.sort}

}

13.객체로 들어간 object 형태 변환 문제 => 형변환 코드 넣기

 

ppt를 열심히 작성하고 발표를 잘 마무리할 수 있었지만

조금 슈팅 부분이 지나치게 많고 과제와 연관되지 않은 느낌이 드는 것도 있었기 때문에

다음에는 조금 중요한 부분만 진행해야 할 것 같다.

 

 

 

이전 과제 피드백은 사실

원화표시가 null일 경우 company info에서 나오지 않기 때문에 설정이 필요하다는 부분이었다.

 

 

 

(1).백준 17174번 전체 계산 횟수는 b진법과 유사한 느낌으로 접근하게 되는데

숫자가 해당 자릿수만큼 되면 올려주고 그 자리에서도 b만큼 차면 다시 자릿수를 올려서

총 몇 번의 숫자 카운팅을 하는지를 묻는 듯한 문제였다.

 

현재 숫자를 더해준 다음 다음 자릿수로 넘어갈 숫자를 Math.floor로 구했고

해당 값을 다시 while을 통해 반복시키며 덧셈을 한 다음 count를 출력했다.

let [a, b] = '100 8'.split(' ').map(Number)
let count = 0

while(a > 0){
    count += a
    a = Math.floor(a/b)
}

console.log(count)

'회고' 카테고리의 다른 글

[수습일지] - 55(주말)  (0) 2023.05.20
[수습일지] - 54  (0) 2023.05.19
[수습일지] - 52  (0) 2023.05.17
[수습일지] - 51  (0) 2023.05.16
[수습일지] - 50  (0) 2023.05.15

오늘도 밤 사이에 외국인에게 제보를 받았기 때문에

어제 포기했던 다중 wire params 문제를 해결해보기로 했다.

 

wire는 외국인이 제시한 방식대로 해도 제대로 전달되지 않았지만

문득 새로운 방식이 떠올라서 적용하니 코드가 정상적으로 전달되는 것을 볼 수 있었다

@wire(getProducts, {strObj : '$object'})
  wiredRecord(result) {
      this.wiredData = result;
      if (result.data) {
          this.productList = JSON.parse(result.data);
      } else if (result.error) {
          console.log(result.error);
          this.accounts = undefined;
      }
  }
get object(){
    return {family : this.showTemplate, searchKey : this.searchKey, order : this.order, sort:this.sort}
  }

constructor() {
    super();
    this.showTemplate = 'leather';
    this.searchKey = '';
    this.order = 'ASC';
    this.sort = 'Name';
  }

 

하지만 apex는 죽어라 말을 듣지 않는 녀석이기 때문에

js에서처럼 쉽게 obj.xx나 obj[’xx’]형태로 접근할 수 없었고

이게 object 내부에 String이 들어있는 형태라고 반복해서 알려줘야만 했다.

 

간신히 아래와 같이 Object에 있는 값을 사용할 수 있었지만

이번에는 PMD 복잡도가 메서드와 클래스에 터져서

4개의 파라미터를 보내서 1개의 경고를 받았는데

5시간 걸려서 해결하니 3개의 경고가 추가된 상황이 되어버렸다.

if (strObj  instanceof Map<Object, Object>) {
	Map<Object, Object> tempMap = (Map<Object, Object>) a;
	Map<String, Object> dataSet = new Map<String, Object>();
	for (Object key : tempMap.keySet()) {
	dataSet.put(String.valueOf(key), tempMap.get(key));
	}
}

 

갑작스럽게 10시 45분쯤 자체 리뷰를 하자는 이야기가 나와서

11시에 클라우디에서 리뷰를 진행하기로 했다.

 

복잡도로 인한 경고 3개를 제외하고 현재 pmd, test가 완료된 상황이기 때문에

조금 편하게 참여했지만 LWC로 기능을 이것저것 만들어서 그런지

오히려 개선할 부분을 더 많이 들었고 아래와 같은 피드백을 받았다.

 

1.필드에 스톡을 넣어서 조금 편하게 수정하기

2.변동사항이 없을 때는 초록색이 아니고 회색으로 보여주기

3.정렬 버튼이 있으면 좋겠다

4.전체 상품 볼 수 있는 페이지가 있으면 좋겠다.

5.동기화 버튼 이름 바꾸기

6.선택된 org 버튼 brand? 부분 바꾸기

7.상품을 눌렀을 때 상품 레코드 페이지로 가면 좋겠다

8.No.제품 번호로 바꾸면 좋겠다.

9.테이블 바닥 안된 부분

10.페이지 수정한 날짜를 보고 최신 데이터인지 궁금할 것 같다

 

피드백보다 일단 복잡도 문제를 해결하기 위해

열심히 코드들을 살펴보며 어디를 쪼개야 하나 고민했지만

문득 ‘이 데이터 처리도 wrapper로 감싸주면 복잡도를 떠넘길 수 있지 않나?’라는 생각이 들었고

아래와 같은 메서드를 작성해 추가 테스트코드 작성과 메서드, 클래스 분리를 피할 수 있었다.

public class ParamsWrap {
        public String family;
        public String searchKey;
        public String ordering;
        public String sortKey;
    
        /**
        * @description : 복잡도 감소용 wrapper
        * @author Yohan | 2023.05.17
        * @param a
        **/
        public ParamsWrap(Object a) {
            if (a instanceof Map<Object, Object>) {
                Map<Object, Object> tempMap = (Map<Object, Object>) a;
                Map<String, Object> dataSet = new Map<String, Object>();
                for (Object key : tempMap.keySet()) {
                    dataSet.put(String.valueOf(key), tempMap.get(key));
                }
                
                this.family = (String) dataSet.get('family');
                this.searchKey = (String) dataSet.get('searchKey');
                this.ordering = (String) dataSet.get('order');
                this.sortKey = (String) dataSet.get('sort');
            }
        }
    }

 

급한 불(메서드 작성 및 테스트코드 작성 완료)은 모두 껐기 때문에

다시 피드백 진압을 시작했다.

 

10개의 피드백을 보니 아래와 같은 내용이었고

 

1.프로덕트 일반 필드에 스톡 넣어서 편하게 수정

2.변동사항 없음 초록색이 아닌 회색 띄우기

3.정렬 버튼 추가

4.전체 상품 페이지 추가(중요 내부 포함)

5.동기화 버튼 이름 변경(중요 내부 포함)

6.선택된 org 버튼 brand? 부분 바꾸기(중요 내부 포함)

7.상품을 눌렀을 때 상품 레코드 페이지로 가면 좋겠다

8.No.제품 번호로 바꾸면 좋겠다.

9.테이블 바닥 안된 부분

#10.페이지 수정한 날짜를 보고 최신 데이터인지 궁금할 것 같다.(궁금하게 두기)

 

정리해보면 일반 6개와 페이지 세부 변경에 포함되는 나머지로 구분할 수 있었다.

1.프로덕트 일반 필드에 스톡 넣어서 편하게 수정

2.변동사항 없음 초록색이 아닌 회색 띄우기

3.정렬 버튼 추가

7.상품을 눌렀을 때 상품 레코드 페이지로 가면 좋겠다

8.No.제품 번호로 바꾸면 좋겠다.

9.테이블 바닥 안된 부분

추가.하이퍼링크 추가로 상품 확인하기

(하이퍼 링크용 주소 https://mindful-otter-qmf3eu-dev-ed.trailblaze.lightning.force.com/lightning/r/Product2/{ProductId}/view)

 

@중요 5개 페이지 세부사항

-페이지별 버튼 이름 바꾸기

-페이지별 버튼 브랜드 변경

-전체 동기화 이름 변경

-전체상품조회 동기화 함수 추가

-전체상품조회 선택 상품 동기화 함수 추가

-각 css파일 적용

 

여기에 org3개 생성 및 연동, 상품추가 등의 작업과

ppt작성, ppt 내부에 이전 과제 피드백 작성 등을 추가해야 하는데

ppr 관련은 내일 처리한다고 잡고 오늘의 작업 순서를 정해보면

 

org 3개 생성 → 연결 → 상품생성(product code 추가)

피드백 7개 먼저 적용

각 페이지별 적용사항 6개 적용

 

일정은 다음과 같다

~16:00 작업 순서 정리 완료 16:17 (과제 일지 정리로 인한 지연)

~16:40 로즈마리글라스 연결, 상품생성

~17:10 덕은철강 연결, 상품생성

~17:30 아낌없이주는나무 연결, 상품생성

~17:50 정렬 버튼 추가로 인한 시각화

~18:00 변동사항 없을 경우 toast 색 회색으로 변경

~18:20 No. index 대신 상품코드로 변경

~18:30 테이블 바닥 사이즈 고정

~19:00 상품을 누를 경우 상품 레코드 페이지로 이동 희망(상품명 A태그)

~20:00 저녁식사(먹는 사람 없으면 대충 먹기)

~20:10 페이지별 버튼 이름 바꾸기(xx동기화) 및 브랜드 변경

~20:20 전체 동기화 페이지 조정

~20:50 전체 동기화 함수 추가 및 적용

~21:20 전체 페이지 선택 동기화 함수 추가 및 적용

~21:30 각 페이지별 css파일 적용으로 인한 hovering 적용하기

 

첫 연결을 진행하는데 이번에도full - full 설정으로는 제대로 진행되지 않았기 때문에

모든 항목을 다 선택하니 정상적으로 넘어가는 모습을 볼 수 있었다.

 

연결 후 모든 상품을 제대로 넣었지만 되지 않아서 로그만 계속 찍고 있었는데

양쪽에서 디버그는 찍히지만 제대로 소통하지 않아서 뭔가 했는데

10분이 걸린다는 경고가 아마 이것과 관련있을지도 모르겠다.

3분에 등록한 데이터를 12분에 간신히 받아볼 수 있었다

 

어쨌건 동기화 및 내용이 제대로 들어간 것을 확인했지만

예시로 남겨둔 leather01부분이 마음에 안들어서 그 부분 먼저 처리하기로 했다.

 

leather01부분을 제거하기 위해서는 A, C org에서 주는 데이터에 ProductCode를 보내야 했고

각자 디플로이 후 받아온 데이터에서도 입력하게 해야 했는데

wrapper도 수정해야 했고 전반적으로 관여하는 느낌이었다.

 

productCode로도 정렬을 했으면 좋겠지만 불가능한데 이건 나중에 js로 처리하던지 버려야곘다.

세번째 org는 생각보다 빨리 처리할 수 있었지만

10분 가까이 인증이 진행되지 않는 대기시간이 있었다.

 

아무래도 초반에 막혀서 포기했던 부분들이 10분 기다려야 한다는 사실을 잘 모르고

이런저런 수정만 하다가 다른 방법을 찾아 떠났던 것 같다.

 

다시 네번째 org를 생성하가 아래의 메세지를 보게 됐는데

"error=invalid_client_id&error_description=client%20identifier%20invalid"이번에는 당황하지 않고 wood에 넣을 상품들을 확인하고 api code deploy를 진행했다.

 

정렬 버튼을 진행하는데 조건문을 넣을 수 없어서 수제로 비교하는 함수를 만들어서 각자 넣어줬는데

생각처럼 잘 작동하지 않고 자꾸 오름차순 정렬에 고정되어버렸다.

 

한참 원인을 찾으면서 로직을 변경하던 도중 웃긴 포인트를 발견했는데

값을 가져오는데 화살표가 붙어있다

 

아래와 같이 상품명↑로 값이 변했기 때문에 인식하지 못했고

현재 선택되지 않은 목록은 이름이 정상적이기 때문에 변화가 가능했던 것이었다.

 

아래와 같은 코드를 통해 문제를 해결했다.

if(sortTargetObj[this.sort] === sortTarget || sortTargetObj[this.sort] + '↑' === sortTarget || sortTargetObj[this.sort] + '↓' === sortTarget)

 

toast 색 변경은 간단하게 해결할 수 있었고

상품 코드로 변경은 기존 제품들의 상품 코드만 추가해야 하는 것이 아니라

업데이트를 진행해줘야 했지만

업데이트 진행 기준이 stock을 바탕으로 하기 때문에

결과적으로 모든 제품의 stock을 바꾸고 업데이트를 한 다음에 정상 작동이 되는 것을 볼 수 있었다.

 

테이블 바닥 사이즈는 height로는 변경이 되지 않았는데

자기만의 height 기준이 따로 있는 것 같았고

max-height으로 최대치 제한이 먹히고 min-height로 최소치 제한이 먹히는 것을 보면

여기서 적용한 css가 먼저 들어간 다음 라이트닝 클래스에서 css를 최후에 먹이는 느낌이었다.

다행히 max, min으로 두개를 다 섞어서 특정 수치로 고정할 수 있었기 때문에 높이 변경을 할 수 있었는데

이전에 vh, %로 해도 될 때가 있고 안될 때가 있던 것도 아마 이런 문제 때문이었던 것 같다.

 

상품을 누를 때 해당 레코드 페이지로 이동은

직접 주소값에 변수와 +연산자를 통해 id를 할당하지 못해서 생각보다 많이 귀찮았는데

wrapper를 한번 만든 덕을 봐서 url을 새로 추가해준 다음 url을 href에 할당할 수 있었다.

 

저녁식사는 사람이 거의 없어서 어쩔 수 없이 혼자 먹었는데

저번에 먹고 남은 피자와 컵라면 그리고 반숙란 하나를 먹었더니 든든했다.

 

페이지 버튼 바꾸기를 진행하려고 했는데 실수로 전체 페이지를 끄는 단축키(??)를 눌러버려서

다시 Access Restricted for API Only Users가 떠버렸다.

 

vscode에서 인증한 다음 SFDX:Open Default Browser를 사용하면 들어올 수 있다는 것을 알아서

바로 재로그인을 할 수 있었지만 혹시 이 키워드가 필요해지거나

도움이 필요한 사람이 이 키워드로 검색을 해서 볼 수 있도록 다시 적었다.

 

버튼의 브랜드가 아니고 variant를 바꾸는 것이었고

기존의 natural에서 success로 변경해서 괜찮은 모양을 볼 수 있었다.

 

전체 동기화 페이지에서 다른 버튼과는 다르게 전체 family 업데이트 함수를 만들어야 했기 때문에

일단 이름부터 부여한 다음 버튼의 onclick에 매칭해줬고

기능을 구현하는데 기존에 했던 코드에서 각자 family만 넣어주니 간단하게 해결할 수 있었다.

 

사실 진짜 어렵다고 생각되는 부분은 선택 동기화였는데

선택 동기화에서 어렵게 진행될 예정이었던 분할 자체를 family로 생각보다 편하게 할 수 있었고

아래와 같은 방식으로 에러핸들링 없이 빠르게 마무리할 수 있었다.

getTotalSelecteProducts() {
    const leatherList = []
    const glassList = []
    const steelList = []
    const woodList = []
    let done = 0
    let deleted = 0
    let updated = 0

    for (let i = 0; i < this.productList.length; i++) {
        if (this.productList[i].isSelected) {
          if(this.productList[i].family === 'leather'){
            leatherList.push(this.productList[i].parentId);
          }
          else if(this.productList[i].family === 'glass'){
            glassList.push(this.productList[i].parentId);
          }
          else if(this.productList[i].family === 'steel'){
            steelList.push(this.productList[i].parentId);
          }
          else if(this.productList[i].family === 'wood'){
            woodList.push(this.productList[i].parentId);
          }
        }
    }
    if(leatherList.length === 0 && glassList.length === 0 && steelList.length === 0 && woodList.length === 0){
      this.dispatchEvent(
        new ShowToastEvent({
          title: "선택된 데이터 없음",
          message: '업데이트 하고 싶은 상품을 선택해주세요',
          variant: "error"
        })
      );
      return
    }
    productUpdateWithParams({idLists:leatherList, family : 'leather'})
    productUpdateWithParams({idLists:glassList, family : 'glass'})
    productUpdateWithParams({idLists:steelList, family : 'steel'})
    productUpdateWithParams({idLists:woodList, family : 'wood'})
  }

css파일은 기존 내용과 동일하기 때문에

이 부분 때문에도 1시간 이상 검색을 했지만 제대로 된 내용은 없는 것 같았고

결국 5개의 css파일로 5개의 페이지에 적용하는 것으로 마무리했다.

 

사실 5개의 페이지로 spa를 만드는 것이 아니라

하나의 페이지에 만들 수도 있을 것 같았지만

lwc의 한계 때문에 여기서 10시간쯤 더 투자해야 하지만

당장 내일 발표기 때문에 여기서 마무리하기로 했다.

 

 

 

 

 

(1).백준 11367번 Report Card Time은 여러개의 테스트케이스가 주어지고

각 케이스에서는 이름과 점수가 있을 때 각 이름과 등급을 줄바꿈으로 출력하는 문제였다.

 

if문으로 분기처리를 해 각 등급을 구별해주고

마무리는 템플릿 리터럴로 두 값을 붙여줬지만

a + ' ' + b 형태로 진행해도 문제는 없었을 것 같다.

const input = `5
Bilbo 13
Sam 90
Pippin 78
Frodo 97
Merry 70`.split('\n')

const result = []
for(let i = 1 ; i < input.length ; i++){
    let [name, nums] = input[i].split(' ')
    nums = Number(nums)
    let grade = 'F'

    if(nums > 96){
        grade = 'A+'
    }
    else if(nums > 89){
        grade = 'A'
    }
    else if(nums > 86){
        grade = 'B+'
    }
    else if(nums > 79){
        grade = 'B'
    }
    else if(nums > 76){
        grade = 'C+'
    }
    else if(nums > 69){
        grade = 'C'
    }
    else if(nums > 66){
        grade = 'D+'
    }
    else if(nums > 59){
        grade = 'D'
    }
    result.push(`${name} ${grade}`)
}

console.log(result.join('\n'))

'회고' 카테고리의 다른 글

[수습일지] - 54  (0) 2023.05.19
[수습일지] - 53  (0) 2023.05.18
[수습일지] - 51  (0) 2023.05.16
[수습일지] - 50  (0) 2023.05.15
[수습일지] - 49(주말)  (0) 2023.05.14

어제 밤에 누가 summer version에서는 보이지 않을 수 있다는 것 같아서

오자마자 버전을 확인했지만 아무리 봐도 summer 버전을 사용하는 것이 없었다.

 

신기하게도 구버전 사용하는 메서드들조차 겨울이었고

메서드들은 봄 버전이었다.

마지막으로 sfdc 현재 접속 라이트닝 또한23 Spring이었기 때문에

더 이상 summer와 연관될 내용이 없어 아쉽지만 넘어가기로 했다.

 

dk가 궁금하다고 하셔서 추가적인 테스트를 하고

계정도 5개나 더 생성하면서 trailhead 4개, 개인 dev 2개, 유료판 1개로 테스트했지만

모두 실패해버렸다.

 

확장프로그램의 문제를 제기하신 분도 있지만

다른 브라우저에서도 되지 않았기 때문에 관련이 없을 것 같았다.

 

작업을 하던 중 갑작스럽게 디케이가 오셔서 네임드 크레덴셜의 문제를 알았다고 하시며

갑작스럽게 company information으로 들어가셨는데

원인은 정말 말도 안되게도 통화 설정 부분이 kr-null로 설정되어 있기 때문에

보안쪽에서 작동이 되지 않는다는 것이었는데

korea 거주중인 사람이 아닌 이상 절대 알 수 없는 문제라

어쩐지 sfdx 개발한 사람조차 모르는 문제라는 답변이 나왔었는데

정확한 원인은 모르지만 통화가 null로 찍히면 숫자도 문제가 생기고

숫자가 문제가 생기면 table 구조에 들어가는 숫자 또한 터지기 때문에

결론적으로는 테이블이 생성되는 즉시 그 아래로 데이터가 터져버리는 것이었다.

 

네임드 크레덴셜 부분을 해결했기 때문에

Oauth2 문제도 쉽게 해결할 수 있을 것 같았지만

중간 중간 막혀서 바로 해결되지 않았는데

위니가 핵심 키워드를 알려줘서 해결할 수 있었다.

 

필터링을 진행하면서 @wire(getProducts, {family : '$family', key:'$key'})를 통해 두개의 데이터를 보내고

해당 값을 통해 필터링을 진행할 수 있었고

js 반응등을 할 때 필터링 결과와 무관하게 남아있던 선택 상품 테이블이나

변경했을 때 필터링 결과가 남아있는 등의 자잘한 문제들을 해결했다.

 

테스트코드 및 pmd도 딱히 문제될 것은 없었기 때문에 넘어갈 수 있었고

그 뒤에 정렬이 문제였는데 정렬까지는 어떻게 구현했지만

4개의 파라미터를 넘겨주니 pmd 에러가 발생했다.

 

해당 문제를 해결하기 위해 여러가지 시도를 했지만 결론적으로는 터져서 할 수 없었다.

 

list, object형태로도 시도해보고 JSON형태로도 시도했지만 제대로 도착하지도 못했고

도착한 경우 ‘$xxx’형태로 변경되지 않은 string 자체가 넘어갔다.

 

사실상 4개의 매개변수를 넘기는 것 보다 더 가독성이 떨어질게 분명하고

될지 안될지도 모르는 객체 형태에 통일 후 class wrapper로 받아 쓰기는

굳이 시도하지 않으려고 했지만 질문을 여러곳에한 업보로 다들 구경하셨기 때문에

결국 class 형태도 시도해볼 수 밖에 없었다.

 

전체 변경을 진행했지만 객체라 그런지 추적을 제대로 하지 않았고

결국 const a = {…obj, changed data} 형태로 바꾼 다음

this.obj = a로 모두 주소 변경까지 해줬지만 그래도 되지 않았다.

(생각해보면 구조분해할당으로 한 시점에서 이미 주소가 바뀐다)

 

그래도 apex에서 객체 형태의 데이터를 마음대로 받을 수 있는 wrapper에 대해

조금 더 자세히 알 수 있는 좋은 시간이었던 것 같다.

아래는 객체 적용까지 된 js 전체 코드와 apex 부분 코드다.

//js
import { wire, LightningElement, track } from 'lwc';
import { refreshApex } from "@salesforce/apex";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
import getProducts from '@salesforce/apex/BProductController.getProducts';
import productUpdateWithParams from '@salesforce/apex/BProductController.productUpdateWithParams';
import productUpdate from '@salesforce/apex/BProductController.productUpdate';
import template1 from './leather.html'
import template2 from './glass.html'
import template3 from './steel.html'
import template4 from './wood.html'

//변경점
export default class InventoryConsolidation extends LightningElement {
  @track
  dataObj = {
    showTemplate : "leather",
    searchKey : '',
    order : 'ASC',
    sortKey : 'Name'
  }

  render() {
    if (this.dataObj.showTemplate === "leather") return template1;
    else if (this.dataObj.showTemplate === "glass") return template2;
    else if (this.dataObj.showTemplate === "steel") return template3;
    else if (this.dataObj.showTemplate === "wood") return template4;
    return null;
  }
  selectedProducts = [];
  @track selectedData = [];
  @track isSelected = false;
  @track productList;
  wiredData;
  // @wire(getProducts, {family : '$family', key:'$key', sortKey:'$sortKey', ordering:'$ordering'})
  @wire(getProducts, {strObj:'$obj'})
  wiredRecord(result) {
      this.wiredData = result;
      if (result.data) {
          this.productList = JSON.parse(result.data);
      } else if (result.error) {
          console.log(result.error);
      }
  }
  get obj(){
    return this.dataObj
  }
  // get family() {
  //   return this.showTemplate;
  // }
  // get key() {
  //   return this.searchKey;
  // }
  // get ordering() {
  //   return this.order;
  // }
  // get sortKey() {
  //   return this.sort;
  // }

  constructor() {
    super();
    const a = {showTemplate : "leather",
    searchKey : '',
    order : 'ASC',
    sortKey : 'Name'}
    this.dataObj = a
  }
  
  showLeather() {
    this.selectedData = new Array(0);
    this.isSelected = false;
    const a = {showTemplate : "leather",
    searchKey : '',
    ...this.dataObj};
    this.dataObj = a;
  }

  showGlass() {
    this.selectedData = new Array(0);
    this.isSelected = false;
    const a = {showTemplate : "glass",
    searchKey : '',
    ...this.dataObj};
    this.dataObj = a;
  }

  showSteel() {
    this.selectedData = new Array(0);
    this.isSelected = false;
    const a = {showTemplate : "steel",
    searchKey : '',
    ...this.dataObj};
    this.dataObj = a;
  }
  showWood() {
    this.selectedData = new Array(0);
    this.isSelected = false;
    const a = {showTemplate : "wood",
    searchKey : '',
    ...this.dataObj};
    this.dataObj = a;
  }

  filterHandler(e){
    if(e.key === 'Enter'){
      this.selectedData = new Array(0);
      this.isSelected = false;
      const a = {searchKey : e.target.value,
      ...this.dataObj};
      this.dataObj = a;
      return refreshApex(this.wiredData);
    } 
  }
  sortHandler(e){
    const sortTarget = e.target.textContent;

    if(this.dataObj.order === 'ASC'){
      const a = {order : 'DESC',
        ...this.dataObj};
      this.dataObj = a;
    }
    else if(this.dataObj.order === 'DESC'){
      const a = {order : 'ASC',
        ...this.dataObj};
      this.dataObj = a;
    }

    if(sortTarget === '상품명'){
      const a = {sortKey : 'Name',
        ...this.dataObj};
        this.dataObj = a;
    }
    else if(sortTarget === '재고'){
      const a = {sortKey : 'Stock__c',
        ...this.dataObj};
        this.dataObj = a;
    }
    else if(sortTarget === '최종 수정일'){
      const a = {sortKey : 'LastModifiedDate',
        ...this.dataObj};
        this.dataObj = a;
    }

    this.selectedData = new Array(0);
    this.isSelected = false;
    return refreshApex(this.wiredData);
  }

  handleAllChange(event) {
      for (let i = 0; i < this.productList.length; i++) {
          const product = this.productList[i];
          product.isSelected = event.target.checked;
      }
      if(event.target.checked){
          this.selectedData = [...this.productList];
          this.isSelected = true;
      }
      else{
          this.selectedData = new Array(0);
          this.isSelected = false;
      }
  }

  handleCheckChange(event) {
      this.productList[event.target.value].isSelected = event.target.checked;     
      const arr = []
      for(let i = 0 ; i < this.productList.length ; i++){
          if(this.productList[i].isSelected === true){
              arr.push(this.productList[i]);
          }
      }
      this.selectedData = [...arr]
      if(this.selectedData.length > 0){
          this.isSelected = true;
      }
      else{
          this.isSelected = false;
      }
  }

  getSelecteProducts() {
      for (let i = 0; i < this.productList.length; i++) {
          if (this.productList[i].isSelected) {
              this.selectedProducts.push(this.productList[i].parentId);
          }
      }
      console.log(JSON.stringify(this.selectedProducts));
      
      if(this.selectedProducts.length === 0){
          this.dispatchEvent(
            new ShowToastEvent({
              title: "선택된 데이터 없음",
              message: '업데이트 하고 싶은 상품을 선택해주세요',
              variant: "error"
            })
          );
          return
      }
      //family 추가
      productUpdateWithParams({idLists:this.selectedProducts, family : this.showTemplate})
      .then((result)=>{
          if(result === 'done'){
              this.dispatchEvent(
                new ShowToastEvent({
                  title: "최신 데이터 확인",
                  message: "변경 사항이 없습니다.",
                  variant: "success"
                })
              );
          }
          else if(result === 'updated'){
            this.selectedData = new Array(0);
            this.isSelected = false;
              this.dispatchEvent(
                new ShowToastEvent({
                  title: "변경 사항 적용",
                  message: "변경 사항이 적용되었습니다.",
                  variant: "success"
                })
              );
          }
          else{
            this.selectedData = new Array(0);
            this.isSelected = false;
              this.dispatchEvent(
                new ShowToastEvent({
                  title: "삭제 및 변경 확인",
                  message: "삭제된 데이터가 존재합니다.",
                  variant: "success"
                })
              );
          }
          return refreshApex(this.wiredData);
      })
      .catch((error) => {
        this.dispatchEvent(
          new ShowToastEvent({
            title: "에러 발생",
            message: error.message,
            variant: "error"
          })
        );
      })
      this.selectedProducts = new Array(0);
  }

  updateAllProduct(){
    //family 추가
      productUpdate({family : this.dataObj.showTemplate})
      .then(()=>{
          setTimeout(() => {
              this.dispatchEvent(
                new ShowToastEvent({
                  title: "전체 동기화",
                  message: "전체 동기화 완료.",
                  variant: "success"
                })
              );
              this.selectedData = new Array(0);
              this.isSelected = false;
              const arr = []
              for(let i = 0 ; i < this.productList.length ; i++){
                      this.productList[i].isSelected = false;
                      arr.push(this.productList[i]);
              }
              this.selectedData = [...arr]
              let a = this.template.querySelectorAll("lightning-input")[0]
              a.checked = false;
              return refreshApex(this.wiredData);
          }, 100);
      })
      .catch((error) => {
          this.dispatchEvent(
            new ShowToastEvent({
              title: "에러 발생",
              message: error.message,
              variant: "error"
            })
          );
      })
  }
}

//apex
    /**
    * @description : 재고 보기
    * @author Yohan | 2023.05.11
    * @param strObj
    * @return String
    **/
    @AuraEnabled(cacheable=true)
	public static String getProducts(CustomWrapper strObj){
        System.debug(strObj);
        String family = strObj.showTemplate;
        String searchKey = strObj.searchKey + '%';
        String ordering = strObj.order;
        String sortKey = strObj.sortKey;
        List<ProductWrap> productList = new List<ProductWrap>();
        List<Product2> products = new List<Product2>();
        // // List<Product2> products = Database.query('SELECT Name, Stock__c, ProductParentId__c, LastModifiedDate FROM Product2 WHERE Family = :family');
        // // List<Product2> products = Database.query('SELECT Name, Stock__c, ProductParentId__c, LastModifiedDate FROM Product2 WHERE Family = :family AND Name LIKE :searchKey ');
        // // String b = 'SELECT Name, Stock__c, ProductParentId__c, LastModifiedDate FROM Product2 WHERE Family = :family AND Name LIKE :searchKey ORDER BY '+ sortKey + ' ' +ordering;
        // // List<Product2> products = Database.query(b);
        // // String b = sortKey + ' ' + ordering;
        // // List<Product2> products = Database.query('SELECT Name, Stock__c, ProductParentId__c, LastModifiedDate FROM Product2 WHERE Family = :family AND Name LIKE :searchKey ORDER BY :b');

        
            if(sortKey == 'Name'){
                products = Database.query('SELECT Name, Stock__c, ProductParentId__c, LastModifiedDate FROM Product2 WHERE Family = :family AND Name LIKE :searchKey ORDER BY Name ASC');
            }
            else if(sortKey == 'Stock__c'){
                products = Database.query('SELECT Name, Stock__c, ProductParentId__c, LastModifiedDate FROM Product2 WHERE Family = :family AND Name LIKE :searchKey ORDER BY Stock__c ASC');
            }
            else if(sortKey == 'LastModifiedDate'){
                products = Database.query('SELECT Name, Stock__c, ProductParentId__c, LastModifiedDate FROM Product2 WHERE Family = :family AND Name LIKE :searchKey ORDER BY LastModifiedDate ASC');
            }
            else if(ordering == 'DESC'){
            if(sortKey == 'Name'){
                products = Database.query('SELECT Name, Stock__c, ProductParentId__c, LastModifiedDate FROM Product2 WHERE Family = :family AND Name LIKE :searchKey ORDER BY Name DESC');
            }
            else if(sortKey == 'Stock__c'){
                products = Database.query('SELECT Name, Stock__c, ProductParentId__c, LastModifiedDate FROM Product2 WHERE Family = :family AND Name LIKE :searchKey ORDER BY Stock__c DESC');
            }
            else if(sortKey == 'LastModifiedDate'){
                products = Database.query('SELECT Name, Stock__c, ProductParentId__c, LastModifiedDate FROM Product2 WHERE Family = :family AND Name LIKE :searchKey ORDER BY LastModifiedDate DESC');
            }
            }
        for(Product2 a : products){
            productList.add(new ProductWrap(a));
        }
        return JSON.serialize(productList);
	}


    /****************************************************************************************   
    * File Name   : BProductController.cls  
    * @description : 테이블용 상품 정보 wrapper
    * Test Clss   : -  
    * Author      : Yohan  
    * Page        : -
    * Modification Log   
    * ===============================================================   
    * Ver  Date        Author        Modification  
    * ===============================================================  
    * 1.0  2023.05.11  Yohan        타입지정용 클래스, 메서드 생성
    ****************************************************************************************  
    * TODO  
    ****************************************************************************************/ 
    public class ProductWrap{
        public String parentId;
        public String name;
        public Decimal stock;
        public dateTime dates;
        public Boolean isSelected;
        
        /**
        * @description : 상품 재고 확인용 타입 반환
        * @author Yohan | 2023.05.11
        * @param a
        **/
	    public ProductWrap(Product2 a){
            this.parentId = a.ProductParentId__c;
            this.name = a.Name;
            this.stock = a.Stock__c;
            this.dates = a.LastModifiedDate;
            this.isSelected = false;
		}
    }

    /****************************************************************************************   
    * File Name   : BProductController.cls  
    * @description : wire 전달용 클래스 생성
    * Test Clss   : -  
    * Author      : Yohan  
    * Page        : -
    * Modification Log   
    * ===============================================================   
    * Ver  Date        Author        Modification  
    * ===============================================================  
    * 1.0  2023.05.16  Yohan        타입지정용 클래스, 메서드 생성
    ****************************************************************************************  
    * TODO  
    ****************************************************************************************/ 
    public with sharing class CustomWrapper {
        
        /**
        * @description : CustomWrapper 내부 family
        * @author Yohan | 2023.05.16
        **/
        @AuraEnabled
        public String showTemplate { get; set; }
        
        /**
        * @description : CustomWrapper 내부 key
        * @author Yohan | 2023.05.16
        **/
        @AuraEnabled
        public String searchKey { get; set; }
        
        /**
        * @description : CustomWrapper 내부 ordering
        * @author Yohan | 2023.05.16
        **/
        @AuraEnabled
        public String order { get; set; }
        
        /**
        * @description : CustomWrapper 내부 sortKey
        * @author Yohan | 2023.05.16
        **/
        @AuraEnabled
        public String sortKey { get; set; }
    }

}

 

 

 

 

 

(1).백준 11648번 지속은 숫자가 두자리 이상일 경우 행복하며

다음 턴이 되면 각 숫자를 곱해야 하는 문제로 100 => 0 이 되어 1만큼 행복

25 => 10 =>  0  2만큼 행복과 같은 방식이었다.

 

행복한 조건인 10이상임을 체크하기 위해 9보다 클 경우 반복문을 계속해서 돌게 했고

내부에서는 각 자릿수를 잘라 1에 모두 곱한 다음 결과값을 input에 재할당했다.

 

최종적으로 input이 10보다 작아진 경우 적립된 count를 출력해 문제를 해결했다.

let input = 679
let count = 0

while(input > 9){
    count++
    let tempInput = 1
    let arr = String(input).split('').map(Number)
    for(let i = 0 ; i < arr.length ; i++){
        tempInput *= arr[i]
    }
    input = tempInput
}

console.log(count)

'회고' 카테고리의 다른 글

[수습일지] - 53  (0) 2023.05.18
[수습일지] - 52  (0) 2023.05.17
[수습일지] - 50  (0) 2023.05.15
[수습일지] - 49(주말)  (0) 2023.05.14
[수습일지] - 48(주말)  (0) 2023.05.13

오늘은 family를 추가하기 위해 메서드들을 순회하며

어디에 family가 들어가야 하는지 미리 주석을 다는 작업을 진행한 다음

Aorg의 ProductDataController, ProductDataControllerTest의 pmd를 처리했다.

 

Borg에는 BProductController, BOrgSchedulerController, BOrgSchedulerControllerTest가 있었고

스케줄 부분에서 CRON_EXP에서 pmd 두개가 발생했는데

대문자 사용 및 언더바 사용에 대한 경고 메세지였다.

 

분명 해당 기호는 다들 이렇게 사용하기 때문에 인식을 위해 대문자 + 언더바로 사용하는게 맞다고 생각했고

해결 방법을 모르겠던 찰나 지나가던 파이가 보여서 대문자 사용이 금지인지 물어보니

자바에도 자바스크립트처럼 상수 값을 선언하는 방법이 있다고 알려주셨고

아래와 같이 final 선언을 통해 상수 선언을 해서 pmd를 통과할 수 있었다.

private final static String CRON_EXP = '0 0 0 17 4 ? 2024';

이런 선언이 있다는 사실 자체를 몰랐기 때문에 접근할 수 없었던 내용인데

확실히 자바를 조금 알아야 진행이 되는 부분도 존재하는 것 같다.

 

BOrgSchedulerController, BOrgSchedulerControllerTest의 pmd는 다 잡았지만

BProductController의 마지막 두개인 ‘has a cyclomatic complexity of 11’를 처리하지 못했다.

 

코드 변경 전 잘 작동하는지 확인하려고 했는데 인증이 제대로 되지 않아서 당황했지만

A org의 토큰이 아닌 B org의 토큰을 발급받아서 생긴 문제였다.

 

원하는 인증에 매칭시키기 위해서는 원하는 org의 로그인이 제일 최신이어야 했기 때문에(다중 로그인 시)

ORGANIZER를 사용해 org의 로그인만 한번씩 더 진행해주면

빠르게 여러 인증 코드를 받을 수 있을 것 같았다.

 

인증 후 정상적으로 코드가 작동하는 것을 확인했기 때문에

BProductController의 복잡도 문제를 해결하기 위해

두 메서드의 공통된 처리 부분을 분리해 다른 메서드를 생성해보기로 했다.

 

처음에는 조금 당황했지만 생각보다 어렵지 않게 문제를 해결할 수 있었고

아래와 같이 처리한 다음 값을 할당해서 기존 처리와 유사하게 할 수 있었는데

재미있는 부분은 list를 중첩해서 사용해도 문제가 발생하지 않는다는 점이었다.

List<List<Product2>> dmlLists = responseHandler(response, bDatas);
            insertList = dmlLists[0];
            updateList = dmlLists[1];
            deleteList = dmlLists[2];

 

테스트를 시작했는데 A org에서 B org로 데이터를 옮겨야 하고

그 기준점이 A org의 아이디였기 때문에 B org에서는 ParentId를 required로 설정했는데

그 부분에서 문제가 발생해 아이디를 만들려면 ParentId를 만들어줘야 하고

아무 값이나 id에 넣으면 id 비교 부분에서 올바른 id가 아니라고 터져버렸다.

 

pmd의 빨간줄을 보며 계속 고민하다가

이건 포기하는게 맞다는 생각이 들어서 천에게 물어보니

웬만한 pmd 에러는 해결하는게 맞고 해결할 수 있을거라고 말씀하셔서

우회할 방법을 생각해보니 생각보다 어렵지 않았다.

 

하드코딩을 우회할 product를 생성한 다음

해당 id만 뽑아먹고 버리면 되는 것이었다.

List<Product2> tempPds = new List<Product2>();
        Product2 productA = new Product2(Name = 'Test 1', Stock__c = 1, ProductParentId__c = 'a');
        Product2 productB = new Product2(Name = 'Test 2', Stock__c = 2, ProductParentId__c = 'b');
        Product2 productC = new Product2(Name = 'Test 3', Stock__c = 3,ProductParentId__c = 'c');
        tempPds.add(productA);
        tempPds.add(productB);
        tempPds.add(productC);
        insert tempPds;

        List<Product2> pds = new List<Product2>();
        Product2 product1 = new Product2(Name = 'Test 1', Stock__c = 1, ProductParentId__c = tempPds[0].Id);
        Product2 product2 = new Product2(Name = 'Test 2', Stock__c = 2, ProductParentId__c = tempPds[1].Id);
        Product2 product3 = new Product2(Name = 'Test 3', Stock__c = 3,ProductParentId__c = tempPds[2].Id);
        pds.add(product1);
        pds.add(product2);
        pds.add(product3);
        insert pds;
        delete tempPds;

 

Family 를 js코드 및 apex에 적용해야 여러 org를 사용할 수 있지만

하나가 잘못되면 코드가 와장창 고장나기 때문에 변경 전 기존 코드 위치는 다 주석 처리를 하고

모두 연계되어 있기 때문에 중간테스트는 없이 바로 작동 확인을 할 수 밖에 없었다.

 

꼼꼼히 확인해서 통과할 것 같았지만 역시 쉽게 통과시켜주지 않았고

메서드에서는 문제가 없었지만 lwc에서 터져버렸다.

 

원인을 확인해본 결과 wire는 우선순위가 2위로

constructor 다음이었기 때문에 wire에서 사용할 this.template이 없어서 터지는 것이었고

이를 해결하기 위해 방법을 찾아봤지만 constructor 사용 외에는 없었기 때문에

constructor 를 생성해 사용했지만 super를 가져오지 않으면 사용되지 않으며

get 메서드를 사용해서 가져와야만 적용이 됐기 때문에 아래와 같이 처리해서 정상 작동시킬 수 있었다.

@wire(getProducts, {family : '$family'})
    wiredRecord(result) {
        this.wiredData = result;
        if (result.data) {
            this.productList = JSON.parse(result.data);
        } else if (result.error) {
            console.log(result.error);
            this.accounts = undefined;
        }
    }
    get family() {
      return this.showTemplate;
    }
    constructor() {
      super();
      this.showTemplate = 'leather';
    }

 

또한 해당 내용을 적용시켜서 스케줄러에서도 family 값을 넘겨줘야 했는데

내부에 하나의 org를 고정시킨다면 ‘leather’로 처리할수 있지만

여러개의 회사에서 유통을 진행하기 때문에 변수를 가져올 필요성이 있었다.

 

결국 아래와 같이 컨스트럭터를 사용해서 해당 값을 먼저 만들고

그 값을 받아오게 사용할 수 있었다.

public class BOrgSchedulerController implements Schedulable{
    private String family;

    public BOrgSchedulerController(String family) {
        this.family = family;
    }

    public void execute(SchedulableContext sc) {
        BProductController.productUpdate(family);
    }
}
//하단 스케쥴러 추가용 코드 사용 예시
String jobName = '19분 데이터 추가 확인 예정';
String cronExpression = '0 19 * * * ?'; // 매 시간마다 실행
BOrgSchedulerController schedulable = new BOrgSchedulerController('leather');
System.schedule(jobName, cronExpression, schedulable);

 

 

 

 

 

(1).백준 11520번 And Then There Was 5는 파이에 5가 있다는 내용을 주장하고 싶은 문제인데

결론적으로는 각 테스트케이스의 뒤에 있는 숫자와 5를 같이 출력해주면 되는 문제로

값을 nums로 받아준 다음 출력할 때는 템플릿 리터럴을 사용해 'num 5'형태로 출력했다.

const input = `4
3 2
123456 6
999999 8
765432 7`.split('\n')

const result = []

for(let i = 1 ; i < input.length ; i++){
    const nums = input[i].split(' ').map(Number)[1]
    result.push(`${nums} 5`)
}

console.log(result.join('\n'))

'회고' 카테고리의 다른 글

[수습일지] - 52  (0) 2023.05.17
[수습일지] - 51  (0) 2023.05.16
[수습일지] - 49(주말)  (0) 2023.05.14
[수습일지] - 48(주말)  (0) 2023.05.13
[수습일지] - 47  (0) 2023.05.12

현재 남은 작업을 정리해보면

spa, toast, family 처리, filtering, sorting, batch, Oauth2, PMD, test class, additional Org *3

여기에 ppt, 동기api 연동, 환경변수 알아보기도 있지만 그건 별개로 치고

10개의 작업 중 주말에 혼자 할 수 있어 보이는

패밀리 추가, 필터기능, 정렬기능은 나중에 하기로 했다.

 

결국 오늘 진행은 spa, toast, Oauth2, test class 4개에 가능하면 pmd까지 해보고 싶은 상황이고

아래와 같은 일정을 작성했다.

 

~08:40 기능 정리 및 css 다듬기 완료 08:38

~09:00 일정 정리 완료 09:03

~10:00 spa 페이지 전환 구현

~10:20 toast 필요한 부분 있는지 확인

~11:30 Oauth2 인증 적용(현재 하드코딩)

~12:30 점심시간

~14:00 Named Credential 확인

~15:00 A org Rest Api 테스트 클래스 작성

~16:00 B org Scheduler 테스트 클래스 작성

~18:00 B org BProductController 테스트 클래스 작성

 

spa 전환을 위해 코드를 작성했는데

css는 해당 html과 연동되기 때문에 4개에 각각 호버링을 줄 수 없었는데

이를 위해 style을 위에 띄울지 같은 css 4개 파일을 만들어야 할지는 잘 모르겠다.

 

해당 부분은 넘어갔고 named credentials를 도전했는데

1.Connected App 생성

2.Auth.Providers 생성

3.app manager에 callback 넣기

4.named credential에 providers와 A url 넣기

방식으로 접근했는데

도저히 되지 않아서 물어보니

이번 문제는 참신하게도 permission이 들어가는 계정이 있고

안되는 계정이 있어서 org를 새로 파서 해결하셨다는 말을 들었다.

 

하루 종일(6시간) named credential만 처리하고

외국 sfdc 채널에 질문도 올려봤지만 아무도 답변이 오지 않았다

 

결국 해당 최신 자동화 방식을 포기하고

수동으로 Oauth2 토큰을 넣어주지만

그나마 Named Credential 내부에 넣어주고 해당 값을 불러와서

헤더를 따로 작성하거나 경로를 다 적지 않고

조금이나마 더 편하게 문제를 해결하기는 했다.

 

뭔가 야매로 하는 것 같은 부분이 아쉽긴 한데

이게 되는 계정이 있고 가끔 안되는 계정이 있는데

새로 계정을 파서 새로 작업을 해야 해결이 된다는 말을 듣고

원인을 파악하기 전에는 하는 법은 모두 충분히 10여차례 이상 반복 학습했으니

pmd 경고만 해결하는 것으로 넘어갔다.

 

테스트코드는 손도 대지 못했지만

그래도 학습을 6시간동안이나 진행해서

좀 약했던 Oauth2와 Named Credential에 대해 알 수 있는 기회였다.

 

 

 

 

 

(1).백준 23303번 이 문제는 D2 입니다는 

문자열 내부에 D2 또는 d2가 들어있을 경우 D2를 출력하고

D2 또는 d2가 들어있지 않은 문자열인 경우 unrated를 출력해야 하는 문제였다.

 

정확히 그 철자가 들어가야 했기 때문에 includes로 해결할 수 있었다.

const input = `naver d2`
let result = `unrated`

if(input.includes('d2') || input.includes('D2')){
    result = 'D2'
}
console.log(result)

'회고' 카테고리의 다른 글

[수습일지] - 49(주말)  (0) 2023.05.14
[수습일지] - 48(주말)  (0) 2023.05.13
[수습일지] - 46  (0) 2023.05.11
[수습일지] - 45  (0) 2023.05.10
[수습일지] - 44  (0) 2023.05.09

lwc 페이지를 만들려고 생각해보니

wire로 테이블에 재고를 뿌려주려면 테이블에 넣을 데이터가 먼저 있어야 하고

그 데이터를 먼저 가져오기 위해서는 Product2에서 정보를 가져올 메서드가 있어야 연결이 된다.

 

결국 lwc로 시작하려고 하다가 다시 또 메서드를 작성하게 되었다.

 

남은 일을 다시 정리해보면

1.B org Product2 데이터 받아오는 메서드

  • 나중에 where family in xx로 수정 필요
  • 메서드 하나에 a,c,d,e family만 받으면 하나의 메서드로 되기 때문에 BProductController에 작성

2.lwc 페이지 생성

  • 미리 spa 탭 구상해서 생성 (A, C, D, E)
  • 내부 테이블 작성
  • 테이블 우측 전체 동기화 추가
  • 전체 동기화 js 기능 추가
  • 테이블 내부 체크박스 추가
  • 테이블 우측 체크박스 부분 동기화 추가
  • 부분 동기화 js 기능 추가
  • 우측 버튼 하단에 선택된 상품명 정렬 컨테이너(여백 없애기)
  • 테이블 상단에 input창 추가로 상품명 검색으로 필터링 기능 추가

3.pmd 처리

4.테스트케이스 작성

5.보안 처리(Oauth2 받아서 처리하기)

6.toast 추가로 작업 반영 확인시키기

7.3개 org 추가하기

8.ppt 작성(급할 경우 notion으로 해도 된다)

9.배치 사용해서 처리하기

10.동기 api 소통

 

메서드를 작성하려다가 넣을 페이지 생성이 떠올라서

탭 생성을 찾아보다가 결국 앱 빌더를 통해 새로운 페이지를 만들고

제일 어울리는 sales 앱에 탭을 추가했다.

 

내부 spa탭에 들어갈 이름을 고민하다가

A org는 가죽 컨셉이기 때문에 가죽동화공방이라는 이름을 확정했고

나머지 C, D, E는 아직 미정이라 목재공방, 유리공방, 철재공방으로 가명을 넣어둘 예정이다.

 

결국 이름을 먼저 고민하게 되었는데

유리공방은 조금 더 세련된 느낌이라 로즈마리글라스로 정했고

철재 공방은 둔탁하고 철공소에 가까운 이름이라 덕은철강으로 결정했고

목재공방은 아낌없이주는나무로 결정했다.

 

이제 드디어 LWC 제작에 들어갈 수 있다.

lwc를 생성하고 spa 페이지를 만들려고 했는데

의식에 흐름에 따라 만들었던 부분 동기화 메서드를 이식해버렸고

2번에 작성했던 작업의 거의 역순으로 처리를 진행해버렸다.

 

테이블을 생성하기 위해

B org의 전체데이터를 받아오고

체크박스를 미리 만들어서 테이블을 생성했다.

 

생성한 체크박스를 이용하기 위해

js를 연동해서 기능을 추가하고

결국 부분 동기화를 전체 동기화 버튼보다 먼저 구현해버렸다.

 

체크박스를 만들려고 하는데 이것 또한 1시간 가량 먹히지 않아서

회사 LWC의 권위자 파이에게 질문을 했는데

토다다다다닥 엄청난 속도로 타이핑을 하시면서

간단한 오류들을 체크해보시더니

우회해서 값을 넣는 방식을 사용하셔서 성공하고

심지어 다시 정상적으로 되는 방법까지 찾아서

이게 하다 막히면 우회하는 방법으로 접근하라고 알려주시고 멋있게 떠나셨다.

 

apex 관련은 블레이크, LWC는 파이가 계시기 때문에 든든하지만

시도 때도 없이 질문하기에는 죄송하기도 하고 기본 매너도 아니기 때문에

할 수 있는 모든 방식을 다 시도하고

검색 키워드도 다 확인한 다음 내 선에서 정말 안될 것 같을 때 요청했는데

그 이후로는 문제가 길어야 1시간이면 해결이 되서 추가적인 요청은 없었다.

 

그 뒤로 작업을 하던 중 부분 데이터 요청하기 조회가 되지 않아 1시간 넘게 고생했는데

새로 도입하려던 코드가 문제가 아니었고

하필 새로 도입하려던 코드를 작성하던 시점에 인증이 만료되었기 때문에

해당 코드 작동이 되지 않았는데

혼자 기상천외한 방법들을 계속 시도하고 있었던 것이다.

 

그나마 예전에 작성했던 코드까지 도착해서

해당 코드의 작동이 안될 수 없다는 사실을 파악하고

역순으로 원인을 찾아보니 메서드의 비동기 처리 인증서 만료 문제였기 때문에

찾고자 하지 않는 상황에서는 에러 메세지를 볼 수 없었다.

 

spa를 만들고 싶었지만 저녁에 피자를 먹기로 해서 피자를 먼저 먹고

테이플 내부에 들어갈 데이터를 더 받아오고 싶었는데

곰곰이 생각해보니 매개변수 4개부터 에러메세지를 표기했는데

Product2를 전체로 넘기고 처리하면 된다는 사실을 알고 변경 시간도 추가했다.

 

테이블이 너무 비어있어서 어쩔 수 없이 추가한 부분인데

실제 데이터라면 제품설명등이 있으면 조금 더 풍부한 데이터가 될 수 있을 것 같지만

일단 기능적으로 더 다듬는 것이 더 중요해보였다.

 

css는 포기하고 lwc 공식문서의 class 내부에 내장형? 스타일을 먹이는 방식으로 진행했더니

그나마 그 때부터 일이 해결되기 시작했다.

 

뭔가 일이 많았는데 의식의 흐름으로 작업을 해서 뭔가 잘 했지만

과정 기록이 되지 않아 아쉬웠다.

 

 

 

 

 

(1).백준 17350번 2루수 이름이 뭐야는 뭔가 많이 이상한 문제였는데

여러 줄의 테스트케이스에서 'anj'라는 이름(?)이 있는 경우'뭐야;'를 출력하고 없는 경우 '뭐야?'를 출력하는 문제였다.

 

문자열 내부에 값까지 포함되어야 하는지 고민했지만단순 일치로 문제를 제출해보니 통과되는 것을 보면anj가 들어간 문자열이 아닌 'anj'만 일치해야 함을 알 수 있다.

const input = `4
aptl
molamolamolamola
dlqmfkglahqlcl
QWERTOP`.split('\n')

let result = '뭐야?'

for(let i = 1 ; i < input.length ; i++){
    if(input[i] === 'anj'){
        result = '뭐야;'
    }
}

console.log(result)

'회고' 카테고리의 다른 글

[수습일지] - 48(주말)  (0) 2023.05.13
[수습일지] - 47  (0) 2023.05.12
[수습일지] - 45  (0) 2023.05.10
[수습일지] - 44  (0) 2023.05.09
[수습일지] - 43  (0) 2023.05.08

오늘도 무난하게 출근했고

과제를 진행하는데 생각보다 과제는 쉽지 않았다.

 

과제를 진행할 때 프론트엔드 때 처럼 api를 이용하는게 아니라

api를 직접 만들어야 하는 느낌이었는데

api를 생성하기 위한 재료들을 여기저기서 모아와야 하는 느낌이었다.

 

점심에는 홍콩반점에 가서 자장면 곱배기와 해시브라운을 추가했는데

양이 조금 많은 것 같았다.

 

오늘 회사에서 기념비적인 업적을 달성했기 때문에

전원 참석했으면 좋겠다는 회식이 진행되었는데

가는 길에 티비에서나 보던 무한도전 사람 두명 모형을 볼 수 있었다.

 

기본으로 스폐셜을 시켰다고 하시는데 무려 12만원이나 했다.

 

거기에 술은 소맥으로 마셨는데 

주는대로 마시다보니 소맥으로 10잔정도 마신 것 같았다.

스폐셜 세이로무시

 

2차로는 뭔가 알 수 없는 곳으로 갔는데

여기서만 6명이서 무려 71만 8천원이 나왔다고 한다.

 

2차가 끝난 뒤 노래방에 간다고 하셔서 그냥 들으러 갔는데

30분 추가가 되어서 1시간 30분이나 있어버렸다.

 

소맥 10잔에 데킬라 8잔을 마시니 확실히 조금 취한 것 같은 느낌은 들었는데

그래도 일기, 회고를 작성하고 알고리즘 문제를 푸는데는 큰 지장은 없었다.

 

여기서 한 5잔정도 더 마신다면 조금 위험할 것 같다.

 

돌아오는 길에는 택시를 타고 10여분 걸어야 하는 곳에서 내렸는데

살짝 걷는게 어색한 느낌이 들기도 하는 것을 보면

70%정도 취한 것 같은 느낌이 들었다.

 

과연 내일 숙취가 있을지 궁금하다.

 

 

오늘은 1시간 이상 걸었다.

'일기' 카테고리의 다른 글

순식간에 사라진 토요일  (0) 2023.04.29
생각보다 어려운 첫 번째 과제  (5) 2023.04.28
WSL로 작업 환경 개선  (0) 2023.04.26
LWC 강의 완료  (2) 2023.04.25
벌써 일년  (4) 2023.04.24

오늘도 무난하게 출근했고 계획대로 학습을 진행하다가

로딩이 3~5분이 걸리는 것 자체에 답답함을 너무 느껴서 환경 개선을 하고 싶었다.

 

20여명이 3분만 절약해도 하루 한시간이 절약되기 때문에

비효율적인 부분이 너무 답답해서 계속 신경이 쓰였다.

 

오전에 잠깐 해보려고 했는데 계속 하나씩 문제가 생기고 다 된 것 같아서

점심을 먹고 나서도 시도해서 결국 wsl 환경으로 실행을 완료할 수 있었다.

 

기본 환경에서는 상상도 못한 프리징같은 이슈나 인증에러가 있었지만

해결하고 20초정도만에 모든 로딩을 끝내니 뿌듯했다.

 

이걸 공식적으로 적용하게 하려면 설치 과정을 조금 더 체계화해서

사진들을 포함한 문서화를 진행하면 좋을 것 같은데

5월 4일날 발표를 진행해야 하는 과제를 이제 시작했기 때문에

과제를 하기에도 시간이 빠듯한 것 같다.

 

중간에 먹은 점심은 육전국밥이었는데

굳이 육전을 국에 담궈서 눅눅하게 먹는 이유는 잘 모르겠다.

9000원

육전이라는 이름이 들어가는 것 치고 바가지는 아닌 9천원이었는데

육전이 아닌 소고기가 들어간 비슷한 생김새의 소고기국밥은 8천원이었기 때문에

다음에 온다면 소고기국밥으로 먹을 것 같다.

 

9천원에 육전국밥을 먹느니 조금 더 푸짐하게 저번에 먹었던 뼈다귀해장국을 먹을 것 같은데

8천원에 소고기국밥을 먹는다면 고민을 좀 할 문제인 것 같다.

 

과제를 진행했는데 생각처럼 잘 되지 않아서

오늘도 8시가 넘어서 퇴근했다.

 

확실히 저녁에 퇴근해서 그런지 

밖도 어둡고 버스에 사람도 별로 없었다.

퇴근버스

웃긴건 퇴근시간대보다 저녁시간대에 버스 간격이 더 짧았는데 

사람이 더 많은 퇴근시간대에 버스를 몇대 더 넣는게 더 좋지 않을까 생각됐다.

 

오늘은 저녁을 주문하는 분이 안계셔서 그냥 밥을 먹지 않고 하다가 왔는데

집에 와서 밥을 해서 먹기에는 피곤할 것 같아서 도시락을 사서 들어갔다.

4500 -> 3600(GS 20%) -> 2600(카카오 1000원 페이백)

내일은 과제 진도를 좀 뽑았으면 좋겠다.

 

 

 

오늘도 40분 이상 걸었다.

'일기' 카테고리의 다른 글

생각보다 어려운 첫 번째 과제  (5) 2023.04.28
첫 회식  (0) 2023.04.27
LWC 강의 완료  (2) 2023.04.25
벌써 일년  (4) 2023.04.24
괜찮았던 주말  (0) 2023.04.23

오늘은 버스가 막히지 않아서 8분에 도착할 수 있었다.

오자마자 계획을 작성하고 lwc 학습을 시작했는데

뒤로 갈수록 코드를 작성하기보다는 완성된 내용을 간단히 설명하고 넘어가버렸다.

 

다 치느라 손가락이 아프긴 했기 때문에 나쁘지는 않았지만

해당 코드가 에러가 나기도 하고 쓸모없는 import등을 지우지 않는 것들이 조금 신경쓰이긴 했다.

 

점심에는 햄버거를 먹으러 가자고 해서 갔는데 아쉽게도 바스버거였다.

바스버거

감자칩 같은 것도 무제한으로 주기는 했지만 많이 단단하고 짜서 딱 대기하며 심심풀이로 먹을 정도의 맛이었다.

 

돌아와서도 lwc 강의를 듣다가 휴게실에 가보니 과자가 많이 들어와있었는데

어제 저녁에 정리한 것 외에도 조금 더 과자가 있는 것을 보면 아침에 배송된 것도 있는 것 같았다.

 

오늘도 야근을 해도 나쁘지는 않을 것 같았지만

오늘은 딱히 저녁을 먹는 분도 안계셔서 혼자 뭘 주문해 먹기도 이상하고

학습을 당장 급하게 할 필요성이 있긴 한데 수,목도 남아 있기 때문에

일단 목표한 강의는 다 들었으니 6시 30분쯤 퇴근했다.

 

추가로 들으면 좋을 것 같은 강의 3개를 오늘 듣지 못한 것은 아쉽지만

쓸대 없이 튀어나오는 버전 차이(?)로 생기는 에러들을 해결하느라 시간이 많이 지연되서

빠르게 했다고 생각했는데 어느새 예정 시간보다 조금 초과되어 버렸다.

 

사실 35강 까지 있는 강의를 35강까지 다 듣고 남은 70여개의 상황에 맞는 강의 추가 학습은

진행할 생각이 없었는데 해당될 것 같은 내용만 3개 고른 것이라 내일 들어도 될 것 같기는 했다.

 

내일은 그래도 오전시간 내에 과제를 시작할 수 있을 것 같은데

과제가 얼마나 어려울지 걱정도 조금 되고

과제를 시작할 수 있다는 것 자체로 좋은 것 같기도 하고 애매하다.

 

집에 오는 길에 GS에 보니

오늘은 웬일인지 혜자도시락이 두개나 있었다.

 

닭강정맛과 제육볶음 둘 다 괜찮고 닭강정을 조금 더 좋아하기도 하고

닭강정을 두번 연속으로 먹은 것은 상관없는데

닭강정에는 뭔가 이상한 가짜 시금치같은 나물이 존재하는데 정말 맛이 별로였다.

 

음식물 쓰레기가 나오지 않게 다 먹기는 하는데

닭강정이나 다른 반찬에 덮어서 억지로 먹으면서 조금 기분이 안좋아져서

안심하고 먹을 수 있는 제육볶음을 먹기로 했다.

(거기다 320원 더 저렴하다)

4500 -> 3600(20%할인) -> 2600(1000원 페이백)

 

 

 

오늘도 40분 이상 걸었다.

'일기' 카테고리의 다른 글

첫 회식  (0) 2023.04.27
WSL로 작업 환경 개선  (0) 2023.04.26
벌써 일년  (4) 2023.04.24
괜찮았던 주말  (0) 2023.04.23
다이소 가야할까?  (0) 2023.04.22

1.static resource를 사용하는 방법은 간단한데

setup 부분에서 static을 검색한 다음 컴퓨터에 있는 파일을 등록하고(이름 중요)

아래의 코드처럼 js에서 해당 파일을 import 하면 된다.

 

주소 자체에 해당 파일명이 들어가기 때문에 명확한 이름이 유리하다.

//html
<template>
    <lightning-card title="Toast Events" icon-name="utility:animal_and_nature">
        <div class="slds-var-m-around_medium">
            <div style="padding: 10px">
                <img src={logo1url}>
                <img src={logo2url}>
            </div>
        </div>
    </lightning-card>

</template>

//js
import { LightningElement } from "lwc";
import logo1 from "@salesforce/resourceUrl/logo1";
import logo2 from "@salesforce/resourceUrl/logo2";

export default class StaticResource extends LightningElement {
  logo1url = logo1;
  logo2url = logo2;
}

 

 

2.permission에 따라 보이는 내용을 다르게 할 수 있는데

setup에서 permission을 설정한 다음 해당 permission을 보유했는지 조회할 수 있다.

 

해당 permission들은 permission sets를 통해 편하게 부여할 수 있고

hasAccessUI라는 명령어를 통해 해당 permission의 주소를 입력할 경우 알아서 boolean type으로

값을 반환해주기 때문에 template의 if:true, if:false를 사용하여 서로 다른 내용을 보여줄 수 있다.

//html
<template>
    <lightning-card title="Permission Set UI" icon-name="utility:animal_and_nature">
        <div class="slds-var-m-around_medium">
            <div style="padding: 10px">
                <template if:true={isUIAccessible}>
                    <h1>I am available.</h1>
                </template>
                <template if:false={isUIAccessible}>
                    <h1>I am not available.</h1>
                </template>
            </div>
        </div>
    </lightning-card>

</template>

//js
import { LightningElement } from "lwc";
import hasAccessUI from "@salesforce/customPermission/lwcStack";

export default class PermissionSetUI extends LightningElement {
  get isUIAccessible() {
    return hasAccessUI;
  }
}

 

 

3.Navigation 기능은 여기저기에서 많이 쓸 것 같은 유용한 기능인데

Action이나 state를 추가해 생성, 초기값, 필터링 등 유용한 기능들이 많이 있다.

 

주의사항은 extends에 NavigationMixin를 넣어줘야 한다는 것으로

이걸 빼고 작동이 되지 않아 당황했었다.

//html
<template>
    <lightning-card title="Navigate" icon-name="utility:animal_and_nature">
        <div class="slds-var-m-around_medium">
            <lightning-button label="Navigate to Home" class="slds-var-m-around_medium"
                onclick={goHome}></lightning-button>

            <lightning-button label="Navigate to New Contact" class="slds-var-m-around_medium"
                onclick={goNewContact}></lightning-button>

            <lightning-button label="Navigate to Nw Contact with value" class="slds-var-m-around_medium"
                onclick={goNewContactWithValue}></lightning-button>

            <lightning-button label="Navigate to Contact Listview" class="slds-var-m-around_medium"
                onclick={goContactList}></lightning-button>

            <lightning-button label="Navigate to Tab" class="slds-var-m-around_medium"
                onclick={goTab}></lightning-button>
        </div>
    </lightning-card>
</template>

//js
import { LightningElement } from "lwc";
import { NavigationMixin } from "lightning/navigation";
import { encodeDefaultFieldValues } from "lightning/pageReferenceUtils";

export default class Navigations extends NavigationMixin(LightningElement) {
  goHome() {
    this[NavigationMixin.Navigate]({
      type: "standard__namedPage",
      attributes: {
        pageName: "home"
      }
    });
  }
  goNewContact() {
    this[NavigationMixin.Navigate]({
      type: "standard__objectPage",
      attributes: {
        objectApiName: "Contact",
        actionName: "new"
      }
    });
  }

  goNewContactWithValue() {
    const defaultValue = encodeDefaultFieldValues({
      FirstName: "Jichang",
      LastName: "Ryu"
    });

    this[NavigationMixin.Navigate]({
      type: "standard__objectPage",
      attributes: {
        objectApiName: "Contact",
        actionName: "new"
      },
      state: {
        defaultFieldValues: defaultValue
      }
    });
  }

  goContactList() {
    this[NavigationMixin.Navigate]({
      type: "standard__objectPage",
      attributes: {
        objectApiName: "Contact",
        actionName: "list"
      },
      state: {
        filterName: "Recent"
      }
    });
  }

  goTab() {
    this[NavigationMixin.Navigate]({
      type: "standard__navItemPage",
      attributes: {
        apiName: "LWCStack"
      }
    });
  }
}

 

 

4.modal 부분은 코드가 너무 많고 복잡해서 작성하기는 어려울 것 같았다.

실제로 이번 강의에서는 코드를 제대로 보여주지도 않고 이론적 설명만 진행했는데

기존의 작성 코드와 스타일이 다른 것을 보면 공식문서에서 복사한 느낌이 강했다.

 

모달을 작성해야 할 경우 Lightning design system에 들어가서

blueprint modal 부분을 확인해야겠다.

 

 

5.quick action은 조금 재미있는 구조였는데

모달과 유사하게 lwc를 가져와서 사용하는 느낌이었다.

 

또한 CloseActionScreenEvent를 사용해 닫는 것도 신기했는데

보통은 상위 컴포넌트를 클릭시 위로 전파되며 닫히고

현재 컴포넌트는 내부의 취소 또는 x 버튼을 눌러야 닫히는 구조로만 생성했었는데

띄운 창을 닫는 기능 자체가 있는 것은 조금 신선했다.

 

적으며 코드를 다시 보니 대단한 것은 아니었고

state를 기준으로 true/false로 띄우는 것이 아니기 때문에

CloseActionScreenEvent가 없으면 닫을 수가 없는 상태라 당연한 기능이었던 것 같다.

 

게다가 현재도 외부를 클릭할 경우 닫히지 않는건 마찬가지인데

외부 클릭할 경우 닫히게 추가해야 하는 것도 동일하다.

//meta
<?xml version="1.0" encoding="UTF-8" ?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>56.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordAction</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordAction">
            <actionType>ScreenAction</actionType>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

//html
<!-- sldsValidatorIgnore -->
<!-- sldsValidatorIgnoreNextLine -->
<template>
    <lightning-quick-action-panel title="Quick Action Title">
        Body Contents
        <div slot="footer">
            <lightning-button variant="brand" label="Save"></lightning-button>
            <lightning-button variant="neutral" label="Cancel" onclick={closeAction}></lightning-button>
        </div>
    </lightning-quick-action-panel>
</template>

//js
import { LightningElement } from "lwc";
import { CloseActionScreenEvent } from "lightning/actions";

export default class QuickActionLWC extends LightningElement {
  closeAction() {
    this.dispatchEvent(new CloseActionScreenEvent());
  }
}

추가적으로 html이 없어도 되는 quickAction도 존재했는데

단순히 js파일을 작동시키는 구조인 것 같았다.

 

invoke 자체가 실행 시 자동으로 작동하는 것인지

왜 저게 작동하는지는 정확하게 모르겠는데

검색을 해보니 actionName이라는 것으로 확정을 지어주지 않으면

그냥 내부에 있는 함수를 실행한다고 한다.

 

invoke인지 아닌지가 중요한 것이 아니고

console.log를 찍는다는 의미가 invoke 였을 뿐 다른 기능을 할 때는 changePage 등 아무 이름이나 사용해도 상관 없었다.

//meta
<?xml version="1.0" encoding="UTF-8" ?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>56.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordAction</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordAction">
            <actionType>Action</actionType>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

//js
import { LightningElement, api } from "lwc";

export default class HeaderlessActionLWC extends LightningElement {
  @api invoke() {
    console.log("headerlessAction Clicked");
  }
}

 

 

6.이번 코드에서는 실수가 있었는데 왜 동영상에서는 통과가 됐는지 의아한 부분이었다.

 

하단의 코드를 자세히 보면 template에서 if:true로 체크하는 부분이 존재하지 않는데

이렇게 되면 contacts라는 요청을 렌더링이 되는 도중에 즉각적으로 보내게 된다.

 

현재 this.listview에는 아무 데이터가 없는 undefined 상태임에도 불구하고

.data.record.record로 접근하려고 하면 페이지가 터저버린다.

 

sfdc 자체적인 문제였던건지 아니면 예전 버전에서는 문제가 없었는지는 모르겠지만

프론트쪽에서는 당연히 터져야 하는 부분이기 때문에 오히려 통과된 것이 의아했다.

 

자체적으로 get contacts 내부에 if문을 통해 this.listview.data가 undefined인지를 확인하고

값이 들어있는 경우 정상적인 값을 반환해주고

값이 없는 경우 null을 반환해 에러가 생기지 않도록 처리했다.

 

작성자가 이전에는 if:true를 자주 사용했기 때문에

그 값이 없는 경우 하단의 값 요청인 get xxx를 사용조차 하지 않아서 문제가 없었는데

이번에는 분명 에러를 발생시킬 코드를 썼는데 자기만 영상을 멈추고 수정했던건지 의아하다.

//html
<template>
    <lightning-card title="wireListView" icon-name="utility:animal_and_nature">
        <div class="slds-var-m-around_medium">
            <div style="padding: 10px">
                <template for:each={contacts} for:item="con">
                    <p key={con.fields.Id.value}>{con.fields.Name.value}</p>
                </template>
            </div>
        </div>
    </lightning-card>
</template>

//js
import { LightningElement, wire } from "lwc";
import { getListUi } from "lightning/uiListApi";
import CONTACT_OBJECT from "@salesforce/schema/Contact";
import NAME_FIELD from "@salesforce/schema/Contact.Name";

export default class WireListview extends LightningElement {
  @wire(getListUi, {
    objectApiName: CONTACT_OBJECT,
    listViewApiName: "Contact_test_view",
    sortBy: NAME_FIELD,
    pageSize: 20
  })
  listview;

  get contacts() {
    if (this.listview.data) {
      return this.listview.data.records.records;
    }
    return null;
  }
}

 

 

7.wire를 통해 페이지의 정보를 받아오는 것은 CurrentPageReference 를 사용해서 진행했는데

해당 명령어를 통해 pageRef에 wire를 통해 값을 넘겨줬는데

사실 중요한 것은 CurrentPageReference로 현재 페이지의 데이터를 받아온다 정도인 것 같고

해당 값은 JOSN형태이기 때문에 JSON.stringify를 사용해야 한다 정도를 주의해야겠다.

//html
<template>
    <lightning-card title="wire Current Page Reference" icon-name="utility:animal_and_nature">
        <div class="slds-var-m-around_medium">
            <div style="padding: 10px">
                {currentPageReference}
            </div>
        </div>
    </lightning-card>
</template>

//js
import { LightningElement, wire } from "lwc";
import { CurrentPageReference } from "lightning/navigation";

export default class WirePageReference extends LightningElement {
  @wire(CurrentPageReference) pageRef;

  get currentPageReference() {
    return this.pageRef ? JSON.stringify(this.pageRef) : "";
  }
}

 

 

8.이번 코드에서는 사실 html은 큰 의미는 없고 js 부분이 중요한데

각각의 페이지들을 하나의 폴더에 몰아넣고 이름을 마음대로 정한 다음

기본 렌더링 페이지를 1로 설정했는데 render()의 역할이 아주 중요했다.

 

기본적으로 render()는 상태가 변할 경우 자동으로 실행되기 때문에

처음 showTemplate에 1을 할당한 순간 render가 실행되고

내부 조건에 따라 page 1,2,3을 출력하게 된다.

 

show1, 2, 3 또한 해당 showTemplate을 1,2,3으로 변경하는 역할이 전부였는데

이러한 변경 동작을 render()가 감지하기 때문에

단순히 상태 변경 작업만 하더라도 자동으로 페이지가 변경된다.

//html1
<template>
    <lightning-card title="wire Current Page Reference" icon-name="utility:animal_and_nature">
        <div class="slds-var-m-around_medium">
            <p> page1 </p>
            <p class="slds-var-m-vertical_small">
                <lightning-button label="template1" onclick={show1}></lightning-button>
                <lightning-button label="template2" onclick={show2}></lightning-button>
                <lightning-button label="template3" onclick={show3}></lightning-button>
            </p>
        </div>
    </lightning-card>
</template>

//html2
<template>
    <lightning-card title="wire Current Page Reference" icon-name="utility:animal_and_nature">
        <div class="slds-var-m-around_medium">
            <p> page2 </p>
            <p class="slds-var-m-vertical_small">
                <lightning-button label="template1" onclick={show1}></lightning-button>
                <lightning-button label="template2" onclick={show2}></lightning-button>
                <lightning-button label="template3" onclick={show3}></lightning-button>
            </p>
        </div>
    </lightning-card>
</template>

//html3
<template>
    <lightning-card title="wire Current Page Reference" icon-name="utility:animal_and_nature">
        <div class="slds-var-m-around_medium">
            <p> page3 </p>
            <p class="slds-var-m-vertical_small">
                <lightning-button label="template1" onclick={show1}></lightning-button>
                <lightning-button label="template2" onclick={show2}></lightning-button>
                <lightning-button label="template3" onclick={show3}></lightning-button>
            </p>
        </div>
    </lightning-card>
</template>

//js
import { LightningElement } from "lwc";
import template1 from "./template1.html";
import template2 from "./template2.html";
import template3 from "./template3.html";

export default class SwitchTemplates extends LightningElement {
  showTemplate = "1";

  render() {
    if (this.showTemplate === "1") return template1;
    else if (this.showTemplate === "2") return template2;
    else if (this.showTemplate === "3") return template3;
    return null;
  }

  show1() {
    this.showTemplate = "1";
  }

  show2() {
    this.showTemplate = "2";
  }

  show3() {
    this.showTemplate = "3";
  }
}

 

 

9.이번에는 유효성검사를 한다고 이상한 짓을 했는데

reportValidity와 checkValidity가 기본 html 제공 메서드라는 사실과

유효성이 어떻게 체크는 된다를 제외하면 엉망 진창인 것 같았다.

 

특히 답답한 부분은 유효성 검사 조건은 없었기 때문에

phone 부분에 한글이나 영어를 넣어도 문제가 없었고

이름 부분에도 숫자가 그냥 다 들어갔다.

 

편견없는 세일즈포스는 멋있을 수 있지만

실제 사용에서는 오타나 잘못된 입력을 막지 못하기 때문에 별로였다.

 

이번 코드에서 주목할 점은 조건(은 없어 보이지만)에 따른 아이디 생성과

생성 결과에 toast 반응하는 정도만 기존의 사용과 조금 다르다는 정도인 것 같다.

//html
<template>
    <lightning-card title="Validate custom LWC" icon-name="utility:animal_and_nature">
        <div class="slds-var-m-around_medium">
            <div style="padding: 10px">
                <lightning-input class="slds-p-around_medium" label="Name" name="accName" onchange={handleNameChange}
                    required></lightning-input>
                <lightning-input class="slds-p-around_medium" label="Phone" type="phone" name="accPhone"
                    onchange={handlePhoneChange} required></lightning-input>
                <br />
                <lightning-button class="slds-m-left_x-small" label="Save" variant="brand"
                    onclick={save}></lightning-button>
            </div>
        </div>
    </lightning-card>
</template>

//js
import { LightningElement } from "lwc";
import { createRecord } from "lightning/uiRecordApi";
import { ShowToastEvent } from "lightning/platformShowToastEvent";

export default class ValidateRecordEditForm extends LightningElement {
  accName;
  accPhone;

  handleNameChange(event) {
    this.accName = event.target.value;
  }
  handlePhoneChange(event) {
    this.accPhone = event.target.value;
  }

  save() {
    const isInputsCorrect = [
      ...this.template.querySelectorAll("lightning-input")
    ].reduce((validSoFar, inputField) => {
      inputField.reportValidity();
      return validSoFar && inputField.checkValidity();
    }, true);
    if (isInputsCorrect) {
      let fields = {
        Name: this.accName,
        Phone: this.accPhone
      };

      let objRecordInput = { apiName: "Account", fields };
      createRecord(objRecordInput)
        .then(() => {
          this.dispatchEvent(
            new ShowToastEvent({
              title: "Success",
              message: "Account Created Successfully",
              variant: "success"
            })
          );
        })
        .catch((error) => {
          this.dispatchEvent(
            new ShowToastEvent({
              title: "Error",
              message: error.message,
              variant: "error"
            })
          );
        });
    }
  }
}

 

 

10.empAPI는 subscription의 한도가 있는 것 같은데

멀쩡히 동작하던 코드가 작동하지 않게 되었다.

 

강의에서 보여주는 코드는 작동이 제대로 되지 않아

사족 주석들과 불필요한 부분들을 개선하며 주기적으로 동작 확인을 했는데

구독해제를 하지 않고 구독만 보내다 보니 문제가 발생했을 것 같다.

 

어쨌든 코드적으로는 이해를 하고 수정이 가능한 상태였고

구독 제한이 7이라면 실제로 쓰기에는 조금 애매한 기능이기 떄문에

이해하고 넘어간다는 점에서 만족해야겠다.

//html
<template>
    <lightning-card title="EmpApi Example" icon-name="custom:custom14">
        <div class="slds-m-around_medium">
            <p>
                Use the buttons below to subscribe and unsubscribe to a streaming
                channel!
            </p>
            <lightning-input label="Channel Name" value={channelName} onchange={handleChannelName}></lightning-input>
            <br />
            <lightning-button variant="success" label="Subscribe" title="Subscribe" onclick={handleSubscribe}
                disabled={isSubscribeDisabled} class="slds-m-left_x-small"></lightning-button>
            <lightning-button variant="destructive" label="Unsubscribe" title="Unsubscribe" onclick={handleUnsubscribe}
                disabled={isUnsubscribeDisabled} class="slds-m-left_x-small"></lightning-button>
            <br />
            <br />
            <p>Message : {message}</p>
        </div>
    </lightning-card>
</template>

//js
import { LightningElement, track } from "lwc";
import { subscribe, unsubscribe, onError } from "lightning/empApi";

export default class EmpAPIListner extends LightningElement {
  @track message = "";
  channelName = "/event/CustomEvent__e";
  isSubscribeDisabled = false;
  isUnsubscribeDisabled = true;

  subscription = {};

  handleChannelName(event) {
    this.channelName = event.target.value;
  }

  connectedCallback() {
    this.registerErrorListener();
  }

  handleSubscribe() {
    subscribe(this.channelName).then((response) => {
      this.subscription = response;
      this.message = response.data.payload.message__c;
      this.toggleSubscribeButton(true);
    });
  }

  handleUnsubscribe() {
    unsubscribe(this.subscription, () => {
      this.toggleSubscribeButton(false);
    });
  }

  toggleSubscribeButton(enableSubscribe) {
    this.isSubscribeDisabled = enableSubscribe;
    this.isUnsubscribeDisabled = !enableSubscribe;
  }

  registerErrorListener() {
    onError((error) => {
      console.log("Received error from server: ", JSON.stringify(error));
    });
  }
}

//send message with event
CustomEvent__e event = new CustomEvent__e(message__c = 'You are Awesome...');
Database.SaveResult result = EventBus.publish(event);

 

 

11.이번에는 드디어 유효성검사에 들어갔는데 console을 찍어보니 아래와 같은 모습을 볼 수 있었다.

결과적으로 보면 x에 문자열을 할당하고

replace /\+/g를 통해 숫자가 아닌 문자열을 모두 제거한 다음

3개, 3개, 4개로 각각의 자리를 채워주는 형태였는데

해당 값을 다시 조회해 2번째 인덱스에 (index 0에는 원본 값이 들어간다)

값이 있는 경우 앞의 세 글자에는 소괄호를 씌워주고 공백과 2번쨰 값을 넣어주며

다시 세번쨰 값이 존재하는지 확인 후 존재한다면 ‘-’를 추가해 앞의 숫자와 구분해줬다.

 

마지막으로 초기 match에서는 3, 3, 4개의 숫자만 들어갔기 때문에

초과된 숫자들은 최종 값 변환에서 사용되지 않아 더 이상의 숫자 입력이 제한된다.

 

추가적으로 html에서 패턴과 다를 경우 출력할 값들도 지정해줄 수 있었다.

//html
<template>
    <lightning-card title="Input Mask in Lightning Web Component" icon-name="utility:phone">
        <div style="padding: 10px">
            <lightning-input name="PhoneNumber" label="Phone Number" value={phoneNumber}
                pattern="^\(\d{3}\)\s\d{3}-\d{4}$" message-when-pattern-mismatch="Phone number is not valid"
                message-when-value-missing="Phone number is required" onchange={handleInputMask} required>
            </lightning-input>
        </div>
    </lightning-card>
</template>

//js
import { LightningElement } from "lwc";

export default class InputMask extends LightningElement {
  phoneNumber;

  handleInputMask(event) {
    const x = event.target.value
      .replace(/\D+/g, "")
      .match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
    console.log(x, x[1], x[2], x[3]);
    event.target.value = !x[2]
      ? x[1]
      : `(${x[1]}) ${x[2]}` + (x[3] ? `-${x[3]}` : ``);
  }
}

 

 

12.이번에는 값을 가져와서 뿌려줬는데 지금 다시 확인하니 index는 필요 없는 값인 것 같다.

 

class에서는 어떻게 필요한 데이터 형태를 잡아서 생성 후 쿼리로 받은 값을 보낼 수 있는지 알 수 있었고

html 부분 또한 쓸모없는 input value는 들어가 있지만

테이블에 참고할만한 데이터로 볼 수 있을 것 같다.

 

js부분도 엉망이었는데

체크를 해도 체크박스에는 변화가 없고

get 버튼을 누를 때 마다 배열에 값이 추가만 되기 때문에 누를 때 마다 값이 증가해버리는 현상이 발생했다.

 

자체적으로 boxList를 생성해 체크박스의 버튼 체크 상태도 관리해줬고

(기존에는 간직한 배열 내부의 정보만 수정해서 보이지는 않았다)

불필요한 주석과 코드를 제거하고 var를 수정하니 적당히 참고할만한 코드가 된 것 같다.

//apex class
public with sharing class wrapperTable {
    public wrapperTable(){}

    @AuraEnabled(cacheable=true)
	public static String getAccounts(){
        Integer rowIndex = 0;
        List<accountWrap> accWrapList = new List<accountWrap>();
		try {
            List<Account> accList = [SELECT Id, Name, Phone FROM Account limit 10];
            for(Account a : accList){
            accWrapList.add(new accountWrap(a.Id,a.Name,a.Phone,rowIndex));
            rowIndex++;
            }
            return JSON.serialize(accWrapList);
		} catch (Exception e) {
			throw new AuraHandledException(e.getMessage());
		}
	}
        //class로 type 잡아주는 모습
    public class accountWrap{
        public String Id;
        public String AccountName;
        public String Phone;
        public Boolean isSelected;
        public Integer index;
	    public accountWrap(String Id, String AccountName, String Phone, Integer index){
            this.Id = Id;
            this.AccountName = AccountName;
            this.Phone = Phone;
            this.isSelected = false;
            this.index = index;
		}
            
    }
}

//html
<template>
    <div style="padding: 10px">
        <div style="padding: 10px">
            <table class="
            slds-table
            slds-table_cell-buffer
            slds-table_bordered
            slds-table_col-bordered
          " border="1" width="100%">
                <thead>
                    <tr class="slds-line-height_reset">
                        <td>
                            <lightning-input type="checkbox" onchange={handleAllChange}></lightning-input>
                        </td>
                        <td style="font-weight: bold">Account Name</td>
                        <td style="font-weight: bold">Phone</td>
                    </tr>
                </thead>
                <template for:each={accList} for:item="acc">
                    <tr key={acc.Id} class="slds-hint-parent">
                        <td>
                            <lightning-input type="checkbox" checked={acc.isSelected} onchange={handleCheckChange}
                                value={acc.index}></lightning-input>
                        </td>
                        <td>{acc.AccountName}</td>
                        <td>{acc.Phone}</td>
                    </tr>
                </template>
            </table>
        </div>
        <br />
        <lightning-button variant="brand" label="Get Selected Accounts"
            onclick={getSelectedAccounts}></lightning-button>
    </div>
</template>

//js
import { wire, LightningElement } from "lwc";
import getAccList from "@salesforce/apex/wrapperTable.getAccounts";

export default class WrapperTable extends LightningElement {
  selectedAccounts = [];
  accList;
  @wire(getAccList)
  wiredRecord({ error, data }) {
    if (error) {
      let message = "Unknown error";
      if (Array.isArray(error.body)) {
        message = error.body.map((e) => e.message).join(", ");
      } else if (typeof error.body.message === "string") {
        message = error.body.message;
        console.log(message);
      }
    } else if (data) {
      this.accList = JSON.parse(data);
    }
  }
  handleAllChange(event) {
    const boxList = this.template.querySelectorAll("lightning-input");
    for (let i = 0; i < this.accList.length; i++) {
      this.accList[i].isSelected = event.target.checked;
      boxList[i + 1].checked = event.target.checked;
    }
  }
  handleCheckChange(event) {
    this.accList[event.target.value].isSelected = event.target.checked;
  }
  getSelectedAccounts() {
    for (let i = 0; i < this.accList.length; i++) {
      if (this.accList[i].isSelected) {
        this.selectedAccounts.push(this.accList[i]);
      }
    }
    //출력 내용 확인용
    console.log("Accounts Number : " + this.selectedAccounts.length);
    console.log("details : " + JSON.stringify(this.selectedAccounts));
    //처리가 끝난 후 초기화 해줘야 쌓이지 않는다
    this.selectedAccounts = new Array(0);
  }
}

 

 

13.chart.js는 되지 않는 것 같다.

애초에 된다고 우기는 코드에는 track도 들어있는데 사용하지 않고 있고

import로 추가적인 데이터를 받아오는 것도 아니고

이런저런 추가적 조치를 취했지만 되지 않는데

당장 과제가 급하기 때문에 코드만 저장해두고 나중에 돌아보던지 해야 할 것 같다.

 

추가로 static에 chart 파일까지 넣었는데 이 부분도 다음에 할 때 잊지 말아야겠다.

//html
<template>
    <div class="slds-grid slds-grid--align-center" style="background: white">
        <canvas width="400" height="400"></canvas>
    </div>
</template>

//js
import { LightningElement } from "lwc";
import chartjs from "@salesforce/resourceUrl/ChartJs";
import { loadScript } from "lightning/platformResourceLoader";
import { ShowToastEvent } from "lightning/platformShowToastEvent";

export default class PolarAreaChart extends LightningElement {
  chart;
  config = {
    type: "polarArea",
    data: {
      labels: ["Red", "Green", "Yellow", "Grey", "Blue"],
      datasets: [
        {
          label: "My First Dataset",
          data: [11, 16, 7, 3, 14],
          backgroundColor: [
            "rgb(255, 99, 132)",
            "rgb(75, 192, 192)",
            "rgb(255, 205, 86)",
            "rgb(201, 203, 207)",
            "rgb(54, 162, 235)"
          ]
        }
      ]
    }
  };

  renderedCallback() {
    Promise.all([loadScript(this, chartjs)])
      .then(() => {
        window.Chart.platform.disableCSSInjection = true;
        const canvas = document.createElement("canvas");
        this.template.querySelector("div.chart").appendChild(canvas);
        const ctx = this.template.querySelector("canvas");
        this.chart = new window.Chart(ctx, this.config);
      })
      .catch((error) => {
        this.dispatchEvent(
          new ShowToastEvent({
            title: "Error",
            message: error.message,
            variant: "error"
          })
        );
      });
  }
}

 

 

14.이제는 대부분의 코드를 슬쩍 보여주고 넘어가는 방식으로 강의가 진행되어서

class를 새로 만들거나 하는 모습이 없이 import 부분을 보고 직접 생성했는데

class를 만들지 않고 기존의 class에 넣고 해서 class가 조금 지저분하다.

 

사실 수정 부분은 기본적인 crud 느낌이기 때문에 별다를 것이 없지만

css적용이나 update를 하는방식에 대해 조금 참고할 정도는 되는 것 같다.

//apex class(하단의 getSingleContact만 사용된다
public with sharing class AccountController {
  @AuraEnabled(cacheable=true)
  public static List<Account> getAccList() {
    return [SELECT Id, Name FROM ACCOUNT ORDER BY CreatedDate DESC LIMIT 10];
  }

  @AuraEnabled(cacheable=true)
  public static List<Account> findAccList(String keyword) {
    String key = '%' + keyword + '%';
    return [
      SELECT Id, Name, Phone
      FROM ACCOUNT
      WHERE Name LIKE :key
      ORDER BY CreatedDate DESC
    ];
  }
  @AuraEnabled(cacheable=true)
  public static Account getSingleAccount() {
    return [
      SELECT Id, Name, Phone
      FROM ACCOUNT
      ORDER BY CreatedDate DESC
      LIMIT 1
    ];
  }

  @AuraEnabled(cacheable=true)
  public static Contact getSingleContact() {
    return [
      SELECT Id, FirstName
      FROM CONTACT
      ORDER BY CreatedDate DESC
      LIMIT 1
    ];
  }
}

//html
<template>
    <lightning-card title="Custom Inline Edit" icon-name="utility:edit">
        <div style="padding: 10px; font-weight: bold; width: 250px">
            <div class="slds-grid slds-gutters">
                <div class="slds-col">
                    <span>Hello I am</span>
                </div>
                <div class="slds-col">
                    <template if:false={editFirstName}>
                        <span style="border-bottom: 1px dotted black">{firstName}
                            <lightning-button-icon class="slds-float_right" icon-name="utility:edit"
                                alternative-text="Update First Name" title="Update First Name" variant="bare"
                                size="medium" onclick={handleFirstNameEdit}></lightning-button-icon>
                        </span>
                    </template>
                    <template if:true={editFirstName}>
                        <lightning-input name="fileExpirationDate" value={firstName} label=""
                            onchange={handleFirstNameChange}></lightning-input>
                        <lightning-button-icon class="slds-float_right" icon-name="utility:save"
                            alternative-text="Update First Name" title="Update First Name" variant="bare" size="large"
                            onclick={handleUpdateFirstName}></lightning-button-icon>
                    </template>
                </div>
            </div>
        </div>
    </lightning-card>
</template>

//js
import { LightningElement, track } from "lwc";
import getSingleContact from "@salesforce/apex/AccountController.getSingleContact";
import { updateRecord } from "lightning/uiRecordApi";
import ID_FIELD from "@salesforce/schema/Contact.Id";
import FIRSTNAME_FIELD from "@salesforce/schema/Contact.FirstName";

export default class EditName extends LightningElement {
  @track contact;
  @track contactId;
  @track firstName;
  @track error;
  @track editFirstName = false;
  connectedCallback() {
    getSingleContact()
      .then((result) => {
        this.contactId = result.Id;
        this.firstName = result.FirstName;
      })
      .catch((error) => {
        this.error = error;
      });
  }
  handleFirstNameEdit() {
    this.editFirstName = true;
  }
  handleFirstNameChange(event) {
    this.firstName = event.target.value;
  }
  handleUpdateFirstName() {
    const fields = {};
    fields[ID_FIELD.fieldApiName] = this.contactId;
    fields[FIRSTNAME_FIELD.fieldApiName] = this.firstName;

    const recordInput = { fields };

    updateRecord(recordInput)
      .then(() => {
        this.editFirstName = false;
      })
      .catch((error) => {
        console.log("Error updating date => " + error.body.message);
      });
  }
}

 

 

 

 

 

(1).백준 15780번 멀티탭 충분하니?는 연속해서 멀티탭을 사용하지 않는 조건이었는데

간단하게 보자면 홀수일 때는 (n+1)/2, 짝수일 때는 n/2개의 공간을 사용할 수 있다는 말이었다.

 

Math.round로 간단하게 해결할 수 있었다.

const input = `6 2
3 4`.split('\n').map(el => el.split(' ').map(Number))

let sum = 0

for(let i = 0 ; i < input[1].length ; i++){
    sum += Math.round(input[1][i]/2)
}

console.log(sum >= input[0][0] ? 'YES' : 'NO')

'회고' 카테고리의 다른 글

[수습일지] - 32  (0) 2023.04.27
[수습일지] - 31  (0) 2023.04.26
[수습일지] - 29  (0) 2023.04.24
[수습일지] - 28(주말)  (0) 2023.04.23
[수습일지] - 27(주말)  (0) 2023.04.22

+ Recent posts