/* 서식 편집기 — 데이터/로직: 발행 v1 기준선 · 블랭크 서식 · deepClone · 자동 diff */ const bdClone = (o) => JSON.parse(JSON.stringify(o)); let _uid = Date.now(); const bdUid = (p) => (p || 'id') + '_' + (++_uid).toString(36); /* 현재 발행된 v1 (유지 보수) — 편집기 초안과 비교해 변경 diff 산출 */ window.BD_PUBLISHED_V1 = { area: '유지 보수', areaIcon: 'wrench', version: 1, blocks: [ { id: 'intro', kind: 'text', title: '상황판 안내', text: '이 상황판은 교내 시설 수선·보수 용역의 접수부터 완료까지를 한 곳에서 관리합니다. 칸에 「학교 입력」 표시가 있는 항목만 학교에서 직접 작성하실 수 있고, 나머지는 세이프스쿨이 채웁니다.' }, { id: 'basic', kind: 'fields', title: '용역 기본 정보', fields: [ { id: 'school', label: '학교명', type: 'fixed', perm: 'safe', value: '○○중학교' }, { id: 'facility', label: '시설 구분', type: 'input', perm: 'school', ph: '예) 체육관·급식실' }, { id: 'item', label: '보수 항목', type: 'input', perm: 'school', ph: '예) 천장 누수 보수' }, { id: 'reqdate', label: '요청 날짜', type: 'input', perm: 'school', ph: '2026.6.3' }, { id: 'vendor', label: '담당 업체', type: 'input', perm: 'safe', ph: '세이프스쿨 배정' }, { id: 'start', label: '착수 예정일', type: 'input', perm: 'safe', ph: '' }, { id: 'done', label: '완료 예정일', type: 'input', perm: 'safe', ph: '' }, ] }, { id: 'worktable', kind: 'table', title: '보수 내역', rowAdd: 'school', cols: [ { id: 'c1', label: '항목', perm: 'safe' }, { id: 'c2', label: '위치', perm: 'safe' }, { id: 'c3', label: '진행 상태', perm: 'safe' }, { id: 'c4', label: '비용', perm: 'safe' }, ], rows: [['배관 누수 보수', '1층 급식실', '완료', '1,200,000'], ['창호 교체', '3층 3-2 교실', '진행중', '—']], }, ], }; /* 편집 초안 v2 — v1에서 출발해 ‘예상 비용’ 칸, ‘안전점검 안내’ 블록 추가, ‘위치’ 열 학교 개방, 라벨 정리 */ window.BD_DRAFT_V2 = (function () { const t = bdClone(window.BD_PUBLISHED_V1); t.version = 2; const basic = t.blocks.find((b) => b.id === 'basic'); // 라벨 정리 basic.fields.find((f) => f.id === 'reqdate').label = '요청일'; // 예상 비용 추가 (vendor 뒤) const vi = basic.fields.findIndex((f) => f.id === 'vendor'); basic.fields.splice(vi + 1, 0, { id: 'cost', label: '예상 비용', type: 'input', perm: 'safe', ph: '견적 후 입력' }); // 위치 열 학교 개방 t.blocks.find((b) => b.id === 'worktable').cols.find((c) => c.id === 'c2').perm = 'school'; // 안전점검 안내 블록 추가 t.blocks.push({ id: 'safety', kind: 'text', title: '안전점검 확인 안내', text: '보수 완료 후 안전점검 확인서를 첨부해 주세요. 점검 항목은 세이프스쿨 안전관리 표준안을 따릅니다.' }); return t; })(); /* 빈 서식 생성 (미개설 영역 → ‘상황판 만들기’) */ window.bdBlankTemplate = function (area, areaIcon) { return { area, areaIcon: areaIcon || 'layoutgrid', version: 1, blank: true, blocks: [ { id: bdUid('blk'), kind: 'text', title: '상황판 안내', text: '이 상황판의 안내 문구를 입력하세요. 모든 학교에 동일하게 표시됩니다.' }, { id: bdUid('blk'), kind: 'fields', title: '기본 정보', fields: [ { id: bdUid('f'), label: '학교명', type: 'fixed', perm: 'safe', value: '○○학교' }, { id: bdUid('f'), label: '항목', type: 'input', perm: 'school', ph: '학교가 입력' }, ] }, ], }; }; window.bdUid = bdUid; window.bdClone = bdClone; /* 새 칸/블록 기본값 */ window.bdNewField = () => ({ id: bdUid('f'), label: '새 항목', type: 'input', perm: 'school', ph: '' }); window.bdNewBlock = (kind) => { if (kind === 'text') return { id: bdUid('blk'), kind: 'text', title: '안내 텍스트', text: '안내 문구를 입력하세요.' }; if (kind === 'table') return { id: bdUid('blk'), kind: 'table', title: '새 표', rowAdd: 'both', cols: [{ id: bdUid('c'), label: '항목', perm: 'safe' }, { id: bdUid('c'), label: '내용', perm: 'school' }], rows: [['', ''], ['', '']] }; if (kind === 'requests') return { id: bdUid('blk'), kind: 'requests', title: '요청 사항', flow: 'school-send', items: [{ id: bdUid('rq'), label: '새 요청' }] }; if (kind === 'docs') return { id: bdUid('blk'), kind: 'docs', title: '관련 문서', flow: 'safe-upload', docs: [{ id: bdUid('doc'), name: '새 문서', owner: '' }] }; if (kind === 'progress') return { id: bdUid('blk'), kind: 'progress', title: '진행 상황', note: '세이프스쿨이 진행 단계를 시간순으로 기록합니다. 학교는 열람만 합니다.' }; if (kind === 'chat') return { id: bdUid('blk'), kind: 'chat', title: '대화창', note: '학교와 세이프스쿨이 실시간으로 메시지를 주고받습니다 (양방향).' }; return { id: bdUid('blk'), kind: 'fields', title: '정보 그리드', fields: [window.bdNewField()] }; }; /* ---- 자동 diff: 기준(base) → 초안(next) 변경 목록 ---- */ window.bdComputeDiff = function (base, next) { const out = []; const baseBlocks = Object.fromEntries(base.blocks.map((b) => [b.id, b])); const nextBlocks = Object.fromEntries(next.blocks.map((b) => [b.id, b])); next.blocks.forEach((b) => { const pb = baseBlocks[b.id]; if (!pb) { const labMap = { text: '안내 텍스트', fields: '정보 그리드', table: '표', requests: '요청 사항', docs: '관련 문서', progress: '진행 상황', chat: '대화창' }; const kindLab = labMap[b.kind] || '블록'; out.push({ kind: 'add', label: '‘' + b.title + '’ ' + kindLab + ' 블록 추가', detail: b.kind === 'text' ? '고정 안내 · 모든 학교 공통으로 표시됩니다.' : '새 블록 · 기존 학교에 빈 상태로 추가됩니다.' }); return; } if (b.kind === 'text' && b.text !== pb.text) { out.push({ kind: 'edit', label: '‘' + b.title + '’ 안내 문구 수정', detail: '모든 학교에 동일하게 갱신됩니다.' }); } if (b.kind === 'fields') { const pf = Object.fromEntries((pb.fields || []).map((f) => [f.id, f])); b.fields.forEach((f) => { const o = pf[f.id]; if (!o) { out.push({ kind: 'add', label: '‘' + f.label + '’ 칸 추가', detail: (f.perm === 'school' ? '학교 입력' : '세이프스쿨 전용') + ' · 기존 학교는 빈 칸으로 추가됩니다.' }); return; } if (o.label !== f.label) out.push({ kind: 'edit', label: '‘' + o.label + '’ → ‘' + f.label + '’ 라벨 수정', detail: '입력 값은 칸 ID로 그대로 유지됩니다.' }); if (o.type !== f.type) out.push({ kind: 'edit', label: '‘' + f.label + '’ 유형 변경', detail: (f.type === 'fixed' ? '입력 → 고정 안내' : '고정 → 입력 필드') }); if (o.perm !== f.perm) out.push({ kind: 'perm', label: '‘' + f.label + '’ ' + (f.perm === 'school' ? '학교 입력으로 개방' : '세이프스쿨 전용으로 전환'), detail: '칸 권한 변경.' }); }); (pb.fields || []).forEach((f) => { if (!b.fields.some((x) => x.id === f.id)) out.push({ kind: 'remove', label: '‘' + f.label + '’ 칸 삭제', detail: '학교에서 더 이상 표시되지 않습니다(기존 값은 보관).' }); }); } if (b.kind === 'table') { const pc = Object.fromEntries((pb.cols || []).map((c) => [c.id, c])); b.cols.forEach((c) => { const o = pc[c.id]; if (!o) { out.push({ kind: 'add', label: '‘' + c.label + '’ 열 추가', detail: '보수 내역 표 · ' + (c.perm === 'school' ? '학교 입력' : '세이프스쿨 전용') }); return; } if (o.label !== c.label) out.push({ kind: 'edit', label: '‘' + o.label + '’ → ‘' + c.label + '’ 열 이름 수정', detail: '표 열 라벨 변경.' }); if (o.perm !== c.perm) out.push({ kind: 'perm', label: '‘' + c.label + '’ 열을 ' + (c.perm === 'school' ? '학교 입력으로 개방' : '세이프스쿨 전용으로 전환'), detail: '보수 내역 표.' }); }); } if (b.kind === 'requests') { const pi = Object.fromEntries((pb.items || []).map((i) => [i.id, i])); b.items.forEach((i) => { const o = pi[i.id]; if (!o) { out.push({ kind: 'add', label: '‘' + i.label + '’ 요청 항목 추가', detail: '학교가 선택해 전송하는 요청 항목입니다.' }); return; } if (o.label !== i.label) out.push({ kind: 'edit', label: '‘' + o.label + '’ → ‘' + i.label + '’ 요청 항목 수정', detail: '요청 사항 항목 라벨 변경.' }); }); (pb.items || []).forEach((i) => { if (!b.items.some((x) => x.id === i.id)) out.push({ kind: 'remove', label: '‘' + i.label + '’ 요청 항목 삭제', detail: '요청 사항에서 제거됩니다.' }); }); } if (b.kind === 'docs') { const pd = Object.fromEntries((pb.docs || []).map((d) => [d.id, d])); b.docs.forEach((d) => { const o = pd[d.id]; if (!o) { out.push({ kind: 'add', label: '‘' + d.name + '’ 문서 추가', detail: '세이프스쿨이 업로드 → 학교가 받는 문서입니다.' }); return; } if (o.name !== d.name || o.owner !== d.owner) out.push({ kind: 'edit', label: '‘' + o.name + '’ 문서 정보 수정', detail: '문서명·담당자 변경.' }); }); (pb.docs || []).forEach((d) => { if (!b.docs.some((x) => x.id === d.id)) out.push({ kind: 'remove', label: '‘' + d.name + '’ 문서 삭제', detail: '관련 문서에서 제거됩니다.' }); }); } if ((b.kind === 'progress' || b.kind === 'chat') && b.note !== pb.note) { out.push({ kind: 'edit', label: '‘' + b.title + '’ 안내 수정', detail: '섹션 설명이 갱신됩니다.' }); } }); base.blocks.forEach((b) => { if (!nextBlocks[b.id]) out.push({ kind: 'remove', label: '‘' + b.title + '’ 블록 삭제', detail: '학교에서 더 이상 표시되지 않습니다.' }); }); return out; }; /* ============================================================ 기존 「인력 파견」 상황판 → 서식 v1으로 전환 (DETAIL 구조 매핑) - 파견 인력 정보 : 정보 그리드 (대부분 세이프스쿨 전용, 비고만 학교 개방) - 요청 사항 : 정보 그리드 (항목별 학교 개방/전용 — 기존 open 플래그 보존) - 진행 상황 / 관련 문서 : 표 (세이프스쿨 전용) ============================================================ */ window.BD_FACILITY_V1 = { area: '인력 파견', areaIcon: 'hardhat', version: 1, blocks: [ { id: 'fac-worker', kind: 'fields', title: '기본 정보', fields: [ { id: 'place', label: '근무처', type: 'fixed', perm: 'safe', value: '○○중학교' }, { id: 'region', label: '지역', type: 'input', perm: 'safe', ph: '성남시' }, { id: 'name', label: '이름', type: 'input', perm: 'safe', ph: '강병구' }, { id: 'sexage', label: '성별(연령)', type: 'input', perm: 'safe', ph: '남 (65)' }, { id: 'contact', label: '연락처', type: 'input', perm: 'safe', ph: '010-0000-0000' }, { id: 'address', label: '거주지', type: 'input', perm: 'safe', ph: '용인시 기흥구' }, { id: 'workdays', label: '근무일', type: 'input', perm: 'safe', ph: '월 ~ 금' }, { id: 'worktime', label: '근무시간', type: 'input', perm: 'safe', ph: '08:00 ~ 15:00' }, { id: 'job', label: '직군', type: 'fixed', perm: 'safe', value: '시설관리보조' }, { id: 'contract', label: '계약 기간', type: 'input', perm: 'safe', ph: '2026.3.1 ~ 2027.2.28' }, { id: 'note', label: '비고', type: 'input', perm: 'safe', ph: '메모' }, ] }, { id: 'fac-docs', kind: 'docs', title: '관련 자료', flow: 'safe-upload', docs: [ { id: '1', name: '이력서', owner: '팀장 강은영' }, { id: '2', name: '계약서', owner: '팀장 강은영' }, { id: '3', name: '출근부 (5월)', owner: '주임 유주미' }, { id: '4', name: '급여이체', owner: '팀장 이진영' }, { id: '5', name: '산업안전보건 협의회 (5월)', owner: '팀장 김주현' }, { id: '6', name: '근무일지 (5월)', owner: '주임 김정화' }, ] }, { id: 'fac-prog', kind: 'progress', title: '진행 상황', note: '학교의 요청은 자동으로 기록되고(날짜·시간), 세이프스쿨이 조치 결과를 담당자와 함께 기입합니다.', seed: [ { step: '4월 청구', at: '4/25 14:13', by: '팀장 이진영', done: true }, { step: '작업지시 — 체육대회 준비', at: '4/30 08:42', by: '학교', who: 'school', done: true, reply: { text: '완료 — 5/1~5/3 작업', by: '팀장 서경원', at: '5/3 16:20' } }, { step: '5월 협의체', at: '5/2 10:11', by: '', done: false }, { step: '이사장님 방문 요청', at: '5/7 09:02', by: '학교', who: 'school', done: true }, ] }, { id: 'fac-req', kind: 'requests', title: '요청', flow: 'school-send', items: [ { id: 'work', label: '작업' }, { id: 'sub', label: '대체' }, { id: 'visit', label: '협의체 방문' }, { id: 'att', label: '출근부' }, { id: 'council', label: '협의회' }, { id: 'bill', label: '청구' }, { id: 'log', label: '근무일지' }, { id: 'etc', label: '기타' }, ] }, { id: 'fac-chat', kind: 'chat', title: '대화창', note: '학교와 세이프스쿨이 실시간으로 메시지를 주고받습니다 (양방향).' }, ], }; /* ============================================================ 「연간 관리」 상황판 → 서식 v1 (PDF 6-3 매핑) - 한 학교가 여러 서비스(옥외시설물/청소/방역…)를 구독, 서비스마다 이 서식으로 렌더 - 서비스 유형·관리구역·월정액은 서식 안의 칸(기본 정보)으로 표시(서비스별 인스턴스 값) - 작업 요청 항목(전지/예초/…)은 서비스마다 다르므로 표 행 = 인스턴스 데이터로 시드 ============================================================ */ window.BD_SUBSCRIPTION_V1 = { area: '연간 관리', areaIcon: 'calendarcheck', version: 1, blocks: [ { id: 'sub-basic', kind: 'fields', title: '기본 정보', fields: [ { id: 'school', label: '학교명', type: 'input', perm: 'safe', ph: '안양유치원' }, { id: 'region', label: '지역', type: 'input', perm: 'safe', ph: '안양시' }, { id: 'svctype', label: '서비스 유형', type: 'input', perm: 'safe', ph: '옥외시설물 관리' }, { id: 'zone', label: '관리 구역', type: 'input', perm: 'safe', ph: '운동장 / 화단 / 수목' }, { id: 'fee', label: '월정액', type: 'input', perm: 'safe', ph: '월 550,000 (vat 포함)' }, { id: 'period', label: '계약 기간', type: 'input', perm: 'safe', ph: '2026.3.1 ~ 2027.2.28' }, { id: 'tenure', label: '계약 차수', type: 'input', perm: 'safe', ph: '3년 차' }, { id: 'team', label: '담당 부서', type: 'input', perm: 'safe', ph: '사업3팀' }, { id: 'tel', label: '연락처', type: 'input', perm: 'safe', ph: '1533-2075 (내선 3번)' }, { id: 'safety', label: '안전관리자', type: 'input', perm: 'safe', ph: '신형수 팀장' }, { id: 'schoolMgr', label: '학교 담당자', type: 'input', perm: 'safe', ph: '박준영 시설주무관' }, { id: 'note', label: '비고', type: 'input', perm: 'safe', ph: '메모' }, ] }, { id: 'sub-req', kind: 'requests', title: '요청', flow: 'school-send', items: [ { id: 'prune', label: '전지' }, { id: 'mow', label: '예초' }, { id: 'weed', label: '제초' }, { id: 'drain', label: '배수로' }, { id: 'tidy', label: '정리' }, { id: 'etc', label: '기타' }, ] }, { id: 'sub-chat', kind: 'chat', title: '대화창', note: '학교와 세이프스쿨이 실시간으로 메시지를 주고받습니다 (양방향).' }, { id: 'sub-log', kind: 'table', title: '현황 (작업일지)', rowAdd: 'safe', cols: [ { id: 'lg1', label: '일자', perm: 'safe' }, { id: 'lg2', label: '작업 내용', perm: 'safe' }, { id: 'lg3', label: '인원', perm: 'safe' }, { id: 'lg4', label: '시간', perm: 'safe' }, ], rows: [ ['3.3 (1회)', '낙엽', '4인', '8시간'], ['3.11 (2회)', '제초', '2인', '4시간'], ['3.15 (3회)', '배수로', '3인', '10시간'], ['4.5 (1회)', '폐기물 처리', '2인', '6시간'], ['4.28 (2회)', '현관 매트 청소', '2인', '4시간'], ['5.13 (1회)', '제초', '3인', '8시간'], ['5.22 (예정)', '배수로', '4인', '8시간'], ] }, { id: 'sub-docs', kind: 'docs', title: '관련 자료', flow: 'safe-upload', docs: [ { id: '1', name: '계약서', owner: '팀장 강은영' }, { id: '2', name: '특수조건', owner: '팀장 강은영' }, { id: '3', name: '3월 작업일지', owner: '팀장 유주미' }, { id: '4', name: '4월 작업일지', owner: '주임 유주미' }, { id: '5', name: '5월 작업일지', owner: '팀장 유주미' }, ] }, { id: 'sub-prog', kind: 'progress', title: '진행 상황', note: '학교의 작업 요청은 자동으로 기록되고(날짜·시간), 세이프스쿨이 조치 결과를 담당자와 함께 기입합니다.', seed: [ { step: '작업 요청 (배수로)', at: '4/25 14:13', by: '학교', who: 'school', done: true, reply: { text: '점검 완료 — 우수관 이물질 제거', by: '사업 3팀', at: '4/25 14:59' } }, { step: '작업 요청 (제초)', at: '5/2 10:11', by: '학교', who: 'school', done: true, reply: { text: '배정 — 5/13 작업 예정', by: '사업 3팀', at: '5/3 09:30' } }, { step: '5월 정기 작업 (제초)', at: '5/13 16:40', by: '팀장 서경원 외 2', done: true }, ] }, ], }; /* ============================================================ 「단기 대체」 상황판 → 서식 v1 (PDF 7-3-1 매핑) - 인력당 1개 상황판(워커 축) — 시설관리와 동형 - 단기대체 특유: 대체 사유 · 교체/계약 연장 사유 · 범죄경력확인서 ============================================================ */ window.BD_SUBSTITUTE_V1 = { area: '단기 대체', areaIcon: 'userplus', version: 1, blocks: [ { id: 'sb-intro', kind: 'text', title: '상황판 안내', text: '3개월 미만 단기 결원을 대신할 대체 인력의 파견·근태·문서를 한 곳에서 관리합니다. 대체 인력은 학교가 제출한 「구인 신청서」를 세이프스쿨이 매칭하여 배정합니다. 「학교 입력」 칸·열만 학교에서 작성하시고, 나머지는 세이프스쿨이 채웁니다.' }, { id: 'sb-worker', kind: 'fields', title: '대체 인력 정보', fields: [ { id: 'place', label: '근무처', type: 'fixed', perm: 'safe', value: '○○학교' }, { id: 'region', label: '지역', type: 'input', perm: 'safe', ph: '성남시' }, { id: 'name', label: '이름', type: 'input', perm: 'safe', ph: '오영희' }, { id: 'sexage', label: '성별(연령)', type: 'input', perm: 'safe', ph: '여 (62)' }, { id: 'contact', label: '연락처', type: 'input', perm: 'safe', ph: '010-0000-0000' }, { id: 'address', label: '거주지', type: 'input', perm: 'safe', ph: '용인시 수지구 상현동' }, { id: 'workdays', label: '근무일', type: 'input', perm: 'safe', ph: '6.1 ~ 6.3' }, { id: 'worktime', label: '근무시간', type: 'input', perm: 'safe', ph: '08:00 ~ 15:00' }, { id: 'job', label: '직군', type: 'input', perm: 'safe', ph: '미화원' }, { id: 'reason', label: '대체 사유', type: 'input', perm: 'safe', ph: '연차 사용' }, { id: 'note', label: '비고', type: 'input', perm: 'safe', ph: '메모' }, ] }, { id: 'sb-docs', kind: 'docs', title: '관련 자료', flow: 'safe-upload', docs: [ { id: '1', name: '이력서', owner: '팀장 강은영' }, { id: '2', name: '계약서', owner: '팀장 강은영' }, { id: '3', name: '특수조건', owner: '팀장 강은영' }, { id: '4', name: '범죄경력확인서', owner: '주임 유주미' }, { id: '5', name: '급여이체', owner: '팀장 이진영' }, { id: '6', name: '근무일지', owner: '주임 김정화' }, { id: '7', name: '출근부', owner: '팀장 김주현' }, ] }, { id: 'sb-prog', kind: 'progress', title: '진행 상황', note: '학교의 요청은 자동으로 기록되고(날짜·시간), 세이프스쿨이 조치 결과를 담당자와 함께 기입합니다.', seed: [ { step: '범죄경력 확인 요청', at: '4/25 14:13', by: '학교', who: 'school', done: true, reply: { text: '완료 — 확인서 업로드', by: '주임 유주미', at: '4/25 14:59' } }, { step: '작업지시 — 체육대회 준비', at: '4/30 08:42', by: '학교', who: 'school', done: true, reply: { text: '완료 (5/1)', by: '팀장 서경원', at: '5/1 17:00' } }, { step: '계약 연장 (1일)', at: '5/2 10:11', by: '학교', who: 'school', done: true, reply: { text: '완료 (5/3)', by: '팀장 강은영', at: '5/3 10:00' } }, ] }, { id: 'sb-req', kind: 'requests', title: '요청', flow: 'school-send', items: [ { id: 'work', label: '작업지시' }, { id: 'change', label: '교체' }, { id: 'extend', label: '계약 연장' }, { id: 'att', label: '출근부' }, { id: 'pay', label: '급여이체' }, { id: 'bill', label: '청구' }, { id: 'log', label: '근무일지' }, { id: 'etc', label: '기타' }, ] }, { id: 'sb-chat', kind: 'chat', title: '대화창', note: '학교와 세이프스쿨이 실시간으로 메시지를 주고받습니다 (양방향).' }, ], }; /* ============================================================ 「유지 보수」 상황판 → 서식 v1 (Page 3-1-3 매핑) - 하나의 용역 계약 보드 — 용역 정보(정보 그리드) 칸별 권한을 세이프스쿨이 「서식 편집」에서 지정 - 진행 상황 · 관련 문서 · 대화창은 전용 UI(타임라인/문서표/채팅)로 별도 운영 — 서식 대상 아님 - 칸 ID는 maint-board의 SAMPLE 키와 일치시켜 값 시드를 잇는다 ============================================================ */ window.BD_MAINT_V1 = { area: '유지 보수', areaIcon: 'wrench', version: 1, blocks: [ { id: 'mb-intro', kind: 'text', title: '상황판 안내', text: '교내 시설 유지 보수의 접수부터 완료까지를 한 곳에서 관리합니다. 「학교 입력」 표시가 있는 칸만 학교에서 직접 작성하시고, 나머지는 세이프스쿨이 채웁니다.' }, { id: 'mb-info', kind: 'fields', title: '용역 정보', fields: [ { id: 'school', label: '학교명', type: 'fixed', perm: 'safe', value: '○○중학교' }, { id: 'region', label: '지역', type: 'input', perm: 'safe', ph: '성남시' }, { id: 'svcName', label: '용역명', type: 'input', perm: 'safe', ph: '배수로 정비' }, { id: 'category', label: '구분', type: 'input', perm: 'safe', ph: '기타' }, { id: 'zone', label: '작업 구역', type: 'input', perm: 'school', ph: '운동장 / 주차장' }, { id: 'spec', label: '규격', type: 'input', perm: 'safe', ph: '200m / 130m (폭 0.4m)' }, { id: 'crew', label: '작업 인원', type: 'input', perm: 'safe', ph: '7명' }, { id: 'equipment', label: '투입 장비', type: 'input', perm: 'safe', ph: '굴삭기' }, { id: 'dept', label: '담당 부서', type: 'input', perm: 'safe', ph: '사업 3팀' }, { id: 'deptTel', label: '부서 연락처', type: 'input', perm: 'safe', ph: '1533-2075 (내선 3번)' }, { id: 'safetyMgr', label: '안전관리자', type: 'input', perm: 'safe', ph: '신형수 팀장' }, { id: 'safetyTel', label: '안전관리자 연락처', type: 'input', perm: 'safe', ph: '010-0000-0000' }, { id: 'schoolMgr', label: '학교 담당자', type: 'input', perm: 'school', ph: '박준영 시설팀장' }, { id: 'workDate', label: '작업일', type: 'input', perm: 'safe', ph: '4/11' }, { id: 'zoneNote', label: '특이 사항', type: 'input', perm: 'school', full: true, ph: '작업 관련 학교 특이사항·요청을 적어 주세요' }, ] }, { id: 'mb-req', kind: 'requests', title: '요청 사항', flow: 'school-send', items: [ { id: 'survey', label: '실측 방문' }, { id: 'quote', label: '견적서' }, { id: 'schedule', label: '일정 조율' }, { id: 'docs', label: '완수 서류' }, { id: 'cleanup', label: '현장 정리' }, { id: 'etc', label: '기타' }, ] }, { id: 'mb-progress', kind: 'progress', title: '진행 상황', note: '세이프스쿨이 진행 단계를 시간순으로 기록합니다 (견적 의뢰 → 실측 방문 → 견적서 발송 → 일정 확정 → 시공 → 완수계). 학교는 열람만 합니다.' }, { id: 'mb-docs', kind: 'docs', title: '관련 문서', flow: 'safe-upload', docs: [ { id: '1', name: '견적서', owner: '팀장 강은영' }, { id: '2', name: '작업계획서', owner: '팀장 강은영' }, { id: '3', name: '착수계', owner: '팀장 유주미' }, { id: '4', name: '안전교육 일지', owner: '주임 유주미' }, { id: '5', name: '완수계', owner: '팀장 유주미' }, { id: '6', name: '폐기물 처리 증빙서', owner: '팀장 서경원' }, ] }, { id: 'mb-chat', kind: 'chat', title: '대화창', note: '학교와 세이프스쿨이 실시간으로 메시지를 주고받습니다. 양방향이며, 보는 사람 기준으로 내 말풍선이 오른쪽에 표시됩니다.' }, ], };