PageSourceSearch

https://seodoon-church-admin.web.app/assets/AttendanceSettingsPage-DKZgZ-A3.js

js seodoon-church-admin.web.app collected 2026-10-03 12:36:53 UTC 13,738 bytes, 1 lines download raw bytes

1import{j as e}from"./vendor-data-Z5doMMu-.js";import{r as x,L as M}from"./vendor-react-zNM0AU50.js";import{bC as $,aB as W,a2 as E,e as O,af as R,a7 as p}from"./vendor-ui-CW5bJ11U.js";import{u as A}from"./index-D1K3lVhu.js";import{a as f}from"./attendanceSettingsService-xB_5Q3M9.js";import"./vendor-firebase-Da9KY1q_.js";import"./vendor-charts-08G1rM8P.js";import"./settingsService-BWi-XAI3.js";const L=30,D=90,z={일:0,월:1,화:2,수:3,목:4,금:5,토:6};function T(a){return a.replace(/\s+/g,"").replace(/[((].*?[))]/g,"").replace(/[^0-9A-Za-z가-힣]/g,"")||a}function F(a){const i=a.match(/(오전|오후)?\s*(\d{1,2})\s*:\s*(\d{2})/);if(!i)return null;let s=parseInt(i[2],10);const t=i[3],r=i[1];return r==="오후"&&s<12&&(s+=12),r==="오전"&&s===12&&(s=0),`${String(s).padStart(2,"0")}:${t}`}function I(a){const i={},s=/[((]\s*([일월화수목금토])\s*(\d{1,2})\s*:?\s*(\d{2})?\s*[))]/g;let t;for(;t=s.exec(a);){const r=z[t[1]];if(r===void 0)continue;const d=parseInt(t[2],10),m=t[3]||"00";i[String(r)]=`${String(d).padStart(2,"0")}:${m}`}return Object.keys(i).length?i:void 0}function P(a){const i=[];for(const s of a||[]){const t=s.category||"";for(const r of s.services||[]){const d=(r.name||"").trim();if(!d&&!r.time)continue;const m=F(r.time||"")||"00:00",l=I(r.time||"");let c=[],o="weekly",u="time",y,h="adult",g=!1;t.includes("교회학교")?(h="church_school",c=[0],u="department",y=d):t.includes("주일")?c=[0]:d.includes("새벽")?c=[1,2,3,4,5,6]:d.includes("수요")?c=[3]:d.includes("금향로")||d.includes("마지막")||t.includes("마지막")?(o="monthly_last",c=[5]):d.includes("월요")?c=[1]:d.includes("화요")?c=[2]:d.includes("목요")?c=[4]:d.includes("금요")?c=[5]:d.includes("토요")?c=[6]:g=!0,i.push({id:T(d),name:d,category:h,weekdays:c,start:m,timeByWeekday:l,recurrence:o,assignBy:u,department:y,location:r.location,preMin:L,postMin:D,enabled:!0,needsOverride:g})}}return i}function K(a,i={}){return a.map(s=>{const t=i[s.id];if(!t)return s;const r={...s,weekdays:t.weekdays??s.weekdays,start:t.start??s.start,timeByWeekday:t.timeByWeekday??s.timeByWeekday,recurrence:t.recurrence??s.recurrence,assignBy:t.assignBy??s.assignBy,department:t.department??s.department,preMin:t.preMin??s.preMin,postMin:t.postMin??s.postMin,enabled:t.enabled??s.enabled};return r.weekdays.length>0&&(r.needsOverride=!1),r})}const j=["일","월","화","수","목","금","토"];function S(){const a=new Date,i=s=>String(s).padStart(2,"0");return`${a.getFullYear()}-${i(a.getMonth()+1)}-${i(a.getDate())}`}function U(a){return a.needsOverride||a.weekdays.length===0?"—":a.recurrence==="monthly_last"?`매월 마지막 ${j[a.weekdays[0]]??""}`:a.weekdays.length>=6?"매일":a.weekdays.map(i=>j[i]).join("·")}function Y(a){const i=a.timeByWeekday?Object.entries(a.timeByWeekday).map(([s,t])=>`${j[Number(s)]} ${t}`).join(", "):"";return i?`${a.start} (${i})`:a.start}function te(){const{user:a}
1=A(),[i,s]=x.useState(!0),[t,r]=x.useState(null),[d,m]=x.useState([]),[l,c]=x.useState(""),[o,u]=x.useState(100),[y,h]=x.useState(null);x.useEffect(()=>{g()},[]);async function g(){s(!0);const[n,b]=await Promise.all([f.get(),f.getWorshipSchedule()]);r(n),m(b),c(n.auto_attendance_start||S()),u(n.radius_default||100),s(!1)}const N=x.useMemo(()=>K(P(d),(t==null?void 0:t.serviceRules)||{}),[d,t==null?void 0:t.serviceRules]),w=N.filter(n=>n.needsOverride).length;async function B(){if(confirm("자동출석 시작 기준일을 오늘로 설정합니다. 오늘 이전은 결석으로 집계하지 않습니다. 진행할까요?"))try{await f.initializeBaseline(S(),(a==null?void 0:a.displayName)||void 0),p.success("기준일을 오늘로 초기화했습니다"),g()}catch{p.error("초기화에 실패했습니다")}}async function C(){try{await f.save({auto_attendance_start:l||null,radius_default:o},(a==null?void 0:a.displayName)||void 0),p.success("저장되었습니다"),g()}catch{p.error("저장에 실패했습니다")}}async function _(n,b){try{const k={...(t==null?void 0:t.serviceRules)||{},[n]:b};await f.save({serviceRules:k},(a==null?void 0:a.displayName)||void 0),r(v=>v&&{...v,serviceRules:k}),p.success("판정 규칙을 저장했습니다"),h(null)}catch{p.error("저장에 실패했습니다")}}return i||!t?e.jsx("div",{className:"flex items-center justify-center h-96 text-gray-500 text-[13px]",children:"불러오는 중..."}):e.jsxs("div",{className:"space-y-6 p-1",children:[e.jsxs("div",{children:[e.jsxs("h1",{className:"text-[18px] font-bold text-gray-900 flex items-center gap-2",children:[e.jsx($,{className:"w-5 h-5 text-blue-600"})," 출석 설정"]}),e.jsx("p",{className:"text-[12px] text-gray-500 mt-1",children:"자동출석 시작 기준일과 예배 판정 규칙을 관리합니다. 예배명·시간·장소는 교회 설정에서 관리합니다."})]}),e.jsxs("div",{className:"bg-white border border-gray-200 rounded shadow-sm p-4",children:[e.jsx("h2",{className:"text-[14px] font-bold text-gray-900 mb-1",children:"자동출석 시작 기준일"}),e.jsxs("p",{className:"text-[12px] text-gray-500 mb-3",children:["이 날짜 이전은 결석으로 집계하지 않습니다. 자동출석을 처음 시작할 때 ",e.jsx("strong",{children:"오늘로 초기화"}),'하면 "전원 장기결석" 오표시가 사라집니다.']}),e.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"기준일"}),e.jsx("input",{type:"date",value:l,onChange:n=>c(n.target.value),className:"compact-input"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"기본 반경(m)"}),e.jsx("input",{type:"number",value:o,onChange:n=>u(Number(n.target.value)),className:"compact-input w-28"})]}),e.jsx("button",{onClick:C,className:"px-3 py-2 text-[13px] font-medium text-white bg-blue-600 rounded hover:bg-blue-700",children:"저장"}),e.jsxs("button",{onClick:B,className:"inline-flex items-center gap-1.5 px-3 py-2 text-[13px] font-medium text-white bg-green-600 rounded hover:bg-green-700",children:[e.jsx(W,{className:"w-4 h-4"})," 오늘로 초기화"]})]}),e.jsxs("div",{className:"mt-2 text-[12px] text-gray-500",children:["현재 기준일: ",e.jsx("span",{className:"font-semibold text-gray-800",children:t.auto_attendance_start||"미설정"}),t.initialized_at?` · 초기화 ${t.initialized_at.slice(0,10)}`:""]})]}),e.jsxs("div",{className:"bg-white border border-gray-200 rounded shadow-sm",children:[e.jsxs("div",{className:"flex items-center justify-between p-4 border-b border-gray-100",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-[14px] font-bold text-gray-900",children:"자동출석 판정 규칙"}),e.jsx("p",{className:"text-[12px] text-gray-500 mt-0.5",children:"교회 설정의 예배시간을 읽어 자동 판정 규칙을 만듭니다. 요일이 자동으로 안 정해지면 여기서 지정하세요."})]}),e.jsxs(M,{to:"/church-settings",className:"inline-flex items-center gap-1.5 px-3 py-1.5 text-[13px] font-medium text-blue-700 bg-blue-50 rounded hover:bg-blue-100",children:[e.jsx(E,{className:"w-4 h-4"})," 예배시간 편집"]})]}),w>0&&e.jsxs("div",{className:"mx-4 mt-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] text-amber-800 flex items-center gap-1.5",children:[e.jsx(O,{className:"w-4 h-4"})," 요일을 자동으로 정하지 못한 예배 ",w,"건이 있습니다. 수정에서 요일을 지정해야 출석이 판정됩니다."]}),N.length===0?e.jsxs("div",{className:"p-8 text-center text-[13px] text-gray-500",children:["교회 설정에 등록된 예배가 없습니다.",e.jsx(M,{to:"/church-settings",className:"ml-1 text-blue-600 underline",children:"교회 설정 → 교회소개 → 예배시간"}),"에서 등록하세요."]}):e.jsxs("table",{className:"min-w-full divide-y divide-gray-100",children:[e.jsx("thead",{className:"bg-gray-50",children:e.jsx("tr",{children:["예배","요일","시각","반복","배정","판정창","상태",""].map((n,b)=>e.jsx("th",{className:"px-3 py-2 text-left text-[11px] font-bold text-gray-600 uppercase tracking-wider",children:n},b))})}),e.jsx("tbody",{className:"divide-y divide-gray-100",children:N.map(n=>e.jsxs("tr",{className:`hover:bg-gray-50 ${n.enabled?"":"opacity-50"}`,children:[e.jsxs("td",{className:"px-3 py-2.5 text-[13px] font-medium text-gray-900",children:[n.name,n.location?e.jsx("span",{className:"ml-1 text-[11px] text-gray-400",children:n.location}):""]}),e.jsx("td",{className:"px-3 py-2.5 text-[12px] text-gray-600 whitespace-nowrap",children:U(n)}),e.jsx("td",{className:"px-3 py-2.5 text-[12px] text-gray-600 whitespace-nowrap font-mono",children:Y(n)}),e.jsx("td",{className:"px-3 py-2.5 text-[12px] text-gray-500 whitespace-nowrap",children:n.recurrence==="monthly_last"?"매월 마지막":"매주"}),e.jsx("td",{className:"px-3 py-2.5 text-[12px] text-gray-500 whitespace-nowrap",children:n.assignBy==="department"?`부서(${n.department||"-"})`:"시간"}),e.jsxs("td",{className:"px-3 py-2.5 text-[12px] text-gray-500 whitespace-nowrap",children:["-",n.preMin,"분~+",n.postMin,"분"]}),e.jsx("td",{className:"px-3 py-2.5 whitespace-nowrap",children:n.needsOverride?e.jsx("span",{className:"text-[11px] px-2 py-0.5 rounded bg-amber-100 text-amber-700 font-medium",children:"확인 필요"}):e.jsx("span",{className:"text-[11px] px-2 py-0.5 rounded bg-green-100 text-green-700 font-medium",children:"정상"})}),e.jsx("td",{className:"px-3 py-2.5 text-right",children:e.jsx("button",{onClick:()=>h(n),className:"p-1.5 text-blue-600 rounded hover:bg-blue-50",title:"판정 규칙 수정",children:e.jsx(R,{className:"w-4 h-4"})})})]},n.id))})]})]}),y&&e.jsx(q,{service:y,onClose:()=>h(null),onSave:_})]})}function q({service:a,onClose:i,onSave:s}
1){const[t,r]=x.useState({weekdays:a.weekdays,start:a.start,recurrence:a.recurrence,assignBy:a.assignBy,department:a.department,preMin:a.preMin,postMin:a.postMin,enabled:a.enabled});function d(l){r(c=>{const o=c.weekdays||[];return{...c,weekdays:o.includes(l)?o.filter(u=>u!==l):[...o,l].sort()}})}function m(){if(!t.weekdays||t.weekdays.length===0){p.error("요일을 선택하세요");return}s(a.id,{...t,timeByWeekday:a.timeByWeekday})}return e.jsx("div",{className:"fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4",children:e.jsxs("div",{className:"bg-white rounded-lg shadow-xl max-w-md w-full max-h-[90vh] overflow-y-auto",children:[e.jsx("div",{className:"p-4 border-b border-gray-200",children:e.jsxs("h2",{className:"text-[16px] font-bold text-gray-900",children:["판정 규칙 · ",a.name]})}),e.jsxs("div",{className:"p-4 space-y-4",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"요일"}),e.jsx("div",{className:"flex gap-1",children:j.map((l,c)=>e.jsx("button",{type:"button",onClick:()=>d(c),className:`w-9 h-9 rounded text-[13px] font-medium border ${(t.weekdays||[]).includes(c)?"bg-blue-600 text-white border-blue-600":"bg-white text-gray-600 border-gray-300"}`,children:l},c))})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"반복"}),e.jsxs("select",{value:t.recurrence,onChange:l=>r({...t,recurrence:l.target.value}),className:"compact-input",children:[e.jsx("option",{value:"weekly",children:"매주"}),e.jsx("option",{value:"monthly_last",children:"매월 마지막 요일"})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"배정"}),e.jsxs("select",{value:t.assignBy,onChange:l=>r({...t,assignBy:l.target.value}),className:"compact-input",children:[e.jsx("option",{value:"time",children:"시간"}),e.jsx("option",{value:"department",children:"부서"})]})]})]}),t.assignBy==="department"&&e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"소속 부서명(교적부 기준)"}),e.jsx("input",{value:t.department||"",onChange:l=>r({...t,department:l.target.value}),className:"compact-input",placeholder:"예: 유치부"})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"시작 몇 분 전부터"}),e.jsx("input",{type:"number",value:t.preMin??30,onChange:l=>r({...t,preMin:Number(l.target.value)}),className:"compact-input"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"시작 몇 분 후까지"}),e.jsx("input",{type:"number",value:t.postMin??90,onChange:l=>r({...t,postMin:Number(l.target.value)}),className:"compact-input"})]})]}),e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",checked:t.enabled??!0,onChange:l=>r({...t,enabled:l.target.checked}),className:"w-4 h-4"}),e.jsx("span",{className:"text-[12px] font-medium text-gray-700",children:"이 예배 자동출석 사용"})]}),e.jsx("p",{className:"text-[11px] text-gray-400",children:"예배명·시각·장소는 교회 설정(교회소개 → 예배시간)에서 바꿉니다. 여기서는 판정 방식만 보정합니다."}),e.jsxs("div",{className:"flex gap-2 pt-1",children:[e.jsx("button",{onClick:i,className:"flex-1 px-3 py-2 border border-gray-300 rounded hover:bg-gray-50 text-[13px]",children:"취소"}),e.jsx("button",{onClick:m,className:"flex-1 px-3 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 text-[13px] font-medium",children:"저장"})]})]})]})})}export{te as AttendanceSettingsPage,te as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.