| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- 'use client';
- import './style.scss';
- import { useState, useEffect, useCallback } from 'react';
- import { fetchApi } from '@/lib/utils/client';
- import { useStudioContext } from '@/app/studio/context';
- import type { CrewListResponse, CrewItem } from '@/types/response/crew/list';
- const EMPTY_FORM = {
- name: '',
- description: '',
- minAmount: '' as string|number,
- isActive: true
- };
- export default function CrewConfigPage() {
- const { channelID } = useStudioContext();
- const [items, setItems] = useState<CrewItem[]>([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState('');
- const [modal, setModal] = useState<{ open: boolean; editing: CrewItem|null }>({ open: false, editing: null });
- const [form, setForm] = useState(EMPTY_FORM);
- const [saving, setSaving] = useState(false);
- useEffect(() => {
- if (error) { alert(error); setError(''); }
- }, [error]);
- const fetchList = useCallback(() => {
- if (!channelID) {
- setLoading(false);
- return;
- }
- setLoading(true);
- fetchApi<CrewListResponse>(`/api/studio/crew/list/${channelID}`).then(res => {
- setItems(res.data?.list ?? []);
- })
- .catch(err => setError(err.message))
- .finally(() => setLoading(false));
- }, [channelID]);
- useEffect(() => { fetchList(); }, [fetchList]);
- const openAdd = () => {
- setForm(EMPTY_FORM);
- setModal({ open: true, editing: null });
- };
- const openEdit = (item: CrewItem) => {
- setForm({
- name: item.name,
- description: item.description ?? '',
- minAmount: item.minAmount ?? '',
- isActive: item.isActive
- });
- setModal({ open: true, editing: item });
- };
- const closeModal = () => setModal({ open: false, editing: null });
- const handleSave = async () => {
- if (!form.name.trim()) {
- alert('크루명을 입력해 주세요.');
- return;
- }
- setSaving(true);
- try {
- const body = {
- channelID,
- id: modal.editing?.id ?? undefined,
- name: form.name,
- description: form.description || undefined,
- minAmount: form.minAmount !== '' ? Number(form.minAmount) : undefined,
- isActive: form.isActive
- };
- const res = await fetchApi('/api/studio/crew/save', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(body)
- });
- closeModal();
- fetchList();
- } catch (err: unknown) {
- setError(err instanceof Error ? err.message : '저장에 실패했습니다.');
- } finally {
- setSaving(false);
- }
- };
- return (
- <div className="studio-page">
- <div className="studio-page__header">
- <h1 className="studio-page__title">크루 후원 설정</h1>
- <button type="button" className="studio-page__btn studio-page__btn--primary" onClick={openAdd}>
- + 크루 추가
- </button>
- </div>
- <div className="studio-page__table-wrap">
- {loading ? (
- <p className="studio-page__empty">준비 중...</p>
- ) : (
- <table className="studio-page__table">
- <thead>
- <tr>
- <th>크루명</th>
- <th>설명</th>
- <th>최소 후원금</th>
- <th>멤버 수</th>
- <th>활성</th>
- <th>작업</th>
- </tr>
- </thead>
- <tbody>
- {items.length === 0 ? (
- <tr>
- <td colSpan={6} className="studio-page__empty">
- 등록된 크루가 없습니다.
- </td>
- </tr>
- ) : (
- items.map(item => (
- <tr key={item.id}>
- <td className="crew-config__name">{item.name}</td>
- <td className="crew-config__desc">{item.description ?? '-'}</td>
- <td>{item.minAmount ? `${item.minAmount.toLocaleString()}원` : '-'}</td>
- <td>{item.memberCount}명</td>
- <td>
- <span className={`studio-page__badge studio-page__badge--${item.isActive ? 'active' : 'inactive'}`}>
- {item.isActive ? '활성' : '비활성'}
- </span>
- </td>
- <td>
- <div className="studio-page__actions">
- <button type="button" className="studio-page__btn" onClick={() => openEdit(item)}>수정</button>
- </div>
- </td>
- </tr>
- ))
- )}
- </tbody>
- </table>
- )}
- </div>
- {modal.open && (
- <div className="studio-modal">
- <div className="studio-modal__overlay" onClick={closeModal} />
- <div className="studio-modal__box">
- <h2 className="studio-modal__title">{modal.editing ? '크루 수정' : '크루 추가'}</h2>
- <div className="studio-modal__field">
- <label>크루명 *</label>
- <input type="text" value={form.name} onChange={e => setForm(f => ({ ...f, name: e.target.value }))} title='크루명' />
- </div>
- <div className="studio-modal__field">
- <label>설명</label>
- <input type="text" value={form.description} onChange={e => setForm(f => ({ ...f, description: e.target.value }))} title='설명' />
- </div>
- <div className="studio-modal__field">
- <label>최소 후원금 (원)</label>
- <input type="number" min={0} placeholder="미설정" value={form.minAmount} onChange={e => setForm(f => ({ ...f, minAmount: e.target.value }))} />
- <p className="studio-modal__hint">이 금액 이상 후원한 회원만 크루에 가입할 수 있습니다.</p>
- </div>
- <div className="studio-modal__field">
- <label className="studio-modal__checkbox-label">
- <input type="checkbox" checked={form.isActive} onChange={e => setForm(f => ({ ...f, isActive: e.target.checked }))} />
- 활성화
- </label>
- </div>
- <div className="studio-modal__footer">
- <button type="button" className="studio-page__btn" onClick={closeModal}>취소</button>
- <button type="button" className="studio-page__btn studio-page__btn--primary" onClick={handleSave} disabled={saving}>
- {saving ? '저장 중...' : '저장'}
- </button>
- </div>
- </div>
- </div>
- )}
- </div>
- );
- }
|