PageSourceSearch

https://seodoon-church-admin.web.app/assets/AttendanceDashboard-iXM5ZE3U.js

js seodoon-church-admin.web.app collected 2026-10-03 12:36:51 UTC 9,300 bytes, 1 lines download raw bytes

1import{j as e}from"./vendor-data-Z5doMMu-.js";import{r as l,L as h}from"./vendor-react-zNM0AU50.js";import{u as U,m as F,L as $,C as v,W as H,S as m,q as i,r as c,t as d,B as N,v as O,w as Q}from"./index-D1K3lVhu.js";import{a as n}from"./attendanceService-CvkHMi2v.js";import{P as z,a as I}from"./PageHeader-vb1XdvVj.js";import{ai as J,bw as V,bx as X,a5 as w,by as Y,e as M,c as Z,i as ee,U as C,s as se,S as te,a as g}from"./vendor-ui-CW5bJ11U.js";import"./vendor-firebase-Da9KY1q_.js";import"./vendor-charts-08G1rM8P.js";function me(){const{user:ae}=U(),[S,b]=l.useState(!0),[s,u]=l.useState(null),[j,L]=l.useState([]),[B,k]=l.useState([]),[T,W]=l.useState([]),[P,D]=l.useState([]),[y,A]=l.useState(0);l.useEffect(()=>{q()},[]);const q=async()=>{try{b(!0);let t=[];try{t=await F.getMembers()}catch(o){console.error("교인 데이터 로드 실패:",o)}try{const o=await n.getDashboardStats();u(o)}catch(o){console.error("대시보드 통계 로드 실패:",o),u({today:0,thisWeek:0,thisMonth:0,lastMonth:0,byMethod:{geofence:0,geofence_bg:0,qr:0,manual:0,admin:0},avgWeekly:0})}const r=await Promise.allSettled([n.getAttendanceByServiceType(30),n.getWeeklyTrend(8),n.getMonthlyTrend(12),n.getAttendanceByParish(30,t),n.getAbsentMembers(2,t)]);r[0].status==="fulfilled"&&L(r[0].value),r[1].status==="fulfilled"&&k(r[1].value),r[2].status==="fulfilled"&&W(r[2].value),r[3].status==="fulfilled"&&D(r[3].value),r[4].status==="fulfilled"&&A(r[4].value.length)}catch(t){console.error("대시보드 데이터 로드 실패:",t)}finally{b(!1)}};if(S)return e.jsx($,{});const x=s&&s.lastMonth>0?Math.round((s.thisMonth-s.lastMonth)/s.lastMonth*100):0,G=(j||[]).map((t,r)=>({name:t.serviceType,value:t.count,color:v[r%v.length]})),R=(B||[]).map(t=>({name:t.weekLabel,출석수:t.count,참석인원:t.uniqueMembers})),_=(T||[]).map(t=>({name:t.monthLabel,출석수:t.count,주일평균:t.avgPerSunday})),f=(P||[]).filter(t=>t.parish!=="미지정").map(t=>({name:t.parish,출석률:t.attendanceRate,출석인원:t.attendedMembers,전체인원:t.totalMembers})),a=s?s.byMethod.geofence+s.byMethod.geofence_bg+s.byMethod.qr+s.byMethod.manual+s.byMethod.admin:0,p=s?s.byMethod.geofence+s.byMethod.geofence_bg:0,E=a>0?Math.round(p/a*100):0,K=s?[{name:"GPS 자동",value:s.byMethod.geofence,percentage:a>0?Math.round(s.byMethod.geofence/a*100):0,icon:J,color:"text-blue-600",bgColor:"bg-white border border-gray-200"},{name:"백그라운드",value:s.byMethod.geofence_bg,percentage:a>0?Math.round(s.byMethod.geofence_bg/a*100):0,icon:V,color:"text-blue-600",bgColor:"bg-white border border-gray-200"},{name:"QR 스캔",value:s.byMethod.qr,percentage:a>0?Math.round(s.byMethod.qr/a*100):0,icon:X,color:"text-blue-600",bgColor:"bg-white border border-gray-200"},{name:"수동 입력",value:s.byMethod.manual,percentage:a>0?Math.round(s.byMethod.manual/a*100):0,icon:w,color:"text-blue-600",bgColor:"bg-white border border-gray-200"},{name:"관리자",value:s.byMethod.admin,percentage:a>0?Math.round(s.byMethod.admin/a*100):0,icon:Y,color:"text-blue-600",bgColor:"bg-white border border-gray-200"}]:[];return e.jsxs(z,{children:[e.jsx(I,{title:"출석 통계",description:"교회 출석 현황을 한눈에 확인하세요",actions:e.jsxs(h,{to:"/attendance/absentees",className:"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-amber-700 bg-amber-50 rounded-lg hover:bg-amber-100 transition-colors",children:[e.jsx(M,{className:"w-4 h-4"}),"결석자 관리 (",y,"명)"]})}
1),e.jsxs(H,{columns:4,className:"mb-6",children:[e.jsx(m,{title:"오늘 출석",value:(s==null?void 0:s.today)||0,suffix:"명",icon:e.jsx(Z,{className:"w-5 h-5"}),iconColor:"text-blue-500",iconBgColor:"bg-blue-50"}),e.jsx(m,{title:"이번 주",value:(s==null?void 0:s.thisWeek)||0,suffix:"명",icon:e.jsx(ee,{className:"w-5 h-5"}),iconColor:"text-blue-500",iconBgColor:"bg-blue-50",footer:`주간 평균: ${(s==null?void 0:s.avgWeekly)||0}명`}),e.jsx(m,{title:"이번 달",value:(s==null?void 0:s.thisMonth)||0,suffix:"명",icon:e.jsx(C,{className:"w-5 h-5"}),iconColor:"text-blue-500",iconBgColor:"bg-blue-50",change:x!==0?{value:x,type:x>0?"increase":"decrease"}:void 0}),e.jsx(m,{title:"지난 달",value:(s==null?void 0:s.lastMonth)||0,suffix:"명",icon:e.jsx(se,{className:"w-5 h-5"}),iconColor:"text-gray-500",iconBgColor:"bg-gray-50"})]}),e.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6",children:[e.jsxs(i,{children:[e.jsx(c,{title:"월별 출석 추이",description:"최근 12개월 출석 현황"}),e.jsx(d,{children:e.jsx("div",{className:"h-72",children:e.jsx(N,{data:_,bars:[{dataKey:"출석수",name:"출석수",color:"#3B82F6"}],showGrid:!0,showLegend:!1})})})]}),e.jsxs(i,{children:[e.jsx(c,{title:"예배별 출석 분포",description:"최근 30일 기준"}),e.jsx(d,{children:e.jsx("div",{className:"h-72 flex items-center justify-center",children:(j||[]).length>0?e.jsx(O,{data:G,showLegend:!0}):e.jsx("p",{className:"text-gray-500",children:"데이터가 없습니다"})})})]})]}),e.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6",children:[e.jsxs(i,{children:[e.jsx(c,{title:"주간 출석 추이",description:"최근 8주 현황"}),e.jsx(d,{children:e.jsx("div",{className:"h-64",children:e.jsx(Q,{data:R,lines:[{dataKey:"출석수",name:"출석수",color:"#3B82F6"},{dataKey:"참석인원",name:"참석인원",color:"#10B981"}],showGrid:!0,showLegend:!0})})})]}),e.jsxs(i,{children:[e.jsx(c,{title:"교구별 출석 현황",description:"최근 30일 출석률"}),e.jsx(d,{children:e.jsx("div",{className:"h-64",children:f.length>0?e.jsx(N,{data:f,bars:[{dataKey:"출석률",name:"출석률",color:"#8B5CF6"}],showGrid:!0,showLegend:!1,layout:"vertical"}):e.jsx("div",{className:"flex items-center justify-center h-full text-gray-500",children:"교구 데이터가 없습니다"})})})]})]}),e.jsxs(i,{className:"mb-6",children:[e.jsx(c,{title:"출석 방법별 통계",description:"전체 출석 기록 기준"}),e.jsxs(d,{children:[e.jsx("div",{className:"grid grid-cols-2 md:grid-cols-5 gap-4",children:K.map(t=>e.jsxs("div",{className:`${t.bgColor} rounded-xl p-4 flex items-center gap-4`,children:[e.jsx("div",{className:`${t.color} p-3 bg-blue-50 rounded-lg`,children:e.jsx(t.icon,{className:"w-6 h-6"})}),e.jsxs("div",{className:"flex-1",children:[e.jsx("p",{className:"text-sm text-gray-600",children:t.name}),e.jsxs("p",{className:"text-2xl font-bold text-gray-900",children:[t.value.toLocaleString(),e.jsxs("span",{className:"text-sm font-normal text-gray-500 ml-1",children:["(",t.percentage,"%)"]})]})]})]},t.name))}),e.jsx("div",{className:"mt-4 p-4 bg-blue-50 rounded-xl border border-blue-100",children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"p-2 bg-white rounded-lg shadow-sm",children:e.jsx(te,{className:"w-5 h-5 text-blue-600"})}),e.jsxs("div",{children:[e.jsx("p",{className:"text-sm font-medium text-gray-700",children:"자동출석 비율"}),e.jsx("p",{className:"text-xs text-gray-500",children:"GPS 자동 + 백그라운드 / 전체"})]})]}),e.jsxs("div",{className:"text-right",children:[e.jsxs("p",{className:"text-3xl font-bold text-blue-700 tabular-nums",children:[E,"%"]}),e.jsxs("p",{className:"text-xs text-gray-500 tabular-nums",children:[p.toLocaleString()," / ",a.toLocaleString()]})]})]})})]})]}),e.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-4",children:[e.jsxs(h,{to:"/attendance",className:"flex items-center justify-between p-4 bg-white rounded-xl border border-gray-200 hover:border-blue-300 hover:shadow-md transition-all",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"p-2 bg-blue-50 rounded-lg",children:e.jsx(w,{className:"w-5 h-5 text-blue-500"})}),e.jsxs("div",{children:[e.jsx("p",{className:"font-medium text-gray-900",children:"출석 관리"}),e.jsx("p",{className:"text-sm text-gray-500",children:"출석 기록 조회 및 관리"})]})]}),e.jsx(g,{className:"w-5 h-5 text-gray-400"})]}),e.jsxs(h,{to:"/attendance/absentees",className:"flex items-center justify-between p-4 bg-white rounded-xl border border-gray-200 hover:border-amber-300 hover:shadow-md transition-all",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"p-2 bg-amber-50 rounded-lg",children:e.jsx(M,{className:"w-5 h-5 text-amber-500"})}),e.jsxs("div",{children:[e.jsx("p",{className:"font-medium text-gray-900",children:"결석자 관리"}),e.jsxs("p",{className:"text-sm text-gray-500",children:["2주 이상 미출석자 ",y,"명"]})]})]}),e.jsx(g,{className:"w-5 h-5 text-gray-400"})]}),e.jsxs(h,{to:"/members",className:"flex items-center justify-between p-4 bg-white rounded-xl border border-gray-200 hover:border-blue-300 hover:shadow-md transition-all",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:"p-2 bg-blue-50 rounded-lg",children:e.jsx(C,{className:"w-5 h-5 text-blue-500"})}),e.jsxs("div",{children:[e.jsx("p",{className:"font-medium text-gray-900",children:"교인 관리"}),e.jsx("p",{className:"text-sm text-gray-500",children:"교인 정보 조회"})]})]}),e.jsx(g,{className:"w-5 h-5 text-gray-400"})]})]})]})}export{me as AttendanceDashboard,me 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.