All files / src/components/features/admin/dev/DevProjectManagement index.tsx

0% Statements 0/155
100% Branches 0/0
0% Functions 0/1
0% Lines 0/155

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156                                                                                                                                                                                                                                                                                                                       
'use client';

/**
 * DevProjectManagement - Admin dev project management interface
 */

import React, { useState, useEffect, useCallback } from 'react';
import Link from 'next/link';
import DevProjectList from './DevProjectList';
import { Icon } from '@/components/ui/icons';

interface ProjectWithStats {
  id: string;
  name: string;
  key: string;
  description: string | null;
  color: string;
  isActive: boolean;
  lead: {
    id: number;
    name: string | null;
    email: string;
    image: string | null;
  } | null;
  createdAt: string;
  updatedAt: string;
  _count: {
    tickets: number;
    milestones: number;
    sprints: number;
  };
}

export default function DevProjectManagement() {
  const [projects, setProjects] = useState<ProjectWithStats[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [showInactive, setShowInactive] = useState(false);

  const fetchProjects = useCallback(async () => {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch('/api/dev/projects');
      if (!response.ok) {
        throw new Error('Failed to fetch projects');
      }

      const data = await response.json();
      setProjects(data.data || []);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'An error occurred');
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    fetchProjects();
  }, [fetchProjects]);

  const handleDelete = async (id: string) => {
    if (!confirm('Are you sure you want to delete this project? This action cannot be undone.')) {
      return;
    }

    try {
      const response = await fetch(`/api/dev/projects/${id}`, {
        method: 'DELETE'});

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error || 'Failed to delete project');
      }

      fetchProjects();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to delete project');
    }
  };

  const filteredProjects = showInactive
    ? projects
    : projects.filter((p) => p.isActive);

  const activeCount = projects.filter((p) => p.isActive).length;
  const inactiveCount = projects.length - activeCount;

  return (
    <div className="max-w-[1400px] mx-auto px-4 sm:px-7.5 xl:px-0 space-y-6">
      {/* Header */}
      <div className="flex items-center justify-between">
        <div>
          <h1 className="text-2xl font-bold text-gray-900">Projects</h1>
          <p className="text-sm text-gray-500 mt-1">
            Manage development projects and teams
          </p>
        </div>
        <Link
          href="/admin/dev/projects/new"
          className="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
        >
          <Icon name="plus" size={16} className="mr-2" />
          New Project
        </Link>
      </div>

      {/* Stats & Filters */}
      <div className="flex items-center justify-between bg-white rounded-lg border border-gray-200 p-4">
        <div className="flex items-center gap-4">
          <span className="text-sm text-gray-600">
            <span className="font-semibold text-gray-900">{activeCount}</span> active projects
          </span>
          {inactiveCount > 0 && (
            <span className="text-sm text-gray-600">
              <span className="font-semibold text-gray-900">{inactiveCount}</span> inactive
            </span>
          )}
        </div>
        {inactiveCount > 0 && (
          <label className="flex items-center text-sm text-gray-600">
            <input
              type="checkbox"
              checked={showInactive}
              onChange={(e) => setShowInactive(e.target.checked)}
              className="h-4 w-4 text-indigo-600 rounded border-gray-300 focus:ring-indigo-500 mr-2"
            />
            Show inactive projects
          </label>
        )}
      </div>

      {/* Error */}
      {error && (
        <div className="bg-red-50 text-red-700 px-4 py-3 rounded-lg flex items-center justify-between">
          <span>{error}</span>
          <button
            onClick={() => setError(null)}
            className="text-red-500 hover:text-red-700"
          >
            <Icon name="close" size={20} />
          </button>
        </div>
      )}

      {/* Project Grid */}
      <DevProjectList
        projects={filteredProjects}
        loading={loading}
        onDelete={handleDelete}
      />
    </div>
  );
}