소개
이런저런 다양한 강의에서 사용할 나만의 출석부가 필요.
일부 학교, 학원에서는 아직도 QR 출석부 시스템이 없거나, 너무 앞서나간(?) 듯한 다기능적 출석 시스템들이 존재함. 왜 이리도 쓸데없는 기능들을 많이 붙여놨던지...
각자의 휴대폰으로 스크린의 QR을 찍으면, 출석 시간이 자동으로 구글 시트에 기록되는 QR 인식 자동 출석 체크 시스템을 구현해 보기로 함.
(참고로 전 14기 앱스스크립트 수강자였고, 현재는 GPTs 수강 중. 공유드리면 유용하게 사용하실 분들이 계실 듯도 하여 그냥 잠시 시간 내서 공유 드림요~~~)
진행 방법
1.구글 Sheet 준비하기
'출석체크'라는 Sheet 하나를 만들기
'출석체크' 시트 구성
A열 : 이름
B열 : 전화번호
C열 : 출석시간
결과적으로 QR 체크를 하게 되면 C열에 출석 시간이 입력되는 출석 시스템
2.프롬프트
아래와 같이 Claude에서 시작한 것이 전쟁의 시작
*Prompt Apps Script를 이용해서 하나의 QR 코드를 만들고, 그걸 스캔해서 자동으로 출석여부가 확인되는 시스템을 만들거야. - html 페이지에 하나의 공통된 QR 코드를 생성 - 학생들이 공통의 QR코드를 스캔 - 학생의 전화번호를 자동으로 인식 - "출석체크" sheet에 학생들의 이름(A열), 전화번호(B열)는 존재 - "출석체크" sheet에 전화번호(B열)와 일치여부를 판단 - "출석체크" sheet에 출석시간(C열)에 출석시간이 자동으로 체크되는 시스템
*Claude 사용 방법 이 시스템을 구현하려면 다음 단계를 따르세요: Google 스프레드시트 준비: 새 Google 스프레드시트를 만들고 "출석체크"라는 시트를 만듭니다 A열에 학생 이름, B열에 전화번호를 입력합니다 C열은 출석 시간이 기록될 공간으로 비워둡니다 Apps Script 설정: 스프레드시트에서 확장 프로그램 > Apps Script를 클릭합니다 위에 제공된 Apps Script 코드를 main.gs 파일에 붙여넣습니다 새 HTML 파일을 만들기 위해 + 버튼을 클릭하고 'HTML 파일'을 선택합니다 파일 이름을 'Index'로 지정합니다 위에 제공된 HTML 코드를 Index.html 파일에 붙여넣습니다 웹 앱으로 배포: 배포 > 새 배포를 클릭합니다 액세스 권한을 '모든 사용자'로 설정합니다 배포 버튼을 클릭합니다 생성된 URL을 기록해 둡니다 사용하기: 웹 앱 URL에 접속하면 QR 코드가 표시됩니다 '생성' 탭에서 QR 코드를 보여주거나 공유합니다 학생들은 '스캔' 탭에서 자신의 전화번호를 입력하고 QR 코드를 스캔합니다 스캔 성공 시 스프레드시트에 자동으로 출석 시간이 기록됩니다 'QR 코드 생성' 탭은 선생님용, 'QR 코드 스캔' 탭은 학생용입니다 주요 기능 설명 QR 코드 생성: 웹 앱의 URL을 QR 코드로 생성하여 학생들이 스캔할 수 있게 합니다. 출석 처리: 학생이 자신의 전화번호를 입력하고 QR 코드를 스캔하면, 전화번호를 기반으로 스프레드시트에서 학생을 찾아 출석 시간을 기록합니다. 수동 출석: QR 스캔이 어려울 경우 전화번호를 직접 입력하여 출석 체크할 수 있는 옵션도 제공합니다. 사용자 피드백: 출석 처리 결과를 즉시 화면에 표시하여 학생들이 출석이 정상적으로 처리되었는지 확인할 수 있습니다. 이 시스템은 Google Apps Script의 무료 호스팅을 활용하므로 별도의 서버 비용 없이 운영할 수 있습니다.
Claude의 첫 기능 제안은 학생들이 직접 전화번호를 입력하는 시스템.
자세하게 how to do 까지 설명하는 Claude. 아까운 내 무료 Token량.
약간의 답답함으로 Negative 형 프롬프트가 바로 작렬!!!*Prompt 2. 학생이 직접 전화번호를 입력할 필요없이, QR코드 스캔할때 전화번호를 자동인식해서 출석 체크할 수는 없는거야?
Claude 두 가지 접근 방식이 가능합니다: - 개별 학생별 고유 QR 코드 생성: 각 학생마다 전화번호가 포함된 고유 QR 코드를 생성 - 시스템에서 사용자 인식: 기기나 브라우저 정보를 활용해 사용자를 식별 첫 번째 방식으로 시스템을 수정해 보겠습니다:
Prompt 3. 시스템에서 사용자 인식 - 이 방식은 가능한 거야? 코드 생성하지 말고 대답부터 해줘.
그렇게 Claude와 일련의 티키타카를 하다가 선택한 방법
Claude: 가장 현실적인 구현 방법은: 학생들이 최초 1회 본인의 전화번호를 등록 이후에는 브라우저 쿠키/로컬 스토리지에 저장된 정보를 활용해 자동 인식 모든 학생이 동일한 QR 코드를 스캔하지만, 스캔 시 쿠키/로컬에 저장된 전화번호 정보가 서버로 전송되어 출석 처리 이 방식은 학생들이 항상 같은 기기를 사용하는 경우에만 효과적이며, 기기 변경 시에는 다시 등록 과정이 필요합니다. 또한 브라우저 데이터를 삭제하면 재등록이 필요합니다.
최종 선택한 방식
사용자 인식 방식의 출석체크 시스템 구현 이제 시스템을 사용자 인식 방식으로 수정하여 구현했습니다. 이 시스템은 학생들이 처음 한 번만 전화번호를 입력하면, 이후에는 자동으로 인식하여 QR 코드 스캔만으로 출석이 체크됩니다. 작동 방식 최초 사용 시 전화번호 등록: 학생이 처음 시스템에 접속하면 자신의 전화번호를 입력합니다 입력한 전화번호는 학생의 브라우저 로컬 스토리지에 저장됩니다 이 정보는 해당 기기/브라우저에만 저장됩니다 (다른 기기에서는 재등록 필요) QR 코드 스캔으로 출석체크: 등록 후 QR 코드를 스캔하면 저장된 전화번호가 자동으로 서버로 전송됩니다 서버는 전화번호를 확인하고 출석 시간을 기록합니다 출석 결과가 즉시 화면에 표시됩니다 보안 및 검증: 시스템은 스프레드시트에 등록된 전화번호만 인식합니다 등록되지 않은 전화번호는 오류 메시지를 표시합니다 설치 및 사용 방법 Google 스프레드시트 준비: 새 Google 스프레드시트를 만들고 "출석체크"라는 시트를 만듭니다 A열에 학생 이름, B열에 전화번호를 입력합니다 C열은 출석 시간이 기록될 공간으로 비워둡니다 Apps Script 설정: 스프레드시트에서 확장 프로그램 > Apps Script를 클릭합니다 위에 제공된 Apps Script 코드를 붙여넣습니다 HTML 파일을 만들고 'Index'로 이름 지정한 후 HTML 코드를 붙여넣습니다 웹 앱으로 배포: 배포 > 새 배포를 클릭합니다 액세스 권한을 '모든 사용자'로 설정합니다 배포 버튼을 클릭하고 생성된 URL을 복사합니다 사용 방법: 관리자: '생성' 탭에서 QR 코드를 학생들에게 보여줍니다 학생 (최초 사용): '스캔' 탭에서 자신의 전화번호를 입력하고 등록합니다 학생 (이후 사용): QR 코드를 스캔하기만 하면 자동으로 출석이 체크됩니다 장점 사용 편의성: 학생들이 매번 전화번호를 입력할 필요 없이 QR 코드만 스캔하면 됩니다 빠른 출석체크: 간단한 스캔만으로 출석이 처리되어 시간이 절약됩니다 간단한 관리: 관리자는 하나의 QR 코드만 관리하면 됩니다 제한사항 기기 의존성: 학생들은 항상 같은 기기/브라우저를 사용해야 합니다 쿠키/로컬 스토리지 의존: 브라우저 데이터를 삭제하면 재등록이 필요합니다 인터넷 필요: QR 코드 스캔 및 출석체크를 위해 인터넷 연결이 필요합니다 이 시스템은 학생들이 자신의 스마트폰을 같은 브라우저로 계속 사용한다면 매우 편리한 방식입니다. 필요하다면 학생 정보 재설정 기능도 포함되어 있습니다.
아이고~~~! 아까운 내 무료 Token.. 설명은 그만 하라니까!!! 이제 안다고!!!
이때부터 코드가 필요한 경우를 제외하고는 코드/설명하지 말고, 질문에 대한 대답만 하라고 Prompt 진행다만, 문제는 전화번호를 처음에는 입력해야 하고, 이후에 지워질 수 있어서 QR 인식 자동 출석 시스템이 불편할 수도 있을 거 같음.
그래서, 수동 출석 기능 추가도 의뢰
Prompt. 첨부의 Apps Script에서 QR 코드 생성 탭 (관리자용), QR 코드 스캔(학생) -- 이 두 개의 탭에 더해서 '수동 출석' 탭을 하나 만들어줘. 학생들이 전화번호를 직접 수동으로 입력해서 출석체크하는 기능을 추가하는 거야.
Claude. 첨부의 Apps Script에서 QR 코드 생성 탭 (관리자용), QR 코드 스캔(학생) -- 이 두 개의 출석체크 시스템에 '수동 출석' 탭을 추가해드리겠습니다. 이 탭은 학생들이 QR 코드 없이도 직접 전화번호를 입력하여 출석체크할 수 있게 해줍니다.수정된 Index.html (수동 출석 탭 추가)클릭하여 코드 열기탭하여 열기수동 출석 탭을 추가했습니다. 주요 변경사항은 다음과 같습니다: 상단 탭 메뉴에 "수동 출석 (학생)" 탭을 추가했습니다. 새로운 탭에는 학생이 전화번호를 직접 입력하고 출석체크할 수 있는 인터페이스를 구현했습니다. 기존에 저장된 전화번호가 있으면 수동 출석 탭의 입력란에 자동으로 채워지도록 했습니다. 수동 출석 처리 기능은 기존의 출석체크 API를 그대로 활 용합니다. 수동 출석으로 인증 성공시, 해당 전화번호가 아직 로컬에 저장되지 않았다면 자동으로 저장되도록 했습니다. 전화번호 재설정시 수동 출석 탭의 입력란도 함께 초기화되도록 했습니다. 이 코드를 Apps Script의 Index.html 파일에 적용하면 즉시 수동 출석 기능을 사용할 수 있습니다. 기존의 Code.gs 파일은 수정할 필요가 없습니다.
이후, 무료 Token량 사용량을 넘어서 Claude와는 바이바이~~~
ChatGPT를 통해서 화면 구성이나, 탭 컬러 반전 효과 등등 이런저런 debuging 작업 진행함중간 과정을 모두 리뷰해볼 필요성은 없어서,
최종 제대로 동작하는 코드 공개~~!!
[Code.gs]// Google Apps Script 코드 // 웹 앱으로 배포할 도메인 앱 함수 function doGet() { return HtmlService.createHtmlOutputFromFile('Index') .setTitle('QR 코드 출석체크 시스템') .addMetaTag('viewport', 'width=device-width, initial-scale=1'); } // 출석 처리 함수 function markAttendance(phoneNumber) { try { // 스프레드시트 열기 const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getSheetByName("출석체크"); // 데이터 범위 가져오기 const dataRange = sheet.getDataRange(); const values = dataRange.getValues(); // 현재 시간 가져오기 const now = new Date(); const formattedTime = Utilities.formatDate(now, Session.getScriptTimeZone(), 'yyyy-MM-dd HH:mm:ss'); // 전화번호로 학생 찾기 let found = false; for (let i = 1; i < values.length; i++) { // 헤더 행 건너뛰기 if (values[i][1] === phoneNumber) { // B열(인덱스 1)에 전화번호가 있음 sheet.getRange(i + 1, 3).setValue(formattedTime); // C열(인덱스 2)에 출석 시간 기록 found = true; return { success: true, name: values[i][0], time: formattedTime }; } } if (!found) { return { success: false, message: "등록되지 않은 전화번호입니다: " + phoneNumber }; } } catch (error) { return { success: false, message: "오류가 발생했습니다: " + error.toString() }; } } // HTML 페이지에서 QR 코드 URL 생성 function getQRCodeUrl() { // 웹 앱의 URL을 가져옵니다 const scriptUrl = ScriptApp.getService().getUrl(); // 이 URL을 QR 코드에 인코딩합니다 return scriptUrl; } // 모든 학생 전화번호 가져오기 (전화번호 검증용) function getAllPhoneNumbers() { try { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getSheetByName("출석체크"); const dataRange = sheet.getDataRange(); const values = dataRange.getValues(); // 전화번호만 추출 (B열) const phoneNumbers = []; for (let i = 1; i < values.length; i++) { // 헤더 행 건너뛰기 if (values[i][1]) { // 빈 값이 아닌 경우에만 phoneNumbers.push(values[i][1]); } } return phoneNumbers; } catch (error) { console.error('전화번호 목록 가져오기 오류:', error); return []; } }
<!-- Index.html --> <!DOCTYPE html> <html> <head> <base target="_top"> <meta charset="utf-8"> <title>QR 코드 출석체크 시스템</title> <!-- QR 코드 생성 라이브러리 --> <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script> <!-- QR 코드 스캔 라이브러리 --> <script src="https://cdnjs.cloudflare.com/ajax/libs/html5-qrcode/2.3.4/html5-qrcode.min.js"></script> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 20px; text-align: center; } .container { max-width: 800px; margin: 0 auto; } .tab-content { display: none; padding: 20px; border: 1px solid #ccc; border-top: none; } .tab-content.active { display: block; } .tabs { display: flex; justify-content: center; margin-bottom: 20px; } .tab-button { background-color: #f2f2f2; border: 1px solid #ccc; padding: 10px 20px; cursor: pointer; font-size: 16px; } .tab-button.active { background-color: #4CAF50; color: white; } #qrcode { margin: 30px auto; width: 700px; height: 700px; } #reader { width: 100%; max-width: 500px; margin: 0 auto; } #result, #manualResult { margin: 20px 0; padding: 15px; background-color: #f8f9fa; border-radius: 5px; display: none; } .success { background-color: #d4edda; color: #155724; } .error { background-color: #f8d7da; color: #721c24; } .form-group { margin-bottom: 15px; } input[type="tel"] { padding: 8px; width: 200px; font-size: 16px; } button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background-color: #45a049; } #phoneRegistration { margin-top: 20px; padding: 15px; background-color: #e9ecef; border-radius: 5px; } #userInfo { display: none; margin-top: 10px; padding: 10px; background-color: #e2f3ff; border-radius: 5px; } </style> </head> <body> <div class="container"> <h1>라이언의 QR 코드 출석체크 시스템</h1> <div class="tabs"> <button class="tab-button" onclick="openTab('generate', event)">QR 코드 생성 (관리자)</button> <button class="tab-button active" id="scanTabBtn" onclick="openTab('scan', event)">QR 코드 스캔 (학생)</button> <button class="tab-button" onclick="openTab('manual', event)">수동 출석 (학생)</button> </div> <!-- QR 코드 생성 탭 (관리자용) --> <div id="generate" class="tab-content"> <h2>출석체크 QR 코드</h2> <p>스캔하면 출석 체크 화면으로 이동합니다.</p> <div id="qrcode"></div> <p><small>궁금한 점은 라이인에게 문의하세요.</small></p> </div> <!-- QR 코드 스캔 탭 (학생용) --> <div id="scan" class="tab-content active"> <h2>QR 코드 스캔</h2> <!-- 사용자 정보 표시 영역 --> <div id="userInfo"> <p><strong>등록된 출석정보</strong>: <span id="savedPhoneDisplay"></span> <br><br>1.카메라 기능 접속을 허용해주세요 <br>2.하단 카메라 화면에서 QR을 스캔하세요 <br>3.아래 출석 시간이 표시되면 출석 체크 완료!!</p> <button onclick="resetPhoneNumber()">정보 변경</button> </div> <!-- 전화번호 등록 영역 --> <div id="phoneRegistration"> <p>1.전화번호를 등록해 주세요. <br>2.카메라 기능 접속을 허용해주세요 <br>3.하단 카메라 화면에서 QR을 스캔하세요 <br>4.아래 출석 시간이 표시되면 출석 체크 완료!!</p> <div class="form-group"> <label for="phoneInput">전화번호:</label> <input type="tel" id="phoneInput" placeholder="010XXXXYYYY"> <button onclick="savePhoneNumber()">등록</button> </div> <p><small>* 전화번호는 이 기기에만 저장되며, 출석 확인에 사용됩니다.</small></p> </div> <!-- QR 스캐너 영역 --> <div id="reader"></div> <div id="result"></div> </div> <!-- 수동 출석 탭 (학생용) --> <div id="manual" class="tab-content"> <h2>수동 출석체크</h2> <p>QR 코드를 스캔할 수 없는 경우, 전화번호를 입력하고 '출석체크' 버튼을 클릭하세요.</p> <div class="form-group"> <label for="manualPhoneInput">전화번호:</label> <input type="tel" id="manualPhoneInput" placeholder="010XXXXYYYY"> <button onclick="manualAttendance()">출석체크</button> </div> <div id="manualResult"></div> </div> </div> <script> // 로컬 스토리지 키 const PHONE_STORAGE_KEY = 'studentPhoneNumber'; let validPhoneNumbers = []; // 유효한 전화번호 목록 // 페이지 로드 시 실행 window.onload = function() { // 저장된 전화번호 확인 checkSavedPhoneNumber(); // 유효한 전화번호 목록 가져오기 google.script.run .withSuccessHandler(function(phoneList) { validPhoneNumbers = phoneList; }) .getAllPhoneNumbers(); // QR 코드 생성 (관리자용) google.script.run.withSuccessHandler(function(url) { new QRCode(document.getElementById("qrcode"), { text: url, width: 700, height: 700, colorDark: "#000000", colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.H }); }).getQRCodeUrl(); // 수동 출석 탭에 저장된 전화번호 자동 입력 const savedPhone = localStorage.getItem(PHONE_STORAGE_KEY); if (savedPhone) { document.getElementById('manualPhoneInput').value = savedPhone; } // "QR 코드 스캔(학생)" 탭이 기본 활성화되어 있다면, 카메라 스캐너를 초기화합니다. if (document.getElementById('scan').classList.contains('active')) { setTimeout(initScanner, 500); } }; // 저장된 전화번호 확인 function checkSavedPhoneNumber() { const savedPhone = localStorage.getItem(PHONE_STORAGE_KEY); if (savedPhone) { // 저장된 전화번호가 있으면 표시 document.getElementById('userInfo').style.display = 'block'; document.getElementById('savedPhoneDisplay').textContent = savedPhone; document.getElementById('phoneRegistration').style.display = 'none'; } else { // 저장된 전화번호가 없으면 등록 양식 표시 document.getElementById('userInfo').style.display = 'none'; document.getElementById('phoneRegistration').style.display = 'block'; } } // 전화번호 저장 function savePhoneNumber() { const phoneInput = document.getElementById('phoneInput').value; if (!phoneInput) { alert('전화번호를 입력해주세요.'); return; } // 전화번호 형식 검증 (간단한 검증) const phoneRegex = /^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$/; if (!phoneRegex.test(phoneInput)) { alert('올바른 전화번호 형식이 아닙니다. (예: 01012345678)'); return; } // 등록된 전화번호인지 확인 if (validPhoneNumbers.length > 0 && !validPhoneNumbers.includes(phoneInput)) { alert('등록되지 않은 전화번호입니다. 확인 후 다시 시도해주세요.'); return; } // 로컬 스토리지에 저장 localStorage.setItem(PHONE_STORAGE_KEY, phoneInput); alert('전화번호가 등록되었습니다. 이제 QR 코드를 스캔하여 출석체크할 수 있습니다.'); // UI 업데이트 checkSavedPhoneNumber(); // 수동 출석 탭에도 전화번호 입력 document.getElementById('manualPhoneInput').value = phoneInput; // 전화번호 등록 후 자동으로 "QR 코드 스캔(학생)" 탭으로 전환 및 카메라 활성화 openTab('scan'); } // 전화번호 초기화 function resetPhoneNumber() { if (confirm('등록된 전화번호를 삭제하시겠습니까?')) { localStorage.removeItem(PHONE_STORAGE_KEY); alert('전화번호가 삭제되었습니다.'); // UI 업데이트 checkSavedPhoneNumber(); // 수동 출석 탭 전화번호 필드도 초기화 document.getElementById('manualPhoneInput').value = ''; } } function openTab(tabName, evt) { var i, tabcontent, tabbuttons; tabcontent = document.getElementsByClassName("tab-content"); for (i = 0; i < tabcontent.length; i++) { tabcontent[i].classList.remove("active"); } tabbuttons = document.getElementsByClassName("tab-button"); for (i = 0; i < tabbuttons.length; i++) { tabbuttons[i].classList.remove("active"); } document.getElementById(tabName).classList.add("active"); // 이벤트 객체가 있으면 사용, 없으면 tabName이 'scan'일 때 강제로 active 클래스 적용 if(evt) { evt.currentTarget.classList.add("active"); } else if(tabName === 'scan') { document.getElementById("scanTabBtn").classList.add("active"); } if (tabName === 'scan') { setTimeout(initScanner, 500); } } // QR 코드 스캐너 초기화 function initScanner() { // 이미 저장된 전화번호가 없으면 스캐너를 초기화하지 않음 if (!localStorage.getItem(PHONE_STORAGE_KEY)) { return; } const html5QrCode = new Html5Qrcode("reader"); const config = { fps: 10, qrbox: { width: 250, height: 250 } }; html5QrCode.start( { facingMode: "environment" }, config, onScanSuccess, onScanFailure ).catch(err => { console.error("QR 스캐너 초기화 오류:", err); showResult("카메라 접근에 실패했습니다. 카메라 권한을 확인해주세요.", false); }); function onScanSuccess(decodedText) { // 저장된 전화번호 가져오기 const phoneNumber = localStorage.getItem(PHONE_STORAGE_KEY); if (!phoneNumber) { showResult("등록된 전화번호가 없습니다. 먼저 전화번호를 등록해주세요.", false); return; } // 출석 처리 google.script.run .withSuccessHandler(attendanceSuccess) .withFailureHandler(attendanceFailure) .markAttendance(phoneNumber); // 스캐너 중지 html5QrCode.stop(); } function onScanFailure(error) { // 오류가 계속 출력되므로 여기서는 무시 console.log(`QR 코드 스캔 처리 중: ${error}`); } } // 수동 출석 처리 function manualAttendance() { const phoneNumber = document.getElementById('manualPhoneInput').value; if (!phoneNumber) { showManualResult("전화번호를 입력해주세요.", false); return; } // 전화번호 형식 검증 const phoneRegex = /^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$/; if (!phoneRegex.test(phoneNumber)) { showManualResult("올바른 전화번호 형식이 아닙니다. (예: 01012345678)", false); return; } // 출석 처리 google.script.run .withSuccessHandler(manualAttendanceSuccess) .withFailureHandler(manualAttendanceFailure) .markAttendance(phoneNumber); } // 수동 출석 성공 처리 function manualAttendanceSuccess(response) { if (response.success) { showManualResult(response.name + "님 " + response.time + " 출석 완료되었습니다.", true); // 입력한 전화번호 로컬에 저장 (아직 저장되지 않은 경우) if (!localStorage.getItem(PHONE_STORAGE_KEY)) { localStorage.setItem(PHONE_STORAGE_KEY, document.getElementById('manualPhoneInput').value); checkSavedPhoneNumber(); } } else { showManualResult(response.message, false); } } // 수동 출석 실패 처리 function manualAttendanceFailure(error) { showManualResult("오류가 발생했습니다: " + error, false); } // 출석 성공 처리 function attendanceSuccess(response) { if (response.success) { showResult(response.name + "님 " + response.time + " 출석 완료되었습니다.", true); } else { showResult(response.message, false); } } // 출석 실패 처리 function attendanceFailure(error) { showResult("오류가 발생했습니다: " + error, false); } // 결과 표시 function showResult(message, isSuccess) { const resultDiv = document.getElementById('result'); resultDiv.textContent = message; resultDiv.style.display = "block"; if (isSuccess) { resultDiv.classList.add("success"); resultDiv.classList.remove("error"); } else { resultDiv.classList.add("error"); resultDiv.classList.remove("success"); } // 5초 후 결과 숨기기 setTimeout(() => { resultDiv.style.display = "none"; // 스캐너 다시 시작 initScanner(); }, 5000); } // 수동 출석 결과 표시 function showManualResult(message, isSuccess) { const resultDiv = document.getElementById('manualResult'); resultDiv.textContent = message; resultDiv.style.display = "block"; if (isSuccess) { resultDiv.classList.add("success"); resultDiv.classList.remove("error"); } else { resultDiv.classList.add("error"); resultDiv.classList.remove("success"); } // 5초 후 결과 숨기기 setTimeout(() => { resultDiv.style.display = "none"; }, 5000); } </script> </body> </html>
최종 구현 화면
아래 화면은 강의실 스크린에 띄우고
학생들 전화번호 화면(탭)에서 처음 입력/저장
전화 번호 입력 후, QR 인식 출석 체크 화면
수동 입력 시스템
출석시간 확인 : 아래처럼 잘 기재됨
결과와 배운 점
노코드는 하나보다는 결국 두 개 이상의 AI툴을 사용하는 듯 함.
클로드 무료 사용자에게는 Claude > ChatGPT 순서가 가장 효율적인 듯 함(개인적 견해)
이번에도 Claude로 먼저 만들다가 무료 소진 다 하고, 만들어진 에러 덩어리 코드를 ChatGPT와 씨름하면서 하나하나 버그를 잡아감. 총 4시간 정도 소요된 듯함.
코딩을 하기 전에 기능 구현 가능 여부에 대해서 대답 먼저 하라고 하면, 가용한 Token량을 절약!
도움 받은 글 (옵션)
노가다는 진정한 땀과 노력!!
승리의 고지는 당신의 노가다의 양에 정복된다!!!(모라는 거야)
반응이 좋으면 다른 녀석들도 포스팅해 보겠음.