<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>SouthBottle</title>
    <link>https://bnpstudio.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 13 May 2026 15:03:08 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>SBP</managingEditor>
    <item>
      <title>테스트</title>
      <link>https://bnpstudio.tistory.com/782</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;철강 도메인의 복잡한 데이터 구조와 Oracle 11g라는 특정 버전의 제약, 그리고 1000줄에 달하는 대형 쿼리를 다루신다면, Copilot에게 매우 구체적인 &quot;컨텍스트(맥락)&quot;를 주입해야 합니다.&lt;br&gt;특히 11g 버전은 최신 19c/23c의 편리한 문법(예: FETCH FIRST, LATERAL JOIN, JSON 처리 등)을 사용할 수 없으므로, Copilot이 최신 문법을 제안하지 않도록 막는 것이 중요합니다.&lt;br&gt;아래 내용을 복사하여 프로젝트 루트의 .github/copilot-instructions.md 파일로 저장하세요.&lt;br&gt;  .github/copilot-instructions.md (추천 설정)&lt;br&gt;# Oracle 11g SQL Expert for Steel Industry&lt;br&gt;&lt;br&gt;당신은 철강 제조(Steel Manufacturing) 도메인의 숙련된 DBA이자 데이터 엔지니어입니다.&lt;br&gt;아래 규칙에 따라 Oracle 11g 환경에서 실행 가능한 최적화된 SQL을 작성하고 검증하세요.&lt;br&gt;&lt;br&gt;## 1. 환경 및 버전 제약 (Oracle 11g)&lt;br&gt;- **버전:** Oracle Database 11g Release 2&lt;br&gt;- **금지 문법:**&lt;br&gt;&amp;nbsp;&amp;nbsp;- `OFFSET ... FETCH NEXT` (대신 `ROWNUM` 서브쿼리 패턴 사용)&lt;br&gt;&amp;nbsp;&amp;nbsp;- `LATERAL`, `CROSS APPLY` (사용 금지)&lt;br&gt;&amp;nbsp;&amp;nbsp;- ANSI 표준의 `LISTAGG` 길이는 4000바이트 제한이 있음을 인지할 것.&lt;br&gt;- **날짜 처리:** 항상 `TO_DATE`, `TO_CHAR`를 명시적으로 사용하고 형식을 지정할 것 (예: `'YYYY-MM-DD HH24:MI:SS'`).&lt;br&gt;&lt;br&gt;## 2. 쿼리 구조 및 스타일 (대용량 쿼리 대응)&lt;br&gt;- **가독성 (WITH 절):** 1000줄 이상의 쿼리를 관리하기 위해, 중첩 서브쿼리(Inline View) 대신 **`WITH` 절(CTE)**을 적극 사용하여 논리 단계를 분리하세요.&lt;br&gt;- **조인 (Join):**&lt;br&gt;&amp;nbsp;&amp;nbsp;- 10개 이상의 테이블 조인 시, 가독성을 위해 **ANSI 표준 Join** (`INNER JOIN`, `LEFT JOIN`)을 우선 사용하세요.&lt;br&gt;&amp;nbsp;&amp;nbsp;- 단, 기존 레거시 코드가 Oracle 조인(`(+)`)을 따르는 경우 문맥에 맞춰 유지하세요.&lt;br&gt;- **Alias (별칭):**&lt;br&gt;&amp;nbsp;&amp;nbsp;- 테이블 별칭은 `a`, `b`, `c` 대신 의미 있는 약어를 사용하세요.&lt;br&gt;&amp;nbsp;&amp;nbsp;- 예: `OrderMaster -&amp;gt; om`, `ProductionLog -&amp;gt; pl`, `CoilDetail -&amp;gt; cd`&lt;br&gt;&lt;br&gt;## 3. 고급 분석 함수 활용&lt;br&gt;- `PARTITION BY`와 `ORDER BY`를 사용하는 분석 함수를 적극 활용하세요.&lt;br&gt;&amp;nbsp;&amp;nbsp;- **순위/중복 제거:** `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)`&lt;br&gt;&amp;nbsp;&amp;nbsp;- **이전/다음 값 참조:** `LAG()`, `LEAD()`&lt;br&gt;&amp;nbsp;&amp;nbsp;- **이동 평균/누적 합:** `SUM() OVER (...)`, `AVG() OVER (...)`&lt;br&gt;- 윈도우 함수 사용 시 `ROWS BETWEEN` 등 윈도우 프레임을 명확히 정의하여 의도를 드러내세요.&lt;br&gt;&lt;br&gt;## 4. 도메인 컨텍스트 (철강/제조)&lt;br&gt;- 다음 용어와 로직을 이해하고 변수명/주석에 반영하세요:&lt;br&gt;&amp;nbsp;&amp;nbsp;- **단위:** 후판(Plate), 열연(Hot Rolled), 냉연(Cold Rolled), 코일(Coil), 슬라브(Slab).&lt;br&gt;&amp;nbsp;&amp;nbsp;- **식별자:** `Heat_No` (출강 번호), `Coil_No`, `Order_No`.&lt;br&gt;&amp;nbsp;&amp;nbsp;- **공정:** 제강(Steelmaking), 연주(Casting), 압연(Rolling).&lt;br&gt;&amp;nbsp;&amp;nbsp;- **치수:** 두께(Thickness), 폭(Width), 길이(Length), 중량(Weight).&lt;br&gt;&lt;br&gt;## 5. 검증 및 안전 장치&lt;br&gt;- **카테시안 곱 방지:** 조인 조건이 누락되지 않았는지 항상 확인하세요.&lt;br&gt;- **성능:** 대용량 테이블(로그 성 데이터) 조회 시 파티션 키(예: `Work_Date`, `Prod_Date`)가 조건절에 포함되었는지 검증하세요.&lt;br&gt;- **주석:** 복잡한 비즈니스 로직(예: 특정 강종의 불량 처리 기준)이 들어간 곳은 반드시 한국어로 한 줄 주석을 남기세요.&lt;br&gt;&lt;br&gt;  이 설정의 핵심 포인트 설명&lt;br&gt;1. Oracle 11g Hallucination 방지&lt;br&gt;Copilot은 기본적으로 최신 문법을 선호합니다. 11g에서 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY 같은 페이징 쿼리를 짜면 에러가 납니다. 이를 방지하기 위해 ROWNUM 패턴을 강제했습니다.&lt;br&gt;2. 1000줄 쿼리 관리 (WITH 절 강조)&lt;br&gt;1000줄짜리 쿼리에서 FROM (SELECT ... FROM (SELECT ...)) 처럼 깊게 들어가면 수정이 불가능합니다.&lt;br&gt; * Instruction 효과: Copilot이 코드를 짤 때 &quot;먼저 데이터를 필터링하는 CTE를 만들고, 그걸 메인에서 조인하자&quot;는 식으로 사고하게 유도합니다.&lt;br&gt;3. 도메인 용어 학습 (철강)&lt;br&gt;철강 회사는 Heat No(용광로에서 나온 쇳물 번호)가 PK 역할을 하는 경우가 많습니다.&lt;br&gt; * Instruction 효과: Copilot이 변수명을 제안할 때 userId 같은 웹 용어 대신 heatNo, prodLine, thick 같은 도메인 용어를 우선시하게 됩니다.&lt;br&gt;4. 복잡한 조인과 윈도우 함수&lt;br&gt;LAG, LEAD, ROW_NUMBER는 시계열 데이터(공정 로그) 분석의 핵심입니다. 이를 명시함으로써 Copilot이 &quot;이전 공정의 두께와 현재 두께 차이 계산&quot; 같은 요청을 받았을 때 JOIN 대신 LAG를 사용하여 훨씬 효율적인 쿼리를 짜도록 유도합니다.&lt;br&gt;다음 단계로 제가 도와드릴까요?&lt;br&gt;혹시 자주 사용하는 **테이블 스키마(CREATE TABLE 문)**나 대표적인 컬럼명(한글/영문 매핑) 예시가 있다면 저에게 조금만 알려주세요. 위 규칙 파일에 &quot;스키마 맵(Schema Map)&quot; 섹션을 추가하여, Copilot이 테이블 이름과 컬럼을 틀리지 않고 정확하게 자동 완성하도록 업그레이드해 드리겠습니다.&lt;br&gt;또한 모든 앱의 전체 기능을 이용하려면 Gemini 앱 활동을 사용 설정하세요.&lt;br&gt;&lt;br&gt;&lt;/p&gt;</description>
      <author>SBP</author>
      <guid isPermaLink="true">https://bnpstudio.tistory.com/782</guid>
      <comments>https://bnpstudio.tistory.com/782#entry782comment</comments>
      <pubDate>Wed, 21 Jan 2026 21:25:55 +0900</pubDate>
    </item>
    <item>
      <title>ㅇㅇㅇ</title>
      <link>https://bnpstudio.tistory.com/781</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;광석(Ore)을 원료로 하여 **드라이어(건조)**와 전기로(제련/용해) 공정을 거쳐 중간재를 생산하는 제조 현장의 MES(Manufacturing Execution System) 흐름을 정리해 드리겠습니다.&lt;br&gt;요청하신 부서(구매, 자재, 생산, 품질, 판매, 설비, 환경)와 장비(계량대, 분석기, 설비)가 유기적으로 연결된 전체적인 시나리오입니다.&lt;br&gt;1. 전체 프로세스 개요 (High-Level Flow)&lt;br&gt;이 공정은 크게 **[입고] → [전처리(건조)] → [본공정(전기로)] → [출하]**로 이어지며, 전 과정에서 품질과 설비/에너지 데이터가 실시간으로 수집됩니다.&lt;br&gt;2. 단계별 상세 MES 흐름&lt;br&gt;Step 1. 구매 및 자재 입고 (Receiving)&lt;br&gt; * 관련 부서: 구매부서, 자재관리팀&lt;br&gt; * 주요 장비: 계량대(Weighbridge)&lt;br&gt; * 발주 정보 수신: 구매부서가 ERP에 입력한 '광석 발주 정보'가 MES로 연동됩니다.&lt;br&gt; * 입고 계량: 원료를 실은 트럭이 들어오면 계량대에서 중량을 측정합니다. (공차중량 - 실중량 = 순중량)&lt;br&gt;&amp;nbsp;&amp;nbsp; * MES 데이터: 입고 일시, 차량번호, 원료 종류, 공급사, 중량 데이터 자동 인터페이스.&lt;br&gt; * 야적/보관: 자재관리팀이 원료를 야적장(Silo 또는 Yard)에 하차합니다. MES상에서 해당 위치의 재고량이 증가합니다.&lt;br&gt;Step 2. 수입 검사 (Quality - Incoming)&lt;br&gt; * 관련 부서: 품질팀&lt;br&gt; * 주요 장비: 성분 분석기 (XRF 등)&lt;br&gt; * 시료 채취: 입고된 광석에서 샘플을 채취합니다.&lt;br&gt; * 성분 분석: 분석기를 통해 광석의 품위(성분 함량), 수분 등을 측정합니다.&lt;br&gt; * 데이터 전송: 분석 결과가 MES(또는 LIMS)로 자동 전송되어 해당 입고 Lot의 품질 등급이 결정됩니다. (기준 미달 시 반품 처리)&lt;br&gt;Step 3. 전처리 공정 (Dryer - 건조)&lt;br&gt; * 관련 부서: 생산팀&lt;br&gt; * 주요 설비: 드라이어(Dryer), 호퍼&lt;br&gt; * 작업 지시: 생산 계획에 따라 MES가 드라이어 공정에 작업 지시(투입량, 목표 수분율)를 내립니다.&lt;br&gt; * 공정 진행: 젖은 광석이 드라이어에 투입됩니다.&lt;br&gt; * 데이터 수집 (FEMS/DAS):&lt;br&gt;&amp;nbsp;&amp;nbsp; * 드라이어의 온도, 회전수, 체류 시간, 연료 사용량(LNG/Oil) 등이 실시간으로 MES에 기록됩니다.&lt;br&gt;&amp;nbsp;&amp;nbsp; * 건조 후 중량이 체크되어 수분 증발량이 계산됩니다.&lt;br&gt;Step 4. 본 공정 (Electric Furnace - 용해/환원)&lt;br&gt; * 관련 부서: 생산팀, 환경에너지팀&lt;br&gt; * 주요 설비: 전기로(EAF/SAF), 전극봉&lt;br&gt; * 배합 및 투입: 건조된 광석과 부원료(석회석, 환원제 등)가 배합비(Recipe)에 맞춰 전기로에 투입됩니다.&lt;br&gt; * 조업 관리:&lt;br&gt;&amp;nbsp;&amp;nbsp; * 전력 제어: 전기로의 핵심인 **전압, 전류, 전력량(kWh)**을 실시간 모니터링합니다. (피크 전력 관리)&lt;br&gt;&amp;nbsp;&amp;nbsp; * 전극봉 제어: 전극봉의 소모량과 위치(Level)를 추적합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp; * 환경 감시: 집진기 가동 현황, 배가스 온도 등을 모니터링하여 환경 규제를 준수하는지 확인합니다.&lt;br&gt; * 출탕 (Tapping): 녹은 쇳물(용탕)을 꺼내어 중간재(Ingot, Granule 등) 형태로 냉각/성형합니다.&lt;br&gt;Step 5. 공정 검사 및 제품 입고 (Quality &amp;amp; Production)&lt;br&gt; * 관련 부서: 품질팀, 생산팀&lt;br&gt; * 주요 장비: 분석기&lt;br&gt; * 성분 분석: 전기로에서 나온 시료를 채취하여 최종 성분을 분석기로 확인합니다.&lt;br&gt; * 등급 판정: MES가 분석 데이터를 기반으로 제품의 Grade(등급)를 자동 판정합니다.&lt;br&gt; * 제품 입고: 생산 실적(수량, 중량)을 확정하고 제품 창고 재고로 등록합니다. 이때 **Lot ID(생산이력)**가 생성되어 추적성(Traceability)이 확보됩니다.&lt;br&gt;Step 6. 판매 및 출하 (Shipping)&lt;br&gt; * 관련 부서: 판매/영업팀, 자재관리팀&lt;br&gt; * 주요 장비: 계량대&lt;br&gt; * 출하 지시: 판매부서의 주문(Order)에 따라 MES가 출하 지시를 생성합니다.&lt;br&gt; * 상차 및 계량: 제품을 트럭에 싣고 계량대를 통과하며 출하 중량을 확정합니다.&lt;br&gt; * 거래명세서 발행: 계량 데이터와 품질 성적서(COA)가 연동된 거래명세서가 발행되고, 재고가 차감됩니다.&lt;br&gt;3. 지원 프로세스 (설비 및 에너지)&lt;br&gt;이 모든 흐름의 배경에는 설비와 에너지 관리가 있습니다.&lt;br&gt; * 설비 관리 (Facility):&lt;br&gt;&amp;nbsp;&amp;nbsp; * 드라이어 모터, 전기로 변압기, 컨베이어 벨트 등의 가동 시간과 진동/온도 데이터를 수집합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp; * 이상 징후 발생 시 MES 알람 → 설비팀에 정비 요청(Work Order) 자동 생성.&lt;br&gt; * 환경/에너지 관리 (Environment &amp;amp; Energy):&lt;br&gt;&amp;nbsp;&amp;nbsp; * 전기로는 전력 소모가 극심하므로, 단위 생산량당 전력 소모량(원단위)을 실시간 분석합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp; * 피크 타임(전력 요금이 비싼 시간)을 피하거나 조절하는 스케줄링을 지원합니다.&lt;br&gt;요약: 데이터 흐름도 (Data Flow)&lt;br&gt;| 단계 | 입력(Input) | 주요 활동(Process) | 출력(Output) / 데이터 |&lt;br&gt;|---|---|---|---|&lt;br&gt;| 입고 | 광석, 구매오더 | 계량대 측정, 야적 | 입고중량, 원료재고, 수분값 |&lt;br&gt;| 전처리 | 젖은 광석 | 드라이어 가동 | 건조광석, 연료사용량, 건조온도 |&lt;br&gt;| 본공정 | 건조광석, 전력 | 전기로 용해 | 용탕(중간재), 전력사용량, 전극소모량 |&lt;br&gt;| 품질 | 시료(Sample) | 분석기 측정 | 성분데이터(Fe, Si 등), 합격/불합격 판정 |&lt;br&gt;| 출하 | 제품, 출하오더 | 계량대 측정, 상차 | 판매실적, 거래명세서, 제품재고 차감 |&lt;br&gt;이 흐름은 일반적인 광석 가공 공장의 표준 모델입니다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;</description>
      <author>SBP</author>
      <guid isPermaLink="true">https://bnpstudio.tistory.com/781</guid>
      <comments>https://bnpstudio.tistory.com/781#entry781comment</comments>
      <pubDate>Thu, 8 Jan 2026 15:58:43 +0900</pubDate>
    </item>
    <item>
      <title>ㅁㅁ</title>
      <link>https://bnpstudio.tistory.com/780</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;PROCEDURE SP_REJECT_WITH_HISTORY (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;p_id IN NUMBER,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;p_worker_id IN VARCHAR2&lt;br&gt;) IS&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;v_error_msg VARCHAR2(4000);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;v_data&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MAIN_TABLE%ROWTYPE;&lt;br&gt;BEGIN&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- 1. 현재 데이터 로드&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT * INTO v_data FROM MAIN_TABLE WHERE ID = p_id;&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- 2. 20여 가지 항목 검증 (메시지 누적)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- [예시 1: NULL 체크]&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IF v_data.TITLE IS NULL THEN &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;v_error_msg := v_error_msg || '[제목 누락] '; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END IF;&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- [예시 2: 복합 조건 체크]&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- 컬럼값이 1인데 다른 테이블에 데이터가 있는 경우&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DECLARE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;v_cnt NUMBER;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;BEGIN&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IF v_data.TYPE_CD = '1' THEN&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT COUNT(*) INTO v_cnt FROM SUB_TABLE WHERE REF_ID = p_id;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IF v_cnt &amp;gt; 0 THEN&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;v_error_msg := v_error_msg || '[타입1 데이터 중복] ';&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END IF;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END IF;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END;&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- 3. 검증 결과에 따른 처리&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IF v_error_msg IS NOT NULL THEN&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- A. 메인 테이블 상태 업데이트&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UPDATE MAIN_TABLE &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SET STATUS = 'REJECTED',&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; UPD_DT = SYSDATE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE ID = p_id;&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- B. 이력 테이블에 INSERT (반려 사유 포함)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT INTO STEP_HISTORY (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;HIST_SEQ, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TARGET_ID, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;STATUS, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;REMARK,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-- 여기에 'REJECT_REASON' 명칭 사용 권장&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WORKER_ID, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WORK_DT&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;) VALUES (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SEQ_HIST.NEXTVAL,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;p_id,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'REJECTED',&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TRIM(v_error_msg),&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;p_worker_id,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SYSDATE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END IF;&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;COMMIT;&lt;br&gt;END;&lt;br&gt;&lt;br&gt;&lt;/p&gt;</description>
      <author>SBP</author>
      <guid isPermaLink="true">https://bnpstudio.tistory.com/780</guid>
      <comments>https://bnpstudio.tistory.com/780#entry780comment</comments>
      <pubDate>Thu, 27 Nov 2025 11:09:42 +0900</pubDate>
    </item>
    <item>
      <title>ㅎㅎㅎ</title>
      <link>https://bnpstudio.tistory.com/779</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;  모바일 웹 시스템 인증 개선 제안 (Email/SMS 인증)&lt;br&gt;SSO(Single Sign-On)를 사용하지 못하는 모바일 웹 시스템에 이메일(Email) 및 SMS 인증 번호를 통한 인증 방식을 도입하는 것은 사용자 편의성과 보안을 개선할 수 있는 좋은 방안입니다. 아래에서는 필요한 DB 추가 항목과 인증 프로세스를 설명드립니다.&lt;br&gt;1.   데이터베이스(DB) 추가 항목&lt;br&gt;인증 번호 발송 및 확인을 위해 임시 정보를 저장할 별도의 테이블을 생성하는 것을 권장합니다. 기존 사용자 정보 테이블(User Table)에 추가할 수도 있지만, 보안 및 관리의 용이성을 위해 분리하는 것이 일반적입니다.&lt;br&gt;| 테이블 명 | 항목 (Column Name) | 데이터 타입 | 설명 | 비고 |&lt;br&gt;|---|---|---|---|---|&lt;br&gt;| 인증_로그 (Auth_Log) | id | INT (PK) | 고유 식별자 | 자동 증가 |&lt;br&gt;|&amp;nbsp;&amp;nbsp;| user_identifier | VARCHAR | 인증 대상 사용자 식별자 (Email 또는 Phone Number) | 인덱스 필요 |&lt;br&gt;|&amp;nbsp;&amp;nbsp;| auth_code | VARCHAR/INT | 발급된 인증 번호 (6자리 등) |&amp;nbsp;&amp;nbsp;|&lt;br&gt;|&amp;nbsp;&amp;nbsp;| code_type | VARCHAR | 인증 종류 ('EMAIL' 또는 'SMS') |&amp;nbsp;&amp;nbsp;|&lt;br&gt;|&amp;nbsp;&amp;nbsp;| created_at | DATETIME | 인증 번호 발급 시각 |&amp;nbsp;&amp;nbsp;|&lt;br&gt;|&amp;nbsp;&amp;nbsp;| expires_at | DATETIME | 인증 번호 만료 시각 (예: 발급 후 5분) |&amp;nbsp;&amp;nbsp;|&lt;br&gt;|&amp;nbsp;&amp;nbsp;| is_verified | BOOLEAN | 인증 성공 여부 | 기본값: FALSE |&lt;br&gt;2. ⚙️ 인증 프로세스 (Email/SMS 공통)&lt;br&gt;인증 번호를 요청하고 확인하는 과정은 아래와 같은 단계로 진행됩니다.&lt;br&gt;1단계: 사용자 인증 요청&lt;br&gt; * 사용자 입력: 사용자가 로그인 화면에서 이메일 주소 또는 휴대폰 번호를 입력하고 '인증번호 받기'를 요청합니다.&lt;br&gt; * 서버 검증: 서버는 입력된 식별자가 등록된 사용자인지 확인합니다.&lt;br&gt; * 인증번호 생성: 서버는 랜덤한 6자리 숫자 등의 인증 번호를 생성합니다.&lt;br&gt;2단계: 인증번호 발송 및 DB 저장&lt;br&gt; * DB 저장: 서버는 생성된 인증 번호, 사용자 식별자, 발급 시각, 만료 시각을 인증_로그 테이블에 저장합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp; * 팁: 보안을 위해 기존에 만료되지 않은 해당 사용자의 인증 번호 레코드는 삭제하거나 '만료됨'으로 처리합니다.&lt;br&gt; * 발송: 서버는 이메일 전송 API 또는 SMS 전송 API를 이용해 사용자에게 인증 번호를 발송합니다.&lt;br&gt;3단계: 사용자 인증번호 입력&lt;br&gt; * 사용자 입력: 사용자는 수신한 인증 번호를 화면의 입력창에 입력하고 '인증 확인'을 요청합니다.&lt;br&gt;4단계: 인증번호 확인 및 로그인&lt;br&gt; * DB 조회: 서버는 인증_로그 테이블에서 사용자 식별자에 해당하는 가장 최근에 발급된 인증 번호 기록을 조회합니다.&lt;br&gt; * 유효성 검증:&lt;br&gt;&amp;nbsp;&amp;nbsp; * 일치 여부 확인: 사용자가 입력한 번호와 DB의 auth_code가 일치하는지 확인합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp; * 만료 시각 확인: DB의 expires_at 시각이 현재 시각보다 이후인지 확인하여 만료되지 않았는지 검증합니다.&lt;br&gt; * 인증 성공/실패:&lt;br&gt;&amp;nbsp;&amp;nbsp; * 성공: 두 가지 검증을 모두 통과하면 해당 레코드의 is_verified를 TRUE로 업데이트하고, 사용자에게 로그인 성공 처리(예: 세션 생성 또는 JWT 발급)를 진행합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp; * 실패: 일치하지 않거나 만료된 경우 인증 실패 메시지를 사용자에게 표시합니다.&lt;br&gt;3.  ️ 보안 고려 사항&lt;br&gt; * 만료 시간 설정: 인증 번호는 반드시 5분 이내 등의 짧은 만료 시간을 설정하여 재사용을 방지해야 합니다.&lt;br&gt; * 재시도 제한: 짧은 시간 내에 인증 요청 횟수나 인증번호 입력 실패 횟수에 제한(Throttle)을 두어 무차별 대입 공격(Brute Force Attack)을 방지해야 합니다.&lt;br&gt; * 로그 활용: 인증_로그 테이블은 보안 감사 및 이상 행위 분석에도 활용될 수 있습니다.&lt;br&gt;이 프로세스를 통해 SSO 없이도 안전하고 편리한 모바일 웹 인증 시스템을 구축할 수 있습니다.&lt;br&gt;혹시 이 내용을 기반으로 제안 메일 초안 작성을 도와드릴까요?&lt;br&gt;&lt;br&gt;&lt;/p&gt;</description>
      <author>SBP</author>
      <guid isPermaLink="true">https://bnpstudio.tistory.com/779</guid>
      <comments>https://bnpstudio.tistory.com/779#entry779comment</comments>
      <pubDate>Mon, 24 Nov 2025 16:49:42 +0900</pubDate>
    </item>
    <item>
      <title>111</title>
      <link>https://bnpstudio.tistory.com/778</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;/* 깜빡임 On 상태 */&lt;br&gt;.blink-on {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-color: #ffcdd2 !important; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color: #c62828 !important;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;font-weight: bold;&lt;br&gt;}&lt;br&gt;&lt;br&gt;/* 깜빡임 Off 상태 (기본 상태로 돌아가도록 설정) */&lt;br&gt;.blink-off {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-color: transparent !important;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color: inherit !important;&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;class Blinker {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {string} gridId - jqxGrid의 jQuery 선택자 ID (예: '#jqxgrid')&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; */&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;constructor(gridId) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.$grid = $(gridId);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// { 'row_field': { intervalId: number, isBlinkOn: boolean, blinkIntervalMs: number, key: string } }&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.timers = {};&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 현재 깜빡임을 시작한 모든 row_field 키를 저장합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.activeKeys = new Set();&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.isGlobalBlinkOn = false; // 그리드 전체의 공통 깜빡임 상태 (매 렌더링 시 사용)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.globalIntervalId = null; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * cellclassname 함수에 사용될 클로저입니다. &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * 그리드가 셀을 렌더링할 때마다 호출되어 클래스 이름을 반환합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * 주의: 이 함수는 컬럼 정의 시 딱 한 번만 설정됩니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; */&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;getCellClassNameHandler(dataField) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return (row, column, value) =&amp;gt; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const key = `${row}_${dataField}`;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 이 셀이 깜빡임 대상인지, 그리고 현재 깜빡임이 ON 상태인지 확인합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (this.activeKeys.has(key) &amp;amp;&amp;amp; this.isGlobalBlinkOn) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return 'blink-on';&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 깜빡임 대상이 아니거나, 현재 OFF 상태이면 클래스를 제거합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return 'blink-off'; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * 깜빡임을 시작합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {number} rowIndex - 행 인덱스&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {string} dataField - 컬럼의 datafield 이름&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {number} [blinkIntervalMs=500] - 깜빡이는 주기 (밀리초)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {number} [durationMs=null] - 전체 지속 시간 (null이면 영구 지속)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; */&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;start(rowIndex, dataField, blinkIntervalMs = 500, durationMs = null) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const key = `${rowIndex}_${dataField}`;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 이미 해당 셀이 깜빡이는 중이라면 정지합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.stop(rowIndex, dataField);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 깜빡임 대상 목록에 추가&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.activeKeys.add(key);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 아직 전체 깜빡이 Interval이 시작되지 않았다면 시작합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (this.globalIntervalId === null) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.globalIntervalId = setInterval(() =&amp;gt; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.isGlobalBlinkOn = !this.isGlobalBlinkOn; // 상태 토글&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// **핵심**: 그리드를 강제로 다시 렌더링하여 cellclassname 함수를 재호출합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.$grid.jqxGrid('render'); &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}, blinkIntervalMs);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(&quot;Global Blink Interval Started.&quot;);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 지속 시간이 설정된 경우, 일정 시간 후 stop 함수 호출&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (durationMs !== null) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setTimeout(() =&amp;gt; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.stop(rowIndex, dataField);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}, durationMs);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(`Blinking started for cell (${rowIndex}, ${dataField}).`);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * 특정 셀의 깜빡임을 정지하고 기본 상태로 복구합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {number} rowIndex - 행 인덱스&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {string} dataField - 컬럼의 datafield 이름&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; */&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stop(rowIndex, dataField) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const key = `${rowIndex}_${dataField}`;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (this.activeKeys.has(key)) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 깜빡임 대상 목록에서 제거&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.activeKeys.delete(key);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 스타일을 즉시 제거하기 위해 렌더링 한 번 더 호출&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.$grid.jqxGrid('render'); &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 깜빡임 대상이 더 이상 없다면 전역 Interval도 정지합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (this.activeKeys.size === 0) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clearInterval(this.globalIntervalId);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.globalIntervalId = null;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.isGlobalBlinkOn = false;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(&quot;Global Blink Interval Stopped.&quot;);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(`Blinking stopped for cell (${rowIndex}, ${dataField}).`);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;br&gt;// 1. Blinker 인스턴스 생성&lt;br&gt;const blinkManager = new Blinker('#jqxgrid');&lt;br&gt;&lt;br&gt;// 2. jqxGrid 초기화 시 cellclassname 함수로 등록&lt;br&gt;$(&quot;#jqxgrid&quot;).jqxGrid({&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// ... 설정&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;columns: [&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text: '가격',&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;datafield: 'price',&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width: 150,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//   중요: price 컬럼에 핸들러 함수를 등록합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cellclassname: blinkManager.getCellClassNameHandler('price') &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text: '재고',&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;datafield: 'unitsinstock',&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width: 80,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//   중요: unitsinstock 컬럼에도 핸들러 함수를 등록합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cellclassname: blinkManager.getCellClassNameHandler('unitsinstock') &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// ...&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]&lt;br&gt;});&lt;br&gt;// 1. (가격 컬럼) 5번 행을 400ms 주기로 10초간 깜빡이게 시작&lt;br&gt;blinkManager.start(5, 'price', 400, 10000); &lt;br&gt;&lt;br&gt;// 2. (재고 컬럼) 8번 행을 600ms 주기로 영구히 깜빡이게 시작&lt;br&gt;blinkManager.start(8, 'unitsinstock', 600); &lt;br&gt;&lt;br&gt;// 3. 2번 셀의 깜빡임을 수동으로 정지&lt;br&gt;setTimeout(() =&amp;gt; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;blinkManager.stop(8, 'unitsinstock');&lt;br&gt;}, 5000);&lt;br&gt;&lt;br&gt;&lt;/p&gt;</description>
      <author>SBP</author>
      <guid isPermaLink="true">https://bnpstudio.tistory.com/778</guid>
      <comments>https://bnpstudio.tistory.com/778#entry778comment</comments>
      <pubDate>Mon, 17 Nov 2025 09:11:06 +0900</pubDate>
    </item>
    <item>
      <title>11ㅉㄴ</title>
      <link>https://bnpstudio.tistory.com/777</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;/* 깜빡임 On 상태 */&lt;br&gt;.blink-on {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-color: #ffcdd2 !important; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color: #c62828 !important;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;font-weight: bold;&lt;br&gt;}&lt;br&gt;&lt;br&gt;/* 깜빡임 Off 상태 */&lt;br&gt;.blink-off {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-color: transparent !important;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;color: inherit !important;&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;class CellBlinkManager {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {string} gridId - jqxGrid의 jQuery 선택자 ID (예: '#jqxgrid')&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; */&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;constructor(gridId) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.$grid = $(gridId);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// { 'row_field': { intervalId: number, isBlinkOn: boolean, blinkIntervalMs: number } } 형태로 저장&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.timers = {}; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * 특정 셀의 깜빡임을 시작합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * 이미 깜빡이는 셀이라면 멈춘 후 다시 시작합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {number} rowIndex - 행 인덱스&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {string} dataField - 컬럼의 datafield 이름&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {number} [blinkIntervalMs=500] - 깜빡이는 주기 (밀리초)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {number} [durationMs=null] - 전체 지속 시간 (null이면 영구 지속)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; */&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;start(rowIndex, dataField, blinkIntervalMs = 500, durationMs = null) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const key = `${rowIndex}_${dataField}`;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 이미 실행 중이면 중지합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.stop(rowIndex, dataField);&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let isBlinkOn = false;&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const toggleCellBlink = () =&amp;gt; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const className = isBlinkOn ? 'blink-on' : 'blink-off';&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.$grid.jqxGrid(&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'setcellproperty', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rowIndex, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dataField, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'cellclassname', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;className&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isBlinkOn = !isBlinkOn;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Interval 시작&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const intervalId = setInterval(toggleCellBlink, blinkIntervalMs);&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 타이머 상태 저장&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.timers[key] = {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;intervalId: intervalId,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isBlinkOn: false,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;blinkIntervalMs: blinkIntervalMs&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(`Blinking started for cell (${rowIndex}, ${dataField}).`);&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 지속 시간이 설정된 경우, 타이머 설정&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (durationMs !== null) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setTimeout(() =&amp;gt; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.stop(rowIndex, dataField);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}, durationMs);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/**&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * 특정 셀의 깜빡임을 정지하고 스타일을 기본 상태로 복구합니다.&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {number} rowIndex - 행 인덱스&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; * @param {string} dataField - 컬럼의 datafield 이름&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; */&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stop(rowIndex, dataField) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const key = `${rowIndex}_${dataField}`;&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (this.timers[key]) {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 1. Interval 정지&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clearInterval(this.timers[key].intervalId);&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 2. 클래스를 제거하여 기본 상태로 복구&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.$grid.jqxGrid(&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'setcellproperty', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rowIndex, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dataField, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'cellclassname', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'' // 클래스 제거&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 3. 타이머 기록 삭제&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;delete this.timers[key];&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(`Blinking stopped for cell (${rowIndex}, ${dataField}).`);&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br&gt;}&lt;br&gt;&lt;br&gt;&lt;/p&gt;</description>
      <author>SBP</author>
      <guid isPermaLink="true">https://bnpstudio.tistory.com/777</guid>
      <comments>https://bnpstudio.tistory.com/777#entry777comment</comments>
      <pubDate>Mon, 17 Nov 2025 08:00:24 +0900</pubDate>
    </item>
    <item>
      <title>test</title>
      <link>https://bnpstudio.tistory.com/776</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;html&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;head&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;title&amp;gt;jQxGrid Periodic Style Change&amp;lt;/title&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;jqwidgets/styles/jqx.base.css&quot; type=&quot;text/css&quot; /&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;style&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; /*   적용할 커스텀 스타일 정의 */&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .highlight-style {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; background-color: #ffeb3b !important; /* 노란색 강조 */&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; color: #333 !important;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; font-weight: bold;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .alert-style {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; background-color: #f44336 !important; /* 빨간색 경고 */&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; color: white !important;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;/style&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;scripts/jquery-1.11.1.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxcore.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxdata.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxbuttons.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxscrollbar.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxmenu.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxgrid.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxgrid.selection.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;script type=&quot;text/javascript&quot; src=&quot;scripts/demos.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; $(document).ready(function () {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 그리드 및 함수 초기화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; initGrid();&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // --- 스타일 변경 실행 예시 ---&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 1. 행 인덱스 0의 'name' 컬럼에 'highlight-style'을 1초 동안 5초 주기로 반복 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; applyPeriodicStyle(&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; '#jqxgrid', // 그리드 셀렉터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 0, // 행 인덱스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 'name', // 데이터 필드 (컬럼)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 'highlight-style', // 적용할 CSS 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1000, // 스타일 지속 시간 (ms)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 5000 // 반복 주기 (ms)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; );&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 2. 행 인덱스 2의 'value' 컬럼에 'alert-style'을 0.5초 동안 3초 주기로 반복 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; applyPeriodicStyle(&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; '#jqxgrid',&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 'value',&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 'alert-style',&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 500,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3000&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; );&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; });&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; /**&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;* jqxGrid의 특정 셀에 스타일을 주기적으로 변경하고 복구하는 공통 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {string} gridSelector - jqxGrid의 jQuery 셀렉터 (예: '#jqxgrid')&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {number} rowIndex - 스타일을 변경할 행의 인덱스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {string} dataField - 스타일을 변경할 컬럼의 datafield 이름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {string} styleClass - 적용할 CSS 클래스 이름 (예: 'highlight-style')&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {number} duration - 스타일을 유지할 시간 (밀리초)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;* @param {number} repeatInterval - 스타일 변경을 반복할 주기 (밀리초)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;* @returns {number} setInterval ID - 반복을 중지할 때 사용할 ID (clearInterval(id))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;*/&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; function applyPeriodicStyle(gridSelector, rowIndex, dataField, styleClass, duration, repeatInterval) {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const $grid = $(gridSelector);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 스타일 적용 및 복구를 위한 내부 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const toggleStyle = (style) =&amp;gt; {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // styleClass 필드가 없으면 동적으로 추가하여 스타일을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // setcellvalue의 dataField 인자는 실제로 업데이트할 필드 이름이어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; $grid.jqxGrid('setcellvalue', rowIndex, 'styleClass', style);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 스타일 변경 후 그리드 렌더링을 갱신하여 즉시 반영&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; $grid.jqxGrid('refreshdata');&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; };&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 주기적인 실행 로직&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; const intervalId = setInterval(() =&amp;gt; {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 1. 스타일 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; toggleStyle(styleClass);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 2. 지정된 지속 시간 후 스타일 복구 (원래대로)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; setTimeout(() =&amp;gt; {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; toggleStyle('');&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }, duration);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }, repeatInterval);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; console.log(`Periodic style applied to Row: ${rowIndex}, Column: ${dataField}. Interval ID: ${intervalId}`);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; return intervalId;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // --- 그리드 초기화 함수 ---&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; function initGrid() {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var data = new Array();&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var firstNames = [&quot;Andrew&quot;, &quot;Nancy&quot;, &quot;Shelley&quot;, &quot;Regina&quot;, &quot;Yoshi&quot;, &quot;Antoni&quot;];&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var lastNames = [&quot;Fuller&quot;, &quot;Davolio&quot;, &quot;Burke&quot;, &quot;Murphy&quot;, &quot;Tate&quot;, &quot;Nakata&quot;];&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; for (var i = 0; i &amp;lt; 5; i++) {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var row = {};&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; row[&quot;name&quot;] = firstNames[i];&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; row[&quot;last&quot;] = lastNames[i];&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; row[&quot;value&quot;] = Math.floor(Math.random() * 100);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; //   styleClass 필드를 초기화합니다. 이 필드는 그리드 행의 현재 스타일 클래스를 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; row[&quot;styleClass&quot;] = '';&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; data[i] = row;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var source = {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; localdata: data,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; datafields: [&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; { name: 'name', type: 'string' },&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; { name: 'last', type: 'string' },&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; { name: 'value', type: 'number' },&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; //   스타일 제어를 위해 추가된 임시 필드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; { name: 'styleClass', type: 'string' }&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ],&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; datatype: &quot;array&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; };&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var dataAdapter = new $.jqx.dataAdapter(source);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; $(&quot;#jqxgrid&quot;).jqxGrid({&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; width: 600,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; source: dataAdapter,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; selectionmode: 'none',&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; columns: [&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; text: 'Name',&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; datafield: 'name',&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; width: 200,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; //   cellclassname 콜백 함수를 사용하여 스타일 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; cellclassname: (row, column, value, data) =&amp;gt; data.styleClass&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; },&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; { text: 'Last Name', datafield: 'last', width: 200 },&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; text: 'Value',&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; datafield: 'value',&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; width: 200,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; //   cellclassname 콜백 함수를 사용하여 스타일 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; cellclassname: (row, column, value, data) =&amp;gt; data.styleClass&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; });&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;/head&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;body&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;lt;div id='jqxgrid'&amp;gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;/body&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;/html&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>SBP</author>
      <guid isPermaLink="true">https://bnpstudio.tistory.com/776</guid>
      <comments>https://bnpstudio.tistory.com/776#entry776comment</comments>
      <pubDate>Thu, 13 Nov 2025 13:41:31 +0900</pubDate>
    </item>
    <item>
      <title>jqxgrid 특정행 백그라운드 변경</title>
      <link>https://bnpstudio.tistory.com/775</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;jQxGrid 특정 행 배경색 변경&lt;/title&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;jqwidgets/styles/jqx.base.css&quot; type=&quot;text/css&quot; /&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;scripts/jquery-1.11.1.min.js&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxcore.js&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxdata.js&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxgrid.js&quot;&gt;&lt;/script&gt;
    
    &lt;style&gt;
        /* 1. 강조할 행의 CSS 클래스 정의 */
        .highlighted-row {
            background-color: #E0F0FF; /* 밝은 파란색 */
        }
        
        /* 팁: 셀 경계선과 텍스트 색상을 일관되게 유지하기 위해 추가 스타일을 적용할 수 있습니다. */
        .highlighted-row &gt; div {
            /* 텍스트 색상 변경 예시 */
            /* color: #0000FF; */ 
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;

    &lt;div id=&quot;jqxgrid&quot;&gt;&lt;/div&gt;

    &lt;script type=&quot;text/javascript&quot;&gt;
        $(document).ready(function () {
            const data = [
                { id: 1, name: &quot;Apple&quot;, price: 100 },
                { id: 2, name: &quot;Banana&quot;, price: 200 }, // 이 행의 배경색을 변경할 예정
                { id: 3, name: &quot;Cherry&quot;, price: 300 }
            ];

            const source = {
                localdata: data,
                datafields: [
                    { name: 'id', type: 'number' },
                    { name: 'name', type: 'string' },
                    { name: 'price', type: 'number' }
                ],
                datatype: &quot;array&quot;
            };

            const dataAdapter = new $.jqx.dataAdapter(source);

            $(&quot;#jqxgrid&quot;).jqxGrid({
                width: 600,
                source: dataAdapter,
                autoheight: true,
                columns: [
                    { text: 'ID', datafield: 'id', width: 100 },
                    { text: 'Name', datafield: 'name', width: 200 },
                    { text: 'Price', datafield: 'price', width: 150 }
                ],
                
                // ** 핵심: rowclassname 콜백 함수 정의 **
                rowclassname: function (row, rowdata) {
                    // row: 행 인덱스 (bound index)
                    // rowdata: 해당 행의 데이터 객체
                    
                    // 조건: id 값이 2인 경우
                    if (rowdata.id === 2) {
                        return &quot;highlighted-row&quot;;
                    }
                    
                    // 조건에 해당하지 않으면 null 또는 빈 문자열을 반환
                    return &quot;&quot;;
                }
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</description>
      <author>SBP</author>
      <guid isPermaLink="true">https://bnpstudio.tistory.com/775</guid>
      <comments>https://bnpstudio.tistory.com/775#entry775comment</comments>
      <pubDate>Thu, 13 Nov 2025 08:05:32 +0900</pubDate>
    </item>
    <item>
      <title>jqxgrid cell 깜빡임</title>
      <link>https://bnpstudio.tistory.com/774</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;/p&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;scripts/jquery-1.11.1.min.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxcore.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxdata.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxgrid.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxgrid.columnsresize.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;jqwidgets/jqxgrid.selection.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;/p&gt;
&lt;div id=&quot;jqxgrid&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
        $(document).ready(function () {
            // jqxGrid 데이터 및 설정
            const data = [
                { id: 1, name: &quot;Product A&quot;, price: 100 },
                { id: 2, name: &quot;Product B&quot;, price: 200 },
                { id: 3, name: &quot;Product C&quot;, price: 300 }
            ];

            const source = {
                localdata: data,
                datafields: [
                    { name: 'id', type: 'number' },
                    { name: 'name', type: 'string' },
                    { name: 'price', type: 'number' }
                ],
                datatype: &quot;array&quot;
            };

            const dataAdapter = new $.jqx.dataAdapter(source);

            $(&quot;#jqxgrid&quot;).jqxGrid({
                width: 600,
                source: dataAdapter,
                autoheight: true,
                columns: [
                    { text: 'ID', datafield: 'id', width: 100 },
                    { text: 'Name', datafield: 'name', width: 200 },
                    { text: 'Price', datafield: 'price', width: 150 } // 이 셀을 깜빡이게 할 예정
                ]
            });

            // ----------------------------------------------------
            // 2. JavaScript로 깜빡임 구현 ⏱️
            // ----------------------------------------------------

            const gridId = '#jqxgrid';
            const rowIndex = 1; // 깜빡이게 할 행의 인덱스 (예: 두 번째 행)
            const dataField = 'price'; // 깜빡이게 할 컬럼의 datafield ('Price')
            
            let blinkInterval;
            const blinkDuration = 500; // 0.5초마다 깜빡임

            // 그리드가 완전히 렌더링 된 후 DOM 요소에 접근해야 합니다.
            $(&quot;#jqxgrid&quot;).on('bindingcomplete', function () {
                startBlinking();
            });


            /**
             * 특정 셀의 DOM 요소를 찾는 함수
             * @param {number} rowIdx - 행 인덱스 (bound index)
             * @param {string} dataFld - 데이터 필드 이름
             * @returns {jQuery|null} - 셀의 jQuery 요소 또는 null
             */
            function getCellElement(rowIdx, dataFld) {
                // 컬럼 인덱스를 가져옵니다.
                const columnIndex = $(gridId).jqxGrid('getcolumnindex', dataFld);
                
                if (columnIndex === -1) return null;

                // 셀을 찾기 위한 DOM 선택자 구성
                // role=&quot;row&quot;와 role=&quot;gridcell&quot;을 사용하여 행/셀을 찾습니다.
                // jQxGrid는 실제 바인딩된 인덱스를 사용하므로 :eq() 선택자를 사용합니다.
                const cellElement = $(gridId)
                    .find(`div[role=&quot;row&quot;]:eq(${rowIdx + 1})`) // 헤더 행 제외, +1
                    .find(`div[role=&quot;gridcell&quot;]:eq(${columnIndex})`);
                
                return cellElement.length ? cellElement : null;
            }


            function startBlinking() {
                const cellElement = getCellElement(rowIndex, dataField);
                
                if (!cellElement || blinkInterval) return;

                blinkInterval = setInterval(() =&gt; {
                    // CSS 클래스를 주기적으로 토글 (추가/제거)
                    cellElement.toggleClass('blink-bg');
                }, blinkDuration);
                
                console.log(`Blinking started on row ${rowIndex}, column ${dataField}`);
            }

            function stopBlinking() {
                if (blinkInterval) {
                    clearInterval(blinkInterval);
                    blinkInterval = null;
                    // 깜빡임 중지 후 원래 상태로 복구 (클래스 제거)
                    getCellElement(rowIndex, dataField).removeClass('blink-bg'); 
                    console.log(`Blinking stopped on row ${rowIndex}, column ${dataField}`);
                }
            }

            // 예: 10초 후에 깜빡임 중지 (필요 시 주석 해제)
            // setTimeout(stopBlinking, 10000); 

            // **주의**: 페이징, 정렬, 필터링 시 그리드가 재렌더링되면 
            // 위에서 찾은 DOM 요소는 유효하지 않게 됩니다. 
            // 이 경우 `bindingcomplete`나 `sort` 등의 이벤트 발생 시 
            // `startBlinking()`을 다시 호출하여 셀 요소를 재참조해야 합니다.
        });
    &lt;/script&gt;</description>
      <author>SBP</author>
      <guid isPermaLink="true">https://bnpstudio.tistory.com/774</guid>
      <comments>https://bnpstudio.tistory.com/774#entry774comment</comments>
      <pubDate>Thu, 13 Nov 2025 08:04:34 +0900</pubDate>
    </item>
    <item>
      <title>AWS 데이터 분석 서비스 상세 설명 (Athena, S3, Glue)</title>
      <link>https://bnpstudio.tistory.com/773</link>
      <description>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;AWS Athena, S3, Glue를 이용한 데이터 레이크 분석 상세&lt;/title&gt;
    &lt;style&gt;
        body { font-family: 'Malgun Gothic', 'Arial', sans-serif; line-height: 1.6; padding: 20px; color: #333; }
        h1 { color: #232f3e; border-bottom: 2px solid #5a5a5a; padding-bottom: 10px; margin-top: 0; }
        h2 { color: #5a5a5a; margin-top: 30px; border-left: 5px solid #a0a0a0; padding-left: 10px; }
        h3 { color: #232f3e; margin-top: 20px; }
        table { width: 100%; border-collapse: collapse; margin: 20px 0; font-size: 0.9em; }
        th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
        th { background-color: #f7f7f7; color: #333; }
        .section-box { background: #f9f9f9; border: 1px solid #e0e0e0; padding: 15px; margin-bottom: 15px; }
        ul { margin-left: 20px; padding-left: 0; }
        li { margin-bottom: 8px; }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;

    &lt;h1&gt;AWS 데이터 분석 서비스 상세 설명 (Athena, S3, Glue)&lt;/h1&gt;

    &lt;p&gt;AWS Athena, S3, Glue는 서버리스 방식으로 작동하며, 대규모 데이터 레이크 구축 및 효율적인 분석을 가능하게 하는 핵심 서비스 조합입니다.&lt;/p&gt;

    &lt;h2&gt;1. Amazon S3 (Simple Storage Service) - 데이터 레이크 저장소&lt;/h2&gt;
    &lt;div class=&quot;section-box&quot;&gt;
        &lt;h3&gt;주요 특징 및 역할&lt;/h3&gt;
        &lt;ul&gt;
            &lt;li&gt;객체 스토리지: 파일 시스템이 아닌 객체 스토리지이며, 무제한에 가까운 확장성을 제공합니다.&lt;/li&gt;
            &lt;li&gt;내구성 및 가용성: 매우 높은 데이터 내구성을 제공하며, 다양한 스토리지 클래스를 통해 비용 최적화가 가능합니다.&lt;/li&gt;
            &lt;li&gt;데이터 형식: CSV, JSON, Parquet, ORC 등 다양한 형식의 데이터를 변환 없이 저장합니다.&lt;/li&gt;
            &lt;li&gt;최적화: 분석 성능과 비용 효율성을 위해 데이터를 압축하고 Parquet 또는 ORC와 같은 컬럼 기반 형식으로 변환하는 것이 일반적입니다.&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;

    &lt;h2&gt;2. AWS Glue - 메타데이터 및 ETL 계층&lt;/h2&gt;
    &lt;div class=&quot;section-box&quot;&gt;
        &lt;h3&gt;Glue Data Catalog&lt;/h3&gt;
        &lt;ul&gt;
            &lt;li&gt;중앙 메타데이터 저장소: S3 데이터의 위치, 스키마(열 이름, 데이터 타입), 파티션 구조 등 모든 메타데이터 정보를 저장합니다.&lt;/li&gt;
            &lt;li&gt;Athena와 같은 쿼리 엔진이 S3 데이터를 쿼리하기 위한 참조 정보를 제공합니다.&lt;/li&gt;
        &lt;/ul&gt;
        &lt;h3&gt;Glue Crawler&lt;/h3&gt;
        &lt;ul&gt;
            &lt;li&gt;S3의 지정된 경로를 스캔하여 데이터 파일의 형식과 스키마를 자동으로 유추합니다.&lt;/li&gt;
            &lt;li&gt;유추된 스키마 정보를 Glue Data Catalog에 테이블 정의로 생성하여 수동 정의 작업을 줄여줍니다.&lt;/li&gt;
        &lt;/ul&gt;
        &lt;h3&gt;Glue ETL Jobs&lt;/h3&gt;
        &lt;ul&gt;
            &lt;li&gt;서버리스 ETL 환경: Python (Spark) 또는 Scala를 사용하여 대규모 데이터 세트를 추출, 변환, 로드하는 작업을 수행합니다.&lt;/li&gt;
            &lt;li&gt;데이터 정제, 스키마 적용, 분석에 최적화된 형식(예: Parquet)으로의 변환 등에 사용됩니다.&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;

    &lt;h2&gt;3. Amazon Athena - 서버리스 쿼리 엔진&lt;/h2&gt;
    &lt;div class=&quot;section-box&quot;&gt;
        &lt;h3&gt;주요 특징 및 역할&lt;/h3&gt;
        &lt;ul&gt;
            &lt;li&gt;서버리스: 쿼리 실행을 위해 인프라를 프로비저닝하거나 관리할 필요가 없습니다.&lt;/li&gt;
            &lt;li&gt;쿼리 작동 방식: Glue Data Catalog의 메타데이터를 기반으로 S3에 저장된 데이터를 대상으로 표준 SQL 쿼리를 온디맨드(On-Demand) 방식으로 실행합니다.&lt;/li&gt;
            &lt;li&gt;비용 구조: 실제로 스캔한 데이터 양에 따라 요금이 부과됩니다. 따라서 효율적인 쿼리 작성과 데이터 파티셔닝이 중요합니다.&lt;/li&gt;
            &lt;li&gt;Presto 기반: 오픈 소스 분산 SQL 쿼리 엔진인 Presto와 호환되어 강력한 쿼리 기능을 제공합니다.&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;

    &lt;h2&gt;일반적인 데이터 분석 워크플로우&lt;/h2&gt;

    &lt;ol&gt;
        &lt;li&gt;
            &lt;h3&gt;데이터 수집 및 준비 (S3 + Glue ETL)&lt;/h3&gt;
            &lt;p&gt;원본 데이터를 S3의 Raw 버킷에 저장하고, Glue ETL 작업을 통해 데이터를 정제하고 Parquet과 같은 최적화된 형식으로 변환하여 S3의 Curated 버킷에 저장합니다.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
            &lt;h3&gt;메타데이터 생성 (Glue Crawler)&lt;/h3&gt;
            &lt;p&gt;Glue Crawler를 Curated 버킷의 데이터 경로에 실행하여 스키마와 파티션 정보를 감지하고 Glue Data Catalog에 테이블 정의를 생성합니다.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
            &lt;h3&gt;데이터 쿼리 및 분석 (Athena)&lt;/h3&gt;
            &lt;p&gt;Athena 콘솔에서 Glue Data Catalog의 생성된 테이블을 선택하고 표준 SQL 쿼리를 실행하여 S3의 데이터를 직접 분석합니다.&lt;/p&gt;
        &lt;/li&gt;
    &lt;/ol&gt;

    &lt;p&gt;이러한 조합은 유연성, 확장성, 비용 효율성을 제공하는 현대적인 서버리스 데이터 분석 환경을 구축합니다.&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</description>
      <category>DEV/AWS</category>
      <author>SBP</author>
      <guid isPermaLink="true">https://bnpstudio.tistory.com/773</guid>
      <comments>https://bnpstudio.tistory.com/773#entry773comment</comments>
      <pubDate>Wed, 5 Nov 2025 21:12:36 +0900</pubDate>
    </item>
  </channel>
</rss>