summaryrefslogblamecommitdiff
path: root/makima/frontend/src/components/templates/TemplateEditor.tsx
blob: c8e1f984647d8a11846cce9413d3d9d6875de580 (plain) (tree)
1
2
3
4
5
6
7
8
9
10






                                                                                  
                     

 
                                                                                         

































































































                                                                                      
                                                                



                                                                   




                                                               















                                                                                                                                     
                                                                                                                                                                                                                     


                                                                                 
                                   





































































































                                                                                                                                                                                                                                   
                                         
                 








                                                                                                                                                                                                  



            
import { useState } from "react";
import type { ContractTemplate, Phase, Deliverable } from "../../types/templates";

interface Props {
  template: ContractTemplate;
  onSave: (template: ContractTemplate) => void;
  onCancel: () => void;
  readOnly?: boolean;
}

export function TemplateEditor({ template, onSave, onCancel, readOnly = false }: Props) {
  const [editedTemplate, setEditedTemplate] = useState<ContractTemplate>({
    ...template,
    phases: template.phases.map((p) => ({
      ...p,
      deliverables: [...p.deliverables],
    })),
  });
  const [newDeliverableName, setNewDeliverableName] = useState<{
    [phaseId: string]: string;
  }>({});

  const handlePhaseNameChange = (phaseId: string, newName: string) => {
    setEditedTemplate((prev) => ({
      ...prev,
      phases: prev.phases.map((p) =>
        p.id === phaseId ? { ...p, name: newName } : p
      ),
    }));
  };

  const handleDeliverableNameChange = (
    phaseId: string,
    deliverableId: string,
    newName: string
  ) => {
    setEditedTemplate((prev) => ({
      ...prev,
      phases: prev.phases.map((p) =>
        p.id === phaseId
          ? {
              ...p,
              deliverables: p.deliverables.map((d) =>
                d.id === deliverableId ? { ...d, name: newName } : d
              ),
            }
          : p
      ),
    }));
  };

  const handleAddDeliverable = (phaseId: string) => {
    const name = newDeliverableName[phaseId]?.trim();
    if (!name) return;

    const newDeliverable: Deliverable = {
      id: `deliverable-${Date.now()}`,
      name,
    };

    setEditedTemplate((prev) => ({
      ...prev,
      phases: prev.phases.map((p) =>
        p.id === phaseId
          ? { ...p, deliverables: [...p.deliverables, newDeliverable] }
          : p
      ),
    }));
    setNewDeliverableName((prev) => ({ ...prev, [phaseId]: "" }));
  };

  const handleRemoveDeliverable = (phaseId: string, deliverableId: string) => {
    setEditedTemplate((prev) => ({
      ...prev,
      phases: prev.phases.map((p) =>
        p.id === phaseId
          ? {
              ...p,
              deliverables: p.deliverables.filter((d) => d.id !== deliverableId),
            }
          : p
      ),
    }));
  };

  const handleAddPhase = () => {
    const newPhase: Phase = {
      id: `phase-${Date.now()}`,
      name: "New Phase",
      deliverables: [],
    };
    setEditedTemplate((prev) => ({
      ...prev,
      phases: [...prev.phases, newPhase],
    }));
  };

  const handleRemovePhase = (phaseId: string) => {
    setEditedTemplate((prev) => ({
      ...prev,
      phases: prev.phases.filter((p) => p.id !== phaseId),
    }));
  };

  return (
    <div className="bg-[#0d1b2d] border border-[rgba(117,170,252,0.35)] p-6">
      {/* Header */}
      <div className="mb-6 pb-4 border-b border-[rgba(117,170,252,0.15)]">
        <h2 className="text-sm font-mono uppercase tracking-wide text-[#9bc3ff] mb-1">
          {readOnly ? "View" : "Edit"} Template: {template.name}
        </h2>
        <p className="text-xs font-mono text-[#75aafc] opacity-70">
          {template.description}
        </p>
        {readOnly && (
          <p className="text-xs font-mono text-amber-400 mt-2">
            Built-in templates are read-only
          </p>
        )}
      </div>

      {/* Phases */}
      <div className="space-y-4 mb-6">
        {editedTemplate.phases.map((phase, phaseIndex) => (
          <div
            key={phase.id}
            className="bg-[rgba(0,0,0,0.3)] border border-[rgba(117,170,252,0.2)] p-4"
          >
            {/* Phase Header */}
            <div className="flex items-center gap-3 mb-3">
              <span className="w-6 h-6 flex items-center justify-center bg-[rgba(117,170,252,0.2)] text-[#9bc3ff] text-xs font-mono">
                {phaseIndex + 1}
              </span>
              <input
                type="text"
                className="flex-1 px-3 py-1.5 bg-transparent border border-[rgba(117,170,252,0.25)] text-white font-mono text-sm placeholder-[#556677] focus:outline-none focus:border-[#3f6fb3] disabled:opacity-60"
                value={phase.name}
                onChange={(e) => handlePhaseNameChange(phase.id, e.target.value)}
                placeholder="Phase name"
                disabled={readOnly}
              />
              {!template.isBuiltIn && (
                <button
                  type="button"
                  onClick={() => handleRemovePhase(phase.id)}
                  className="w-7 h-7 flex items-center justify-center border border-[rgba(255,100,100,0.3)] text-[#ff6464] hover:bg-[rgba(255,100,100,0.1)] transition-colors text-sm"
                  title="Remove phase"
                >
                  x
                </button>
              )}
            </div>

            {/* Deliverables */}
            <div className="ml-9 space-y-2">
              {phase.deliverables.length === 0 ? (
                <div className="text-xs font-mono text-[#556677] italic">
                  No deliverables
                </div>
              ) : (
                phase.deliverables.map((deliverable) => (
                  <div
                    key={deliverable.id}
                    className="flex items-center gap-2"
                  >
                    <span className="text-[#75aafc] text-xs">-</span>
                    <input
                      type="text"
                      className="flex-1 px-2 py-1 bg-transparent border border-[rgba(117,170,252,0.15)] text-[#dbe7ff] font-mono text-xs focus:outline-none focus:border-[#3f6fb3]"
                      value={deliverable.name}
                      onChange={(e) =>
                        handleDeliverableNameChange(
                          phase.id,
                          deliverable.id,
                          e.target.value
                        )
                      }
                    />
                    <button
                      type="button"
                      onClick={() =>
                        handleRemoveDeliverable(phase.id, deliverable.id)
                      }
                      className="w-5 h-5 flex items-center justify-center text-[#ff6464] hover:bg-[rgba(255,100,100,0.1)] transition-colors text-xs"
                      title="Remove deliverable"
                    >
                      x
                    </button>
                  </div>
                ))
              )}

              {/* Add Deliverable */}
              <div className="flex items-center gap-2 pt-2">
                <input
                  type="text"
                  className="flex-1 px-2 py-1 bg-transparent border border-[rgba(117,170,252,0.15)] text-[#dbe7ff] font-mono text-xs placeholder-[#445566] focus:outline-none focus:border-[#3f6fb3]"
                  placeholder="New deliverable name..."
                  value={newDeliverableName[phase.id] || ""}
                  onChange={(e) =>
                    setNewDeliverableName((prev) => ({
                      ...prev,
                      [phase.id]: e.target.value,
                    }))
                  }
                  onKeyPress={(e) => {
                    if (e.key === "Enter") {
                      handleAddDeliverable(phase.id);
                    }
                  }}
                />
                <button
                  type="button"
                  onClick={() => handleAddDeliverable(phase.id)}
                  className="px-2 py-1 border border-[rgba(117,170,252,0.3)] text-[#9bc3ff] font-mono text-xs hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.1)] transition-colors"
                >
                  + Add
                </button>
              </div>
            </div>
          </div>
        ))}
      </div>

      {/* Add Phase (only for custom templates) */}
      {!template.isBuiltIn && (
        <button
          type="button"
          onClick={handleAddPhase}
          className="w-full mb-6 px-4 py-2 border border-dashed border-[rgba(117,170,252,0.3)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.05)] transition-colors"
        >
          + Add Phase
        </button>
      )}

      {/* Footer Actions */}
      <div className="flex gap-3 justify-end pt-4 border-t border-[rgba(117,170,252,0.15)]">
        <button
          type="button"
          onClick={onCancel}
          className="px-4 py-2 border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] transition-colors"
        >
          {readOnly ? "Close" : "Cancel"}
        </button>
        {!readOnly && (
          <button
            type="button"
            onClick={() => onSave(editedTemplate)}
            className="px-4 py-2 border border-[#3f6fb3] bg-[rgba(117,170,252,0.15)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:bg-[rgba(117,170,252,0.25)] transition-colors"
          >
            Save Changes
          </button>
        )}
      </div>
    </div>
  );
}