PageSourceSearch

https://seodoon-church-admin.web.app/assets/FamilyProgramManagementPage-DEPMKGFX.js

js seodoon-church-admin.web.app collected 2026-10-03 12:36:16 UTC 29,389 bytes, 10 lines download raw bytes

1import{j as e}from"./vendor-data-Z5doMMu-.js";import{r as d}from"./vendor-react-zNM0AU50.js";import{A as J}from"./index.esm-NBNS-BUx.js";/* empty css                        */import{O as q,K as Z,U as V,a7 as x,ar as ee,c as te,aa as ae,D as se,n as re,ah as le,d as ne,X as H,T as ce,Y as oe,a8 as ie}from"./vendor-ui-CW5bJ11U.js";import{H as de,q as U,l as R,w as Y,m as F,k as S,u as B,z as xe,j as ue,x as z,y as me,D as he,G as ge,E as ye}from"./vendor-firebase-Da9KY1q_.js";import{d as g,s as fe}from"./index-D1K3lVhu.js";import"./vendor-charts-08G1rM8P.js";const W="seodoon-church",C=`churches/${W}/familyPrograms`,k=`churches/${W}/familyEnrollments`,E={async getAllPrograms(){try{const t=U(R(g,C),z("sortOrder","asc"));return(await F(t)).docs.map(c=>({id:c.id,...c.data()}))}catch(t){throw console.error("프로그램 목록 조회 실패:",t),t}},async getProgramsByYear(t){try{const s=U(R(g,C),Y("year","==",t),z("sortOrder","asc"));return(await F(s)).docs.map(r=>({id:r.id,...r.data()}))}catch(s){throw console.error("연도별 프로그램 조회 실패:",s),s}},async getProgram(t){try{const s=S(g,C,t),c=await ue(s);return c.exists()?{id:c.id,...c.data()}:null}catch(s){throw console.error("프로그램 조회 실패:",s),s}},async createProgram(t){try{const s={...t,createdAt:new Date,updatedAt:new Date};return(await xe(R(g,C),s)).id}catch(s){throw console.error("프로그램 생성 실패:",s),s}},async updateProgram(t,s){try{const c=S(g,C,t);await B(c,{...s,updatedAt:new Date})}catch(c){throw console.error("프로그램 수정 실패:",c),c}},async updateProgramStatus(t,s){try{const c=S(g,C,t);await B(c,{status:s,updatedAt:new Date})}catch(c){throw console.error("프로그램 상태 변경 실패:",c),c}},async deleteProgram(t){try{const s=de(g),c=U(R(g,k),Y("programId","==",t));(await F(c)).docs.forEach(o=>{s.delete(o.ref)}),s.delete(S(g,C,t)),await s.commit()}catch(s){throw console.error("프로그램 삭제 실패:",s),s}}},M={async getEnrollmentsByProgram(t){try{const s=U(R(g,k),Y("programId","==",t),z("createdAt","desc"));return(await F(s)).docs.map(r=>({id:r.id,...r.data()}))}catch(s){throw console.error("신청 목록 조회 실패:",s),s}},async getAllEnrollments(){try{const t=U(R(g,k),z("createdAt","desc"));return(await F(t)).docs.map(c=>({id:c.id,...c.data()}))}catch(t){throw console.error("전체 신청 조회 실패:",t),t}},async confirmEnrollment(t){try{const s=S(g,k,t);await B(s,{status:"confirmed",updatedAt:new Date})}catch(s){throw console.error("신청 확정 실패:",s),s}},async cancelEnrollment(t){try{const s=S(g,k,t);await B(s,{status:"cancelled",updatedAt:new Date})}catch(s){throw console.error("신청 취소 실패:",s),s}},async deleteEnrollment(t){try{await me(S(g,k,t))}catch(s){throw console.error("신청 삭제 실패:",s),s}}},be={async getOverviewStats(){try{const t=await E.getAllPrograms(),s=await M.getAllEnrollments(),c=t.map(r=>{const o=s.filter(i=>i.programId===r.id);return{programId:r.id,programName:r.name,category:r.category,totalEnrollments:o.length,confirmedEnrollments:o.filter(i=>i.status==="confirmed").length,pendingEnrollments:o.filter(i=>i.status==="pending").length}});return{totalPrograms:t.length,activePrograms:t.filter(r=>r.status==="active").length,recruitingPrograms:t.filter(r=>r.status==="recruiting").length,totalEnrollments:s.filter(r=>r.status!=="cancelled").length,confirmedEnrollments:s.filter(r=>r.status==="confirmed").length,programStats:c}}catch(t){throw console.error("통계 조회 실패:",t),t}}},O={baby:"영유아가정",couple:"부부",family:"가족",senior:"시니어"},Q={baby:"#E8734A",couple:"#D4627A",family:"#4CAF7A",senior:"#7C6BBF"},X={recruiting:"모집중",upcoming:"진행예정",active:"진행중",closed:"마감"},G={pending:"대기",confirmed:"확정",cancelled:"취소"};
1function Oe(){const[t,s]=d.useState("dashboard"),c=[{id:"dashboard",label:"대시보드",icon:Z},{id:"programs",label:"프로그램 관리",icon:q},{id:"enrollments",label:"신청 관리",icon:V}];return e.jsxs("div",{className:"space-y-4",children:[e.jsx("div",{className:"flex items-center justify-between",children:e.jsxs("div",{children:[e.jsxs("h1",{className:"text-[18px] font-bold text-gray-900 flex items-center gap-2",children:[e.jsx(q,{className:"w-5 h-5 text-blue-600"}),"가정사역부"]}),e.jsx("p",{className:"text-[12px] text-gray-500 mt-1",children:"가정사역부 프로그램과 신청 현황을 관리합니다"})]})}),e.jsx("div",{className:"flex gap-1 border-b border-gray-200",children:c.map(r=>e.jsxs("button",{onClick:()=>s(r.id),className:`flex items-center gap-1.5 px-4 py-2 text-[13px] font-medium border-b-2 -mb-[1px] transition-colors ${t===r.id?"border-blue-600 text-blue-600":"border-transparent text-gray-500 hover:text-gray-700"}`,children:[e.jsx(r.icon,{className:"w-4 h-4"}),r.label]},r.id))}),t==="dashboard"&&e.jsx(pe,{}),t==="programs"&&e.jsx(Ne,{}),t==="enrollments"&&e.jsx(we,{})]})}function pe(){const[t,s]=d.useState(null),[c,r]=d.useState(!0);d.useEffect(()=>{o()},[]);async function o(){try{r(!0);const i=await be.getOverviewStats();s(i)}catch(i){console.error("통계 로드 실패:",i),x.error("통계를 불러오지 못했습니다")}finally{r(!1)}}return c?e.jsx("div",{className:"text-center py-12 text-gray-500 text-[13px]",children:"로딩 중..."}):t?e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"grid grid-cols-2 lg:grid-cols-4 gap-3",children:[e.jsx($,{label:"전체 프로그램",value:t.totalPrograms,icon:q,color:"blue"}),e.jsx($,{label:"모집중",value:t.recruitingPrograms,icon:ee,color:"green"}),e.jsx($,{label:"진행중",value:t.activePrograms,icon:te,color:"blue"}),e.jsx($,{label:"총 신청자",value:t.totalEnrollments,icon:V,color:"blue"})]}),e.jsxs("div",{className:"bg-white rounded-lg border border-gray-200 overflow-hidden",children:[e.jsx("div",{className:"px-4 py-3 border-b border-gray-100",children:e.jsx("h3",{className:"text-[14px] font-semibold text-gray-900",children:"프로그램별 신청 현황"})}),t.programStats.length===0?e.jsxs("div",{className:"text-center py-8 text-gray-400 text-[13px]",children:[e.jsx(q,{className:"w-8 h-8 mx-auto mb-2 opacity-30"}),"등록된 프로그램이 없습니다"]}):e.jsx("div",{className:"divide-y divide-gray-50",children:t.programStats.map(i=>e.jsxs("div",{className:"px-4 py-3 flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("span",{className:"inline-block w-2 h-2 rounded-full",style:{backgroundColor:Q[i.category]}}),e.jsxs("div",{children:[e.jsx("div",{className:"text-[13px] font-medium text-gray-900",children:i.programName}),e.jsx("div",{className:"text-[11px] text-gray-500",children:O[i.category]})]})]}),e.jsxs("div",{className:"flex items-center gap-4 text-[12px]",children:[e.jsxs("span",{className:"text-gray-600",children:["신청 ",e.jsx("span",{className:"font-semibold text-gray-900",children:i.totalEnrollments})]}),e.jsxs("span",{className:"text-green-600",children:["확정 ",e.jsx("span",{className:"font-semibold",children:i.confirmedEnrollments})]}),e.jsxs("span",{className:"text-orange-600",children:["대기 ",e.jsx("span",{className:"font-semibold",children:i.pendingEnrollments})]})]})]},i.programId))})]})]}):e.jsx("div",{className:"text-center py-12 text-gray-500 text-[13px]",children:"데이터를 불러올 수 없습니다"})}function $({label:t,value:s,icon:c,color:r}){const o={orange:"bg-orange-50 text-orange-600",green:"bg-green-50 text-green-600",blue:"bg-blue-50 text-blue-600",purple:"bg-purple-50 text-purple-600"};return e.jsxs("div",{className:"bg-white rounded-lg border border-gray-200 p-4",children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("span",{className:"text-[12px] text-gray-500",children:t}),e.jsx("div",{className:`w-8 h-8 rounded-lg flex items-center justify-center ${o[r]}`,children:e.jsx(c,{className:"w-4 h-4"})})]}),e.jsx("div",{className:"text-[24px] font-bold text-gray-900",children:s})]})}function je(t){const s=t.data;return s?e.jsxs("div",{className:"flex gap-1 items-center h-full",children:[e.jsx("button",{className:"px-2 py-1 text-[11px] bg-blue-600 text-white rounded hover:bg-blue-700",onClick:c=>{var r;c.stopPropagation(),(r=t.onEdit)==null||r.call(t,s)},title:"상세편집",children:"수정"}),e.jsx("button",{className:"px-2 py-1 text-[11px] bg-red-500 text-white rounded hover:bg-red-600",onClick:c=>{var r;c.stopPropagation(),(r=t.onDelete)==null||r.call(t,s.id,s.name)},title:"삭제",children:"삭제"})]}):null}
1function Ne(){const[t,s]=d.useState([]),[c,r]=d.useState(!0),[o,i]=d.useState(!1),[j,N]=d.useState(null),[p,v]=d.useState("all"),y=d.useCallback(async()=>{try{r(!0);const a=await E.getAllPrograms();s(a)}catch(a){console.error("프로그램 로드 실패:",a),x.error("프로그램을 불러오지 못했습니다")}finally{r(!1)}},[]);d.useEffect(()=>{y()},[y]);const f=p==="all"?t:t.filter(a=>a.category===p);async function P(a,m){if(confirm(`"${m}" 프로그램을 삭제하시겠습니까?
2연관된 신청 데이터도 함께 삭제됩니다.`))try{await E.deleteProgram(a),x.success("프로그램이 삭제되었습니다"),y()}catch{x.error("삭제에 실패했습니다")}}function L(){N(null),i(!0)}function I(){i(!1),N(null),y()}const A=d.useCallback(async a=>{const m=a.data,u=a.column.colId;let b=a.newValue;(u==="sortOrder"||u==="year")&&(b=b?Number(b):u==="year"?new Date().getFullYear():0);try{await E.updateProgram(m.id,{[u]:b}),x.success("저장되었습니다")}catch(w){console.error("저장 실패:",w),x.error("저장에 실패했습니다"),a.node.setDataValue(u,a.oldValue)}},[]),T=d.useCallback(a=>{N(a.data),i(!0)},[]),n=d.useMemo(()=>[{headerName:"순서",field:"sortOrder",width:70,editable:!0,cellDataType:"number"},{headerName:"",field:"emoji",width:50,editable:!0,filter:!1,sortable:!1},{headerName:"프로그램명",field:"name",width:150,editable:!0,cellStyle:{fontWeight:"bold"}},{headerName:"카테고리",field:"category",width:100,editable:!0,cellEditor:"agSelectCellEditor",cellEditorParams:{values:["baby","couple","family","senior"]},valueFormatter:a=>O[a.value]||a.value,cellStyle:a=>({color:Q[a.value]||"#4b5563",fontWeight:500})},{headerName:"한줄소개",field:"tagline",width:150,editable:!0},{headerName:"대상",field:"target",width:100,editable:!0},{headerName:"일정",field:"schedule",width:140,editable:!0},{headerName:"장소",field:"location",width:100,editable:!0},{headerName:"참가비",field:"fee",width:80,editable:!0},{headerName:"상태",field:"status",width:90,editable:!0,cellEditor:"agSelectCellEditor",cellEditorParams:{values:["recruiting","upcoming","active","closed"]},valueFormatter:a=>X[a.value]||a.value,cellStyle:a=>{const m={recruiting:{bg:"#dcfce7",color:"#16a34a"},upcoming:{bg:"#ffedd5",color:"#ea580c"},active:{bg:"#dbeafe",color:"#2563eb"},closed:{bg:"#f3f4f6",color:"#4b5563"}},u=m[a.value]||m.closed;return{backgroundColor:u.bg,color:u.color,fontWeight:500}}},{headerName:"연도",field:"year",width:70,editable:!0,cellDataType:"number"},{headerName:"액션",field:"id",width:120,editable:!1,sortable:!1,filter:!1,cellRenderer:je,cellRendererParams:{onEdit:a=>{N(a),i(!0)},onDelete:P}}],[]),l=d.useMemo(()=>({sortable:!0,filter:!0,resizable:!0,minWidth:50,editable:!1,cellStyle:{display:"flex",alignItems:"center",fontSize:"12px",lineHeight:"normal"}}),[]);return c?e.jsx("div",{className:"text-center py-12 text-gray-500 text-[13px]",children:"로딩 중..."}):e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("select",{value:p,onChange:a=>v(a.target.value),className:"text-[13px] border border-gray-300 rounded-lg px-3 py-1.5 bg-white",children:[e.jsx("option",{value:"all",children:"전체 카테고리"}),Object.entries(O).map(([a,m])=>e.jsx("option",{value:a,children:m},a))]}),e.jsxs("span",{className:"text-[12px] text-gray-500",children:[f.length,"개 프로그램"]})]}),e.jsxs("button",{onClick:L,className:"flex items-center gap-1.5 px-3 py-1.5 bg-blue-600 text-white rounded-lg text-[13px] font-medium hover:bg-blue-700 transition-colors",children:[e.jsx(ae,{className:"w-4 h-4"}),"프로그램 추가"]})]}),e.jsxs("div",{className:"ag-theme-alpine",style:{height:Math.max(300,Math.min(f.length*42+50,600)),width:"100%"},children:[e.jsx("style",{children:`
3          .ag-theme-alpine .ag-cell {
4            font-size: 12px !important;
5          }
6          .ag-theme-alpine .ag-header-cell-label {
7            font-size: 12px !important;
8          }
9        `}),e.jsx(J,{rowData:f,columnDefs:n,defaultColDef:l,onCellValueChanged:A,onRowDoubleClicked:T,undoRedoCellEditing:!0,undoRedoCellEditingLimit:20,animateRows:!0,rowHeight:40,enableCellTextSelection:!0,ensureDomOrder:!0,getRowId:a=>a.data.id,noRowsOverlayComponent:()=>e.jsxs("div",{className:"flex flex-col items-center justify-center py-8",children:[e.jsx(q,{className:"w-10 h-10 text-gray-300 mb-2"}),e.jsx("p",{className:"text-[13px] text-gray-500",children:"등록된 프로그램이 없습니다"})]})})]}),o&&e.jsx(ve,{program:j,onClose:I})]})}function ve({program:t,onClose:s}){const c=!!t,[r,o]=d.useState({name:(t==null?void 0:t.name)||"",tagline:(t==null?void 0:t.tagline)||"",category:(t==null?void 0:t.category)||"family",catLabel:(t==null?void 0:t.catLabel)||"",emoji:(t==null?void 0:t.emoji)||"❤️",gradient:(t==null?void 0:t.gradient)||"linear-gradient(135deg, #E8734A, #D45A30)",
9imageUrl:(t==null?void 0:t.imageUrl)||"",target:(t==null?void 0:t.target)||"",schedule:(t==null?void 0:t.schedule)||"",location:(t==null?void 0:t.location)||"서둔교회",fee:(t==null?void 0:t.fee)||"무료",status:(t==null?void 0:t.status)||"upcoming",description:(t==null?void 0:t.description)||"",curriculum:(t==null?void 0:t.curriculum)||[],infoCards:(t==null?void 0:t.infoCards)||[],sortOrder:(t==null?void 0:t.sortOrder)||1,year:(t==null?void 0:t.year)||new Date().getFullYear()}),[i,j]=d.useState(!1),[N,p]=d.useState(!1),[v,y]=d.useState(""),[f,P]=d.useState(!1);function L(l){o(a=>({...a,category:l,catLabel:O[l]}))}async function I(l){var u;const a=(u=l.target.files)==null?void 0:u[0];if(!a)return;const m=5*1024*1024;if(a.size>m){x.error("파일 크기는 5MB 이하만 가능합니다");return}try{p(!0);const b=a.name.split(".").pop()||"jpg",w=(t==null?void 0:t.id)||`new-${Date.now()}`,h=he(fe,`family/programs/${w}/cover.${b}`);await ge(h,a);const D=await ye(h);o(_=>({..._,imageUrl:D})),P(!1),x.success("이미지가 업로드되었습니다")}catch(b){console.error("이미지 업로드 실패:",b),x.error("이미지 업로드에 실패했습니다")}finally{p(!1)}}function A(){v.trim()&&(o(l=>({...l,curriculum:[...l.curriculum,v.trim()]})),y(""))}function T(l){o(a=>({...a,curriculum:a.curriculum.filter((m,u)=>u!==l)}))}async function n(){if(!r.name.trim()){x.error("프로그램명을 입력해주세요");return}try{j(!0);const l={...r,catLabel:r.catLabel||O[r.category]};c&&t?(await E.updateProgram(t.id,l),x.success("프로그램이 수정되었습니다")):(await E.createProgram(l),x.success("프로그램이 추가되었습니다")),s()}catch{x.error("저장에 실패했습니다")}finally{j(!1)}}return e.jsx("div",{className:"fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4",children:e.jsxs("div",{className:"bg-white rounded-xl w-full max-w-2xl max-h-[90vh] overflow-y-auto",children:[e.jsxs("div",{className:"sticky top-0 bg-white px-6 py-4 border-b border-gray-200 flex items-center justify-between z-10",children:[e.jsx("h2",{className:"text-[16px] font-bold text-gray-900",children:c?"프로그램 수정":"새 프로그램 추가"}),e.jsx("button",{onClick:s,className:"p-1 text-gray-400 hover:text-gray-600",children:e.jsx(H,{className:"w-5 h-5"})})]}),e.jsxs("div",{className:"p-6 space-y-5",children:[e.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[e.jsxs("div",{className:"col-span-2",children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"프로그램명 *"}),e.jsx("input",{type:"text",value:r.name,onChange:l=>o(a=>({...a,name:l.target.value})),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px]",placeholder:"프로그램 이름"})]}),e.jsxs("div",{className:"col-span-2",children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"한줄 소개"}),e.jsx("input",{type:"text",value:r.tagline,onChange:l=>o(a=>({...a,tagline:l.target.value})),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px]",placeholder:"한줄 홍보 문구"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"카테고리"}),e.jsx("select",{value:r.category,onChange:l=>L(l.target.value),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px]",children:Object.entries(O).map(([l,a])=>e.jsx("option",{value:l,children:a},l))})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"상태"}),e.jsx("select",{value:r.status,onChange:l=>o(a=>({...a,status:l.target.value})),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px]",children:Object.entries(X).map(([l,a])=>e.jsx("option",{value:l,children:a},l))})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"이모지"}),e.jsx("input",{type:"text",value:r.emoji,onChange:l=>o(a=>({...a,emoji:l.target.value})),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px]",placeholder:"❤️"})]}),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:r.sortOrder,onChange:l=>o(a=>({...a,sortOrder:Number(l.target.value)})),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px]",min:1})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"대표 이미지"}),e.jsxs("div",{className:"flex items-center gap-3",children:[r.imageUrl&&!f?e.jsx("img",{src:r.imageUrl,alt:"대표 이미지",className:"w-20 h-20 rounded-lg object-cover",onError:()=>P(!0)}):e.jsx("div",{className:"w-20 h-20 rounded-lg bg-gray-100 flex items-center justify-center",children:e.jsx(oe,{className:"w-6 h-6 text-gray-300"})}),e.jsxs("label",{className:"cursor-pointer",children:[e.jsx("input",{type:"file",accept:"image/*",onChange:I,className:"hidden"}),e.jsx("span",{className:"flex items-center gap-1.5 px-3 py-1.5 bg-gray-100 text-gray-700 rounded-lg text-[12px] hover:bg-gray-200 transition-colors",children:N?"업로드 ì
9¤‘...":e.jsxs(e.Fragment,{children:[e.jsx(ie,{className:"w-3.5 h-3.5"})," 이미지 변경"]})})]})]})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"대상"}),e.jsx("input",{type:"text",value:r.target,onChange:l=>o(a=>({...a,target:l.target.value})),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px]",placeholder:"참여 대상"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"일정"}),e.jsx("input",{type:"text",value:r.schedule,onChange:l=>o(a=>({...a,schedule:l.target.value})),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px]",placeholder:"예: 3주 과정, 매주 금 10시"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"장소"}),e.jsx("input",{type:"text",value:r.location,onChange:l=>o(a=>({...a,location:l.target.value})),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px]",placeholder:"장소"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"참가비"}),e.jsx("input",{type:"text",value:r.fee,onChange:l=>o(a=>({...a,fee:l.target.value})),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px]",placeholder:"무료 / 금액"})]}),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:r.year,onChange:l=>o(a=>({...a,year:Number(l.target.value)})),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px]"})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-1",children:"상세 설명"}),e.jsx("textarea",{value:r.description,onChange:l=>o(a=>({...a,description:l.target.value})),className:"w-full border border-gray-300 rounded-lg px-3 py-2 text-[13px] h-32 resize-none",placeholder:"프로그램 소개"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[12px] font-medium text-gray-700 mb-2",children:"커리큘럼"}),r.curriculum.length>0&&e.jsx("div",{className:"space-y-1 mb-2",children:r.curriculum.map((l,a)=>e.jsxs("div",{className:"flex items-center gap-2 bg-gray-50 rounded px-3 py-1.5",children:[e.jsxs("span",{className:"text-[12px] text-gray-500 font-mono w-5",children:[a+1,"."]}),e.jsx("span",{className:"flex-1 text-[12px] text-gray-700",children:l}),e.jsx("button",{onClick:()=>T(a),className:"text-gray-400 hover:text-red-500",children:e.jsx(H,{className:"w-3.5 h-3.5"})})]},a))}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("input",{type:"text",value:v,onChange:l=>y(l.target.value),onKeyDown:l=>l.key==="Enter"&&(l.preventDefault(),A()),className:"flex-1 border border-gray-300 rounded-lg px-3 py-1.5 text-[12px]",placeholder:"커리큘럼 항목 입력 후 Enter"}),e.jsx("button",{onClick:A,className:"px-3 py-1.5 bg-gray-100 text-gray-700 rounded-lg text-[12px] hover:bg-gray-200",children:"추가"})]})]})]}),e.jsxs("div",{className:"sticky bottom-0 bg-white px-6 py-4 border-t border-gray-200 flex items-center justify-end gap-2",children:[e.jsx("button",{onClick:s,className:"px-4 py-2 text-[13px] text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors",children:"취소"}),e.jsx("button",{onClick:n,disabled:i,className:"px-4 py-2 text-[13px] text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50",children:i?"저장 중...":c?"수정":"추가"})]})]})})}function we(){const[t,s]=d.useState([]),[c,r]=d.useState([]),[o,i]=d.useState(!0),[j,N]=d.useState("all"),[p,v]=d.useState("all"),y=d.useCallback(async()=>{try{i(!0);const[n,l]=await Promise.all([E.getAllPrograms(),M.getAllEnrollments()]);s(n),r(l)}catch(n){console.error("데이터 로드 실패:",n),x.error("데이터를 불러오지 못했습니다")}finally{i(!1)}},[]);d.useEffect(()=>{y()},[y]);const f=c.filter(n=>!(j!=="all"&&n.programId!==j||p!=="all"&&n.status!==p));async function P(n){try{await M.confirmEnrollment(n),x.success("신청이 확정되었습니다"),y()}catch{x.error("확정에 실패했습니다")}}
9async function L(n){if(confirm("이 신청을 취소하시겠습니까?"))try{await M.cancelEnrollment(n),x.success("신청이 취소되었습니다"),y()}catch{x.error("취소에 실패했습니다")}}async function I(n){if(confirm("이 신청 데이터를 완전히 삭제하시겠습니까?"))try{await M.deleteEnrollment(n),x.success("삭제되었습니다"),y()}catch{x.error("삭제에 실패했습니다")}}function A(){if(f.length===0){x.error("다운로드할 데이터가 없습니다");return}const n=["프로그램","이름","연락처","교인여부","배우자","자녀","참고사항","상태","신청일"],l=f.map(h=>{var _,K;const D=h.createdAt?h.createdAt instanceof Date?h.createdAt:new Date(h.createdAt.seconds*1e3):null;return[h.programName,h.name,h.phone,h.isMember?"교인":"비교인",((_=h.familyInfo)==null?void 0:_.spouse)||"",((K=h.familyInfo)==null?void 0:K.children)||"",h.note||"",G[h.status],D?D.toLocaleDateString("ko-KR"):""]}),a=[n,...l].map(h=>h.map(D=>`"${String(D).replace(/"/g,'""')}"`).join(",")).join(`
10`),m="\uFEFF",u=new Blob([m+a],{type:"text/csv;charset=utf-8;"}),b=URL.createObjectURL(u),w=document.createElement("a");w.href=b,w.download=`가정사역부_신청현황_${new Date().toISOString().split("T")[0]}.csv`,w.click(),URL.revokeObjectURL(b),x.success("다운로드 완료")}function T(n){return!n||n.length<8?n:n.replace(/(\d{3})[-]?(\d{3,4})[-]?(\d{4})/,"$1-****-$3")}return o?e.jsx("div",{className:"text-center py-12 text-gray-500 text-[13px]",children:"로딩 중..."}):e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-center justify-between flex-wrap gap-2",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("select",{value:j,onChange:n=>N(n.target.value),className:"text-[13px] border border-gray-300 rounded-lg px-3 py-1.5 bg-white",children:[e.jsx("option",{value:"all",children:"전체 프로그램"}),t.map(n=>e.jsxs("option",{value:n.id,children:[n.emoji," ",n.name]},n.id))]}),e.jsxs("select",{value:p,onChange:n=>v(n.target.value),className:"text-[13px] border border-gray-300 rounded-lg px-3 py-1.5 bg-white",children:[e.jsx("option",{value:"all",children:"전체 상태"}),Object.entries(G).map(([n,l])=>e.jsx("option",{value:n,children:l},n))]}),e.jsxs("span",{className:"text-[12px] text-gray-500",children:[f.length,"건"]})]}),e.jsxs("button",{onClick:A,className:"flex items-center gap-1.5 px-3 py-1.5 bg-green-600 text-white rounded-lg text-[13px] font-medium hover:bg-green-700 transition-colors",children:[e.jsx(se,{className:"w-4 h-4"}),"CSV 다운로드"]})]}),f.length===0?e.jsxs("div",{className:"text-center py-12 text-gray-400",children:[e.jsx(V,{className:"w-10 h-10 mx-auto mb-2 opacity-30"}),e.jsx("p",{className:"text-[13px]",children:"신청 내역이 없습니다"})]}):e.jsx("div",{className:"bg-white rounded-lg border border-gray-200 overflow-x-auto",children:e.jsxs("table",{className:"w-full text-[13px]",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"bg-gray-50 border-b border-gray-200",children:[e.jsx("th",{className:"text-left px-4 py-2.5 font-medium text-gray-600",children:"프로그램"}),e.jsx("th",{className:"text-left px-4 py-2.5 font-medium text-gray-600",children:"이름"}),e.jsx("th",{className:"text-left px-4 py-2.5 font-medium text-gray-600",children:"연락처"}),e.jsx("th",{className:"text-left px-4 py-2.5 font-medium text-gray-600",children:"교인"}),e.jsx("th",{className:"text-left px-4 py-2.5 font-medium text-gray-600",children:"가족정보"}),e.jsx("th",{className:"text-left px-4 py-2.5 font-medium text-gray-600",children:"상태"}),e.jsx("th",{className:"text-left px-4 py-2.5 font-medium text-gray-600",children:"신청일"}),e.jsx("th",{className:"text-right px-4 py-2.5 font-medium text-gray-600",children:"관리"})]})}),e.jsx("tbody",{className:"divide-y divide-gray-50",children:f.map(n=>{var m,u;const l=n.createdAt?n.createdAt instanceof Date?n.createdAt:new Date(n.createdAt.seconds*1e3):null,a={pending:"bg-yellow-100 text-yellow-700",confirmed:"bg-green-100 text-green-700",cancelled:"bg-red-100 text-red-600"};return e.jsxs("tr",{className:"hover:bg-gray-50/50",children:[e.jsx("td",{className:"px-4 py-2.5",children:e.jsx("span",{className:"text-[12px] text-gray-700",children:n.programName})}),e.jsx("td",{className:"px-4 py-2.5",children:e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx(re,{className:"w-3.5 h-3.5 text-gray-400"}),e.jsx("span",{className:"font-medium text-gray-900",children:n.name})]})}),e.jsx("td",{className:"px-4 py-2.5",children:e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx(le,{className:"w-3.5 h-3.5 text-gray-400"}),e.jsx("span",{className:"text-gray-600",children:T(n.phone)})]})}),e.jsx("td",{className:"px-4 py-2.5",children:e.jsx("span",{className:`text-[11px] px-1.5 py-0.5 rounded-full ${n.isMember?"bg-blue-100 text-blue-700":"bg-gray-100 text-gray-600"}
10`,children:n.isMember?"교인":"비교인"})}),e.jsx("td",{className:"px-4 py-2.5",children:e.jsxs("div",{className:"text-[12px] text-gray-600",children:[((m=n.familyInfo)==null?void 0:m.spouse)&&e.jsxs("div",{children:["배우자: ",n.familyInfo.spouse]}),((u=n.familyInfo)==null?void 0:u.children)&&e.jsxs("div",{children:["자녀: ",n.familyInfo.children]})]})}),e.jsx("td",{className:"px-4 py-2.5",children:e.jsx("span",{className:`text-[11px] px-2 py-0.5 rounded-full font-medium ${a[n.status]}`,children:G[n.status]})}),e.jsx("td",{className:"px-4 py-2.5 text-[12px] text-gray-500",children:l?l.toLocaleDateString("ko-KR"):"-"}),e.jsx("td",{className:"px-4 py-2.5",children:e.jsxs("div",{className:"flex items-center justify-end gap-1",children:[n.status==="pending"&&e.jsx("button",{onClick:()=>P(n.id),className:"p-1 text-green-600 hover:bg-green-50 rounded transition-colors",title:"확정",children:e.jsx(ne,{className:"w-4 h-4"})}),n.status!=="cancelled"&&e.jsx("button",{onClick:()=>L(n.id),className:"p-1 text-orange-600 hover:bg-orange-50 rounded transition-colors",title:"취소",children:e.jsx(H,{className:"w-4 h-4"})}),e.jsx("button",{onClick:()=>I(n.id),className:"p-1 text-red-500 hover:bg-red-50 rounded transition-colors",title:"삭제",children:e.jsx(ce,{className:"w-4 h-4"})})]})})]},n.id)})})]})})]})}export{Oe as FamilyProgramManagementPage};

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.