readysite / website / frontend / pages / CollectionEditor.jsx
29.7 KB
CollectionEditor.jsx
import * as React from 'react';
import { useState, useEffect, useCallback } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { apiGet, apiPost, apiPut, apiPatch, apiDelete } from '../hooks/useAPI.js';
import { useChat } from '../layouts/AdminLayout.jsx';

const FIELD_TYPES = ['string', 'text', 'number', 'bool', 'date', 'email', 'url', 'select', 'json'];

export function CollectionEditor() {
  const { toggleChat } = useChat() || {};
  const { id } = useParams();
  const navigate = useNavigate();
  const [collection, setCollection] = useState(null);
  const [documents, setDocuments] = useState([]);
  const [fields, setFields] = useState([]);
  const [loading, setLoading] = useState(true);
  const [showEditModal, setShowEditModal] = useState(false);
  const [showDocModal, setShowDocModal] = useState(false);
  const [showImportModal, setShowImportModal] = useState(false);
  const [saving, setSaving] = useState(false);
  const [importing, setImporting] = useState(false);
  const [importResult, setImportResult] = useState(null);
  const [editFields, setEditFields] = useState([]);
  const [editDoc, setEditDoc] = useState(null);

  const fetchData = useCallback(() => {
    if (!id) return;
    setLoading(true);
    Promise.all([
      apiGet(`/api/admin/collections/${id}`),
      apiGet(`/api/admin/collections/${id}/documents`),
    ]).then(([colData, docsData]) => {
      setCollection(colData);
      setDocuments(docsData?.items || []);
      const schemaFields = colData?.schema ? JSON.parse(colData.schema) : [];
      setFields(schemaFields);
      setEditFields(schemaFields);
    }).finally(() => setLoading(false));
  }, [id]);

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

  const handleEditSubmit = async (e) => {
    e.preventDefault();
    if (!id) return;
    const form = e.target;
    const formData = new FormData(form);

    setSaving(true);
    try {
      await apiPut(`/api/admin/collections/${id}`, {
        name: formData.get('name'),
        description: formData.get('description'),
        schema: JSON.stringify(editFields),
      });
      setShowEditModal(false);
      fetchData();
    } finally {
      setSaving(false);
    }
  };

  const handleDocSubmit = async (e) => {
    e.preventDefault();
    if (!id) return;
    const form = e.target;
    const formData = new FormData(form);
    const data = {};

    fields.forEach((field) => {
      const value = formData.get(field.name);
      if (field.type === 'bool') {
        data[field.name] = value === 'on';
      } else if (field.type === 'number') {
        data[field.name] = value ? parseFloat(value) : 0;
      } else if (field.type === 'json') {
        try {
          data[field.name] = value ? JSON.parse(value) : null;
        } catch {
          data[field.name] = value || '';
        }
      } else {
        data[field.name] = value || '';
      }
    });

    setSaving(true);
    try {
      if (editDoc) {
        await apiPatch(`/api/admin/collections/${id}/documents/${editDoc.id}`, { data });
      } else {
        await apiPost(`/api/admin/collections/${id}/documents`, { data });
      }
      setShowDocModal(false);
      setEditDoc(null);
      form.reset();
      fetchData();
    } finally {
      setSaving(false);
    }
  };

  const openEditDoc = (doc) => {
    setEditDoc(doc);
    setShowDocModal(true);
  };

  const openNewDoc = () => {
    setEditDoc(null);
    setShowDocModal(true);
  };

  const deleteDocument = async (docId) => {
    if (!confirm('Delete this document?')) return;
    await apiDelete(`/api/admin/collections/${id}/documents/${docId}`);
    fetchData();
  };

  const deleteCollection = async () => {
    if (!confirm('Delete this collection and all its documents?')) return;
    await apiDelete(`/api/admin/collections/${id}`);
    navigate('/collections');
  };

  const handleImportSubmit = async (e) => {
    e.preventDefault();
    const form = e.target;
    const formData = new FormData(form);

    setImporting(true);
    setImportResult(null);
    try {
      const res = await fetch(`/admin/collections/${id}/import`, {
        method: 'POST',
        body: formData,
      });
      const text = await res.text();
      if (res.ok) {
        // Parse the success message from the HTML response
        const match = text.match(/(\d+) inserted, (\d+) updated, (\d+) skipped/);
        if (match) {
          setImportResult({ success: true, message: `${match[1]} inserted, ${match[2]} updated, ${match[3]} skipped` });
        } else {
          setImportResult({ success: true, message: 'Import completed successfully' });
        }
        fetchData();
      } else {
        setImportResult({ success: false, message: 'Import failed. Check your file format.' });
      }
    } catch (err) {
      setImportResult({ success: false, message: err.message });
    } finally {
      setImporting(false);
    }
  };

  const addField = () => {
    setEditFields([...editFields, { name: '', type: 'string', required: false }]);
  };

  const updateField = (index, key, value) => {
    const newFields = [...editFields];
    newFields[index] = { ...newFields[index], [key]: value };
    setEditFields(newFields);
  };

  const removeField = (index) => {
    setEditFields(editFields.filter((_, i) => i !== index));
  };

  const formatValue = (doc, field) => {
    const val = doc.data?.[field.name];
    if (field.type === 'bool') {
      return val ? (
        <span className="badge badge-success badge-xs">Yes</span>
      ) : (
        <span className="badge badge-ghost badge-xs">No</span>
      );
    }
    if (field.type === 'date') {
      if (val) {
        return new Date(val).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
      }
      return '';
    }
    if (field.type === 'json') {
      return typeof val === 'object' ? JSON.stringify(val).substring(0, 50) + '...' : String(val || '');
    }
    return val || '';
  };

  if (loading || !collection) {
    return (
      <div className="flex flex-col h-full">
        <div className="flex items-center justify-between px-4 py-3 border-b border-base-300 bg-base-200/50">
          <div className="skeleton h-6 w-48" />
        </div>
        <div className="flex-1 flex justify-center items-center">
          <span className="loading loading-spinner loading-lg" />
        </div>
      </div>
    );
  }

  return (
    <div className="flex flex-col h-full">
      {/* Header */}
      <div className="flex items-center justify-between px-4 py-3 border-b border-base-300 bg-base-200/50">
        <div>
          <h2 className="font-semibold">{collection.name}</h2>
          <p className="text-xs text-base-content/50">Collection documents{collection.system && ' · System'}</p>
        </div>
        <div className="flex items-center gap-2">
          {!collection.system && (
            <button onClick={() => { setEditFields(fields); setShowEditModal(true); }} className="btn btn-ghost btn-sm gap-1">
              <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
              </svg>
              Schema
            </button>
          )}

          {/* Import/Export Dropdown */}
          <div className="dropdown dropdown-end">
            <label tabIndex={0} className="btn btn-ghost btn-sm gap-1">
              <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
              </svg>
              Import/Export
              <svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
              </svg>
            </label>
            <ul tabIndex={0} className="dropdown-content menu bg-base-200 rounded-box z-50 w-56 p-2 shadow-xl border border-base-300">
              <li className="menu-title text-xs text-base-content/50 px-2 pt-1 pb-2">Export</li>
              <li><a href={`/admin/collections/${id}/export?format=json`} target="_blank" className="gap-2">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
                </svg>
                Export as JSON
              </a></li>
              <li><a href={`/admin/collections/${id}/export?format=csv`} target="_blank" className="gap-2">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
                </svg>
                Export as CSV
              </a></li>
              <li><a href={`/admin/collections/${id}/backup`} target="_blank" className="gap-2">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4" />
                </svg>
                Full Backup (with schema)
              </a></li>
              <li className="menu-title text-xs text-base-content/50 px-2 pt-3 pb-2">Import</li>
              <li><a onClick={() => setShowImportModal(true)} className="gap-2 cursor-pointer">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
                </svg>
                Import Data
              </a></li>
            </ul>
          </div>

          <button onClick={openNewDoc} className="btn btn-ghost btn-sm gap-1">
            <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4v16m8-8H4" />
            </svg>
            Add Document
          </button>

          {toggleChat && (
            <button onClick={toggleChat} className="btn btn-soft btn-primary btn-sm gap-2">
              <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
              </svg>
              AI Assistant
            </button>
          )}
        </div>
      </div>

      {collection.description && (
        <div className="px-4 py-2 text-sm text-base-content/70 border-b border-base-300">
          {collection.description}
        </div>
      )}

      {/* Data Table */}
      <div className="flex-1 overflow-auto">
        {documents.length ? (
          <table className="table table-pin-rows table-sm">
            <thead>
              <tr className="bg-base-200">
                {fields.slice(0, 5).map((field) => (
                  <th key={field.name} className="font-medium text-base-content/70">
                    {field.name}
                    {field.required && <span className="text-error ml-1">*</span>}
                  </th>
                ))}
                <th className="w-24"></th>
              </tr>
            </thead>
            <tbody>
              {documents.map((doc) => (
                <tr key={doc.id} className="hover:bg-base-200/50 cursor-pointer" onClick={() => openEditDoc(doc)}>
                  {fields.slice(0, 5).map((field) => (
                    <td key={field.name} className="max-w-xs truncate">
                      {formatValue(doc, field)}
                    </td>
                  ))}
                  <td>
                    <div className="flex gap-1 justify-end">
                      <button
                        onClick={(e) => { e.stopPropagation(); deleteDocument(doc.id); }}
                        className="btn btn-ghost btn-xs text-error"
                      >
                        <svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
                        </svg>
                      </button>
                    </div>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        ) : (
          <div className="flex flex-col items-center justify-center h-full text-center p-8">
            <div className="text-6xl mb-4 opacity-20">
              <svg xmlns="http://www.w3.org/2000/svg" className="h-16 w-16 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
              </svg>
            </div>
            <h3 className="text-lg font-medium mb-2">No documents yet</h3>
            <p className="text-base-content/60 mb-4">Add your first document to this collection</p>
            <button onClick={openNewDoc} className="btn btn-primary btn-sm">
              Add Document
            </button>
          </div>
        )}
      </div>

      {/* Edit Collection Modal */}
      {showEditModal && (
        <dialog className="modal modal-open">
          <div className="modal-box w-11/12 max-w-3xl max-h-[90vh]">
            <button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onClick={() => setShowEditModal(false)}>✕</button>
            <h3 className="font-bold text-lg mb-4">Edit Collection</h3>

            <form onSubmit={handleEditSubmit} className="space-y-4">
              <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                <div className="form-control">
                  <label className="floating-label">
                    <span>Name</span>
                    <input type="text" name="name" placeholder="Blog Posts" className="input input-bordered w-full" defaultValue={collection.name} required autoFocus />
                  </label>
                </div>

                <div className="form-control">
                  <label className="floating-label">
                    <span>Description</span>
                    <input type="text" name="description" placeholder="A collection of blog posts" className="input input-bordered w-full" defaultValue={collection.description || ''} />
                  </label>
                </div>
              </div>

              <div className="form-control">
                <label className="label">
                  <span className="label-text text-sm font-medium">Schema Fields</span>
                </label>
                <div className="space-y-2">
                  {editFields.map((field, index) => (
                    <div key={index} className="schema-field flex items-center gap-2 bg-base-200/50 rounded-lg p-2">
                      <input
                        type="text"
                        className="input input-bordered input-sm flex-1"
                        placeholder="Field name"
                        value={field.name}
                        onChange={(e) => updateField(index, 'name', e.target.value)}
                        required
                      />
                      <select
                        className="select select-bordered select-sm w-32"
                        value={field.type}
                        onChange={(e) => updateField(index, 'type', e.target.value)}
                      >
                        {FIELD_TYPES.map((type) => (
                          <option key={type} value={type}>{type}</option>
                        ))}
                      </select>
                      <label className="flex items-center gap-1 cursor-pointer">
                        <input
                          type="checkbox"
                          className="checkbox checkbox-xs"
                          checked={field.required || false}
                          onChange={(e) => updateField(index, 'required', e.target.checked)}
                        />
                        <span className="text-xs">Req</span>
                      </label>
                      <button type="button" onClick={() => removeField(index)} className="btn btn-ghost btn-xs btn-square text-error">
                        <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
                        </svg>
                      </button>
                    </div>
                  ))}
                </div>
                <button type="button" onClick={addField} className="btn btn-ghost btn-sm btn-block mt-2 gap-1 text-primary">
                  <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4v16m8-8H4" />
                  </svg>
                  Add Field
                </button>
              </div>

              <div className="flex justify-end gap-2 pt-4">
                <button type="button" onClick={deleteCollection} className="btn btn-ghost btn-sm text-error mr-auto">Delete</button>
                <button type="button" className="btn btn-ghost btn-sm" onClick={() => setShowEditModal(false)}>Cancel</button>
                <button type="submit" className="btn btn-primary btn-sm" disabled={saving}>
                  {saving ? <span className="loading loading-spinner loading-sm" /> : 'Save Changes'}
                </button>
              </div>
            </form>
          </div>
          <div className="modal-backdrop" onClick={() => setShowEditModal(false)} />
        </dialog>
      )}

      {/* New/Edit Document Modal */}
      {showDocModal && (
        <dialog className="modal modal-open">
          <div className="modal-box w-11/12 max-w-2xl max-h-[90vh]">
            <button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onClick={() => { setShowDocModal(false); setEditDoc(null); }}>✕</button>
            <h3 className="font-bold text-lg mb-4">{editDoc ? 'Edit Document' : 'New Document'}</h3>

            <form onSubmit={handleDocSubmit} className="space-y-4">
              <div className="grid grid-cols-1 gap-4">
                {fields.length ? fields.map((field) => (
                  <div key={field.name} className="form-control">
                    {field.type === 'bool' ? (
                      <label className="label cursor-pointer justify-start gap-4 py-0">
                        <input
                          type="checkbox"
                          name={field.name}
                          className="checkbox checkbox-primary checkbox-sm"
                          defaultChecked={editDoc?.data?.[field.name] || false}
                        />
                        <span className="text-sm">{field.name} {field.required && <span className="text-error">*</span>}</span>
                      </label>
                    ) : field.type === 'text' ? (
                      <label className="floating-label">
                        <span>{field.name} {field.required && <span className="text-error">*</span>}</span>
                        <textarea
                          name={field.name}
                          placeholder={`Enter ${field.name}`}
                          className="textarea textarea-bordered w-full bg-base-100"
                          rows="3"
                          defaultValue={editDoc?.data?.[field.name] || ''}
                          required={field.required}
                        />
                      </label>
                    ) : field.type === 'number' ? (
                      <label className="floating-label">
                        <span>{field.name} {field.required && <span className="text-error">*</span>}</span>
                        <input
                          type="number"
                          name={field.name}
                          placeholder="0"
                          step="any"
                          className="input input-bordered w-full"
                          defaultValue={editDoc?.data?.[field.name] || ''}
                          required={field.required}
                        />
                      </label>
                    ) : field.type === 'date' ? (
                      <label className="floating-label">
                        <span>{field.name} {field.required && <span className="text-error">*</span>}</span>
                        <input
                          type="date"
                          name={field.name}
                          className="input input-bordered w-full"
                          defaultValue={editDoc?.data?.[field.name]?.split('T')[0] || ''}
                          required={field.required}
                        />
                      </label>
                    ) : field.type === 'email' ? (
                      <label className="floating-label">
                        <span>{field.name} {field.required && <span className="text-error">*</span>}</span>
                        <input
                          type="email"
                          name={field.name}
                          placeholder="email@example.com"
                          className="input input-bordered w-full"
                          defaultValue={editDoc?.data?.[field.name] || ''}
                          required={field.required}
                        />
                      </label>
                    ) : field.type === 'url' ? (
                      <label className="floating-label">
                        <span>{field.name} {field.required && <span className="text-error">*</span>}</span>
                        <input
                          type="url"
                          name={field.name}
                          placeholder="https://example.com"
                          className="input input-bordered w-full"
                          defaultValue={editDoc?.data?.[field.name] || ''}
                          required={field.required}
                        />
                      </label>
                    ) : field.type === 'json' ? (
                      <label className="floating-label">
                        <span>{field.name} {field.required && <span className="text-error">*</span>}</span>
                        <textarea
                          name={field.name}
                          placeholder='{"key": "value"}'
                          className="textarea textarea-bordered w-full font-mono text-sm bg-base-100"
                          rows="4"
                          defaultValue={editDoc?.data?.[field.name] ? JSON.stringify(editDoc.data[field.name], null, 2) : ''}
                          required={field.required}
                        />
                      </label>
                    ) : (
                      <label className="floating-label">
                        <span>{field.name} {field.required && <span className="text-error">*</span>}</span>
                        <input
                          type="text"
                          name={field.name}
                          placeholder={`Enter ${field.name}`}
                          className="input input-bordered w-full"
                          defaultValue={editDoc?.data?.[field.name] || ''}
                          required={field.required}
                        />
                      </label>
                    )}
                  </div>
                )) : (
                  <div className="text-center py-8 text-base-content/50">
                    <p className="mb-2">No fields defined in schema.</p>
                    <button
                      type="button"
                      className="btn btn-ghost btn-sm gap-1"
                      onClick={() => { setShowDocModal(false); setEditDoc(null); setEditFields(fields); setShowEditModal(true); }}
                    >
                      Edit collection schema
                    </button>
                  </div>
                )}
              </div>

              <div className="flex justify-end gap-2 pt-4">
                <button type="button" className="btn btn-ghost btn-sm" onClick={() => { setShowDocModal(false); setEditDoc(null); }}>Cancel</button>
                <button type="submit" className="btn btn-primary btn-sm" disabled={saving || !fields.length}>
                  {saving ? <span className="loading loading-spinner loading-sm" /> : (editDoc ? 'Save Changes' : 'Create Document')}
                </button>
              </div>
            </form>
          </div>
          <div className="modal-backdrop" onClick={() => { setShowDocModal(false); setEditDoc(null); }} />
        </dialog>
      )}

      {/* Import Modal */}
      {showImportModal && (
        <dialog className="modal modal-open">
          <div className="modal-box w-11/12 max-w-lg">
            <button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onClick={() => { setShowImportModal(false); setImportResult(null); }}>✕</button>
            <h3 className="font-bold text-lg mb-4">Import Data</h3>

            <form onSubmit={handleImportSubmit} encType="multipart/form-data" className="space-y-4">
              <div className="form-control">
                <label className="label">
                  <span className="label-text">Select File</span>
                  <span className="label-text-alt text-base-content/50">JSON or CSV</span>
                </label>
                <input
                  type="file"
                  name="file"
                  accept=".json,.csv"
                  className="file-input file-input-bordered w-full"
                  required
                />
              </div>

              <div className="form-control">
                <label className="label">
                  <span className="label-text">Import Mode</span>
                </label>
                <select name="mode" className="select select-bordered w-full">
                  <option value="insert">Insert Only - Skip existing IDs</option>
                  <option value="upsert">Upsert - Update existing, insert new</option>
                  <option value="replace">Replace All - Delete all then import</option>
                </select>
                <label className="label">
                  <span className="label-text-alt text-base-content/50">How to handle documents that already exist</span>
                </label>
              </div>

              <div className="form-control">
                <label className="label cursor-pointer justify-start gap-4">
                  <input type="checkbox" name="skip_invalid" className="checkbox checkbox-sm" defaultChecked />
                  <div>
                    <span className="label-text">Skip invalid records</span>
                    <p className="text-xs text-base-content/50">Continue import even if some records fail validation</p>
                  </div>
                </label>
              </div>

              {importResult && (
                <div className={`alert ${importResult.success ? 'alert-success' : 'alert-error'}`}>
                  <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d={importResult.success ? "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" : "M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"} />
                  </svg>
                  <span>{importResult.message}</span>
                </div>
              )}

              <div className="flex justify-end gap-2 pt-4">
                <button type="button" className="btn btn-ghost btn-sm" onClick={() => { setShowImportModal(false); setImportResult(null); }}>Cancel</button>
                <button type="submit" className="btn btn-primary btn-sm" disabled={importing}>
                  {importing ? <span className="loading loading-spinner loading-sm" /> : 'Import'}
                </button>
              </div>
            </form>
          </div>
          <div className="modal-backdrop" onClick={() => { setShowImportModal(false); setImportResult(null); }} />
        </dialog>
      )}
    </div>
  );
}
← Back