feat: team org panel, admin CRUD, local deploy tools, bidirectional data sync
Add TeamMember model and APIs, team status UI, /admin page, local server bats, and scripts to sync data between local PostgreSQL and Render. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,7 +13,9 @@ import {
|
||||
import { arrayMove } from '@dnd-kit/sortable';
|
||||
import { apiClient } from '../lib/apiClient';
|
||||
import { useTasks } from '../hooks/useTasks';
|
||||
import { useTeamMembers } from '../hooks/useTeamMembers';
|
||||
import { DashboardHeader } from '../components/dashboard/DashboardHeader';
|
||||
import { TeamStatusPanel } from '../components/dashboard/TeamStatusPanel';
|
||||
import { DepartmentColumn } from '../components/dashboard/DepartmentColumn';
|
||||
import { TaskManager } from '../components/dashboard/TaskManager';
|
||||
import { useSocket } from '../contexts/SocketContext';
|
||||
@@ -42,6 +44,9 @@ export default function DashboardPage() {
|
||||
const [filtersBeforeIssue, setFiltersBeforeIssue] = useState<string[]>([...DEFAULT_STATUS_FILTERS]);
|
||||
const [issueFilterActive, setIssueFilterActive] = useState(false);
|
||||
const [showTaskManager, setShowTaskManager] = useState(false);
|
||||
const [teamPanelOpen, setTeamPanelOpen] = useState(false);
|
||||
const [showAllTeamTasks, setShowAllTeamTasks] = useState(false);
|
||||
const [activeTeamProjectId, setActiveTeamProjectId] = useState<string | null>(null);
|
||||
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
||||
const [columnOrders, setColumnOrders] = useState<Record<string, string[]>>({});
|
||||
const saveTimers = useRef<Record<string, ReturnType<typeof setTimeout>>>({});
|
||||
@@ -50,6 +55,7 @@ export default function DashboardPage() {
|
||||
const socket = useSocket();
|
||||
|
||||
const { data: tasks = [], isLoading } = useTasks({ quarter: QUARTER });
|
||||
const { data: teamMembers = [] } = useTeamMembers();
|
||||
|
||||
const { data: colConfigs } = useQuery({
|
||||
queryKey: ['columns', 'all'],
|
||||
@@ -248,9 +254,35 @@ export default function DashboardPage() {
|
||||
onToggleIssue={handleToggleIssue}
|
||||
onOpenDetailWindow={() => { openDetailWindow(); }}
|
||||
onOpenTaskManager={() => setShowTaskManager(true)}
|
||||
teamPanelOpen={teamPanelOpen}
|
||||
onToggleTeamPanel={() => {
|
||||
setTeamPanelOpen((open) => {
|
||||
if (open) {
|
||||
setActiveTeamProjectId(null);
|
||||
setShowAllTeamTasks(false);
|
||||
}
|
||||
return !open;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<main className="relative flex min-h-0 flex-1 overflow-hidden px-5 py-5">
|
||||
{teamPanelOpen && (
|
||||
<TeamStatusPanel
|
||||
members={teamMembers}
|
||||
tasks={tasks}
|
||||
showAllTasks={showAllTeamTasks}
|
||||
activeProjectId={activeTeamProjectId}
|
||||
onToggleShowAll={() => setShowAllTeamTasks((v) => !v)}
|
||||
onProjectClick={setActiveTeamProjectId}
|
||||
onClose={() => {
|
||||
setTeamPanelOpen(false);
|
||||
setActiveTeamProjectId(null);
|
||||
setShowAllTeamTasks(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<main className={`relative flex min-h-0 flex-1 overflow-hidden px-5 py-5 ${teamPanelOpen ? 'hidden' : ''}`}>
|
||||
{/* ── 좌측 라벨 컬럼 ── */}
|
||||
<div className="mr-4 flex w-16 shrink-0 flex-col overflow-hidden rounded-[2rem] bg-white shadow-[0_16px_40px_rgba(15,23,42,0.12)] ring-1 ring-slate-200/70">
|
||||
<div className="h-10 shrink-0 border-b border-slate-100 bg-slate-50" />
|
||||
@@ -283,6 +315,7 @@ export default function DashboardPage() {
|
||||
quarter={QUARTER}
|
||||
onSelectTask={(t) => sendTaskSelected(t.id)}
|
||||
sectionOptions={sectionOptions}
|
||||
teamMembers={teamMembers}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -307,6 +340,7 @@ export default function DashboardPage() {
|
||||
tasks={tasks}
|
||||
sectionOptions={sectionOptions}
|
||||
quarter={QUARTER}
|
||||
teamMembers={teamMembers}
|
||||
onClose={() => setShowTaskManager(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user