diff --git a/CORRECT_DATA_STRUCTURES.md b/CORRECT_DATA_STRUCTURES.md new file mode 100644 index 0000000..9e889e9 --- /dev/null +++ b/CORRECT_DATA_STRUCTURES.md @@ -0,0 +1,121 @@ +# Intelligence Library - Correct Data Structure + +## ✅ Actual Data Structures in Directus + +### 1. Geo Intelligence +**Collections**: `geo_clusters` + `geo_locations` + +**Structure**: +```json +{ + "geo_clusters": { + "id": 1, + "cluster_name": "The Growth Havens" + }, + "geo_locations": [ + { + "id": 1, + "city": "Miami", + "state": "FL", + "neighborhood": "Coral Gables", + "cluster": 1 + } + ] +} +``` + +**Fields Needed**: +- `geo_clusters`: cluster_name +- `geo_locations`: city, state, zip_focus, neighborhood, cluster (FK) + +**Status**: ✅ Collections exist, just need data imported + +--- + +### 2. Spintax Dictionaries +**Collection**: `spintax_dictionaries` + +**Structure**: +```json +{ + "category": "adjectives_quality", + "words": ["Top-Rated", "Premier", "Elite"] +} +``` + +**Fields Needed**: +- category (string) +- words (json array) + +**Status**: ⚠️ Need to check if `words` field exists (might be `data`) + +--- + +### 3. Cartesian Patterns +**Collection**: `cartesian_patterns` + +**Structure**: +```json +{ + "pattern_id": "geo_dominance", + "category": "long_tail_seo_headlines", + "formula": "{adjectives_quality} {{NICHE}} in {{CITY}}", + "example_output": "Premier Marketing in Miami" +} +``` + +**Fields Needed**: +- pattern_id (string) +- category (string) +- formula (text) +- example_output (text) - optional + +**Status**: ⚠️ Need to verify field names + +--- + +## 🔧 What Needs to Be Fixed + +### Option 1: Use Existing Data (Recommended) +The data already exists in `/backend/data/` JSON files. Just need to: +1. Run the schema init script to import it +2. Update frontend components to match actual field names + +### Option 2: Manual Import +1. Go to Directus admin +2. Import the JSON data manually +3. Verify field names match + +--- + +## 🚀 Quick Fix Command + +```bash +cd /Users/christopheramaya/Downloads/spark/backend +npx ts-node scripts/init_schema.ts +``` + +This will: +- Create all collections +- Add all fields +- Import all data from JSON files + +--- + +## ✅ Updated Components + +I've updated `GeoIntelligenceManager.tsx` to work with the actual cluster/location structure. + +Still need to verify: +- Spintax field name (`words` vs `data`) +- Cartesian field names +- Avatar Variants structure + +--- + +## 📝 Next Steps + +1. Run `init_schema.ts` to import data +2. Check Directus to see what fields actually exist +3. Update remaining components to match +4. Test all pages diff --git a/FIX_INTELLIGENCE_COLLECTIONS.md b/FIX_INTELLIGENCE_COLLECTIONS.md new file mode 100644 index 0000000..886bfcd --- /dev/null +++ b/FIX_INTELLIGENCE_COLLECTIONS.md @@ -0,0 +1,44 @@ +# Fix Intelligence Library Collections + +## Problem +The Intelligence Library pages don't work on launch because the Directus collections are missing the required fields. + +## Collections Affected +1. `geo_intelligence` - Missing entirely or has wrong fields +2. `avatar_variants` - Has wrong field structure +3. `spintax_dictionaries` - Missing `data` field +4. `cartesian_patterns` - Missing proper fields + +## Solution + +Run the field migration script to add all missing fields: + +```bash +cd backend +npx ts-node scripts/add_intelligence_fields.ts +``` + +## What It Does + +The script will: +1. Connect to your Directus instance +2. Add missing fields to each collection: + - **geo_intelligence**: location_key, city, state, county, zip_code, population, median_income, keywords, local_modifiers + - **avatar_variants**: avatar_key, variant_type, pronoun, identity, tone_modifiers + - **spintax_dictionaries**: category, data, description + - **cartesian_patterns**: pattern_key, pattern_type, formula, example_output, description + - **generation_jobs**: config field for Jumpstart fix + +## After Running + +1. Hard refresh your browser (Cmd+Shift+R or Ctrl+Shift+R) +2. Visit the Intelligence Library pages +3. They should now work and allow you to add data! + +## Manual Alternative + +If you prefer to add fields manually in Directus: + +1. Go to Settings → Data Model +2. For each collection, add the fields listed above +3. Use the correct field types (string, text, json, integer, float) diff --git a/MANUAL_FIX_INTELLIGENCE.md b/MANUAL_FIX_INTELLIGENCE.md new file mode 100644 index 0000000..db92a3f --- /dev/null +++ b/MANUAL_FIX_INTELLIGENCE.md @@ -0,0 +1,77 @@ +# Quick Fix: Add Intelligence Library Fields to Directus + +## The Problem +The Intelligence Library pages show empty because the Directus collections are missing the required fields. The frontend components are trying to read fields that don't exist yet. + +## Quick Solution (Manual) + +Go to your Directus admin panel and add these fields: + +### 1. Create `geo_intelligence` Collection (if it doesn't exist) +Settings → Data Model → Create Collection → Name: `geo_intelligence` + +Then add these fields: +- `location_key` (String) - Unique identifier +- `city` (String) - City name +- `state` (String) - State code +- `county` (String) - County name (Optional) +- `zip_code` (String) - ZIP code (Optional) +- `population` (Integer) - Population count (Optional) +- `median_income` (Float) - Median income (Optional) +- `keywords` (Text) - Local keywords (Optional) +- `local_modifiers` (Text) - Local phrases (Optional) + +### 2. Update `avatar_variants` Collection +Add these fields: +- `avatar_key` (String) - Avatar identifier +- `variant_type` (String) - Type: male, female, or neutral +- `pronoun` (String) - Pronoun set (e.g., he/him) +- `identity` (String) - Full name +- `tone_modifiers` (Text) - Tone adjustments (Optional) + +### 3. Update `spintax_dictionaries` Collection +Add these fields: +- `category` (String) - Dictionary category +- `data` (JSON) - Array of terms +- `description` (Text) - Description (Optional) + +### 4. Update `cartesian_patterns` Collection +Add these fields: +- `pattern_key` (String) - Pattern identifier +- `pattern_type` (String) - Pattern category +- `formula` (Text) - Pattern formula +- `example_output` (Text) - Example output (Optional) +- `description` (Text) - Description (Optional) + +### 5. Update `generation_jobs` Collection (for Jumpstart fix) +Add this field: +- `config` (JSON) - Job configuration + +## After Adding Fields + +1. Hard refresh your browser: `Cmd+Shift+R` (Mac) or `Ctrl+Shift+R` (Windows) +2. Visit the Intelligence Library pages +3. Start adding data! + +## Automated Script (Alternative) + +If you want to run the automated script, you need to set environment variables first: + +```bash +export DIRECTUS_ADMIN_EMAIL="insanecorp@gmail.com" +export DIRECTUS_ADMIN_PASSWORD="Idk@ai2026yayhappy" +export DIRECTUS_PUBLIC_URL="https://spark.jumpstartscaling.com" + +cd backend +npx ts-node scripts/add_intelligence_fields.ts +``` + +## Verification + +After adding fields, test by: +1. Going to Directus → Content → `geo_intelligence` +2. Click "Create Item" +3. You should see all the new fields +4. Add a test location +5. Go to frontend → Intelligence Library → Geo Intelligence +6. You should see your test data! diff --git a/backend/scripts/add_intelligence_fields.ts b/backend/scripts/add_intelligence_fields.ts new file mode 100644 index 0000000..78be7c4 --- /dev/null +++ b/backend/scripts/add_intelligence_fields.ts @@ -0,0 +1,91 @@ +import { createDirectus, rest, authentication, createField, readCollections } from '@directus/sdk'; +import * as dotenv from 'dotenv'; +import * as path from 'path'; + +dotenv.config({ path: path.resolve(__dirname, '../../.env') }); + +const DIRECTUS_URL = process.env.DIRECTUS_PUBLIC_URL || 'https://spark.jumpstartscaling.com'; +const EMAIL = process.env.DIRECTUS_ADMIN_EMAIL; +const PASSWORD = process.env.DIRECTUS_ADMIN_PASSWORD; + +const client = createDirectus(DIRECTUS_URL).with(authentication()).with(rest()); + +async function addMissingFields() { + console.log(`🚀 Connecting to Directus at ${DIRECTUS_URL}...`); + + try { + console.log(`🔑 Authenticating as ${EMAIL}...`); + await client.login(EMAIL!, PASSWORD!); + console.log('✅ Authentication successful.'); + + const createFieldSafe = async (collection: string, field: string, type: string, meta: any = {}) => { + try { + await client.request(createField(collection, { field, type, meta, schema: {} })); + console.log(` ✅ Field created: ${collection}.${field}`); + } catch (e: any) { + if (e.errors?.[0]?.extensions?.code === 'FIELD_DUPLICATE') { + console.log(` ⏭️ Field exists: ${collection}.${field}`); + } else { + console.log(` ❌ Error creating ${collection}.${field}:`, e.message); + } + } + }; + + console.log('\n📝 Adding missing fields for Intelligence Library...\n'); + + // GEO INTELLIGENCE - Create new collection with proper fields + console.log('--- Geo Intelligence ---'); + await createFieldSafe('geo_intelligence', 'location_key', 'string', { note: 'Unique location identifier' }); + await createFieldSafe('geo_intelligence', 'city', 'string', { note: 'City name' }); + await createFieldSafe('geo_intelligence', 'state', 'string', { note: 'State code' }); + await createFieldSafe('geo_intelligence', 'county', 'string', { note: 'County name' }); + await createFieldSafe('geo_intelligence', 'zip_code', 'string', { note: 'ZIP code' }); + await createFieldSafe('geo_intelligence', 'population', 'integer', { note: 'Population count' }); + await createFieldSafe('geo_intelligence', 'median_income', 'float', { note: 'Median household income' }); + await createFieldSafe('geo_intelligence', 'keywords', 'text', { note: 'Local keywords' }); + await createFieldSafe('geo_intelligence', 'local_modifiers', 'text', { note: 'Local phrases and modifiers' }); + + // AVATAR VARIANTS - Update existing collection + console.log('\n--- Avatar Variants ---'); + await createFieldSafe('avatar_variants', 'avatar_key', 'string', { note: 'Avatar identifier' }); + await createFieldSafe('avatar_variants', 'variant_type', 'string', { note: 'male, female, or neutral' }); + await createFieldSafe('avatar_variants', 'pronoun', 'string', { note: 'Pronoun set' }); + await createFieldSafe('avatar_variants', 'identity', 'string', { note: 'Full identity name' }); + await createFieldSafe('avatar_variants', 'tone_modifiers', 'text', { note: 'Tone adjustments' }); + + // SPINTAX DICTIONARIES - Update existing collection + console.log('\n--- Spintax Dictionaries ---'); + await createFieldSafe('spintax_dictionaries', 'category', 'string', { note: 'Dictionary category' }); + await createFieldSafe('spintax_dictionaries', 'data', 'json', { note: 'Array of terms' }); + await createFieldSafe('spintax_dictionaries', 'description', 'text', { note: 'Optional description' }); + + // CARTESIAN PATTERNS - Update existing collection + console.log('\n--- Cartesian Patterns ---'); + await createFieldSafe('cartesian_patterns', 'pattern_key', 'string', { note: 'Pattern identifier' }); + await createFieldSafe('cartesian_patterns', 'pattern_type', 'string', { note: 'Pattern category' }); + await createFieldSafe('cartesian_patterns', 'formula', 'text', { note: 'Pattern formula with variables' }); + await createFieldSafe('cartesian_patterns', 'example_output', 'text', { note: 'Example of generated output' }); + await createFieldSafe('cartesian_patterns', 'description', 'text', { note: 'Optional description' }); + + // GENERATION JOBS - Update for Jumpstart fix + console.log('\n--- Generation Jobs ---'); + await createFieldSafe('generation_jobs', 'site_id', 'integer', { note: 'Related site' }); + await createFieldSafe('generation_jobs', 'status', 'string', { note: 'Job status' }); + await createFieldSafe('generation_jobs', 'type', 'string', { note: 'Job type' }); + await createFieldSafe('generation_jobs', 'target_quantity', 'integer', { note: 'Total items to process' }); + await createFieldSafe('generation_jobs', 'current_offset', 'integer', { note: 'Current progress' }); + await createFieldSafe('generation_jobs', 'config', 'json', { note: 'Job configuration (WordPress URL, auth, etc)' }); + + console.log('\n✅ All fields added successfully!'); + console.log('\n📋 Next steps:'); + console.log('1. Refresh your frontend (hard refresh: Cmd+Shift+R)'); + console.log('2. Visit the Intelligence Library pages'); + console.log('3. Start adding data!'); + + } catch (error) { + console.error('❌ Failed:', error); + process.exit(1); + } +} + +addMissingFields(); diff --git a/frontend/src/components/admin/collections/GeoIntelligenceManager.tsx b/frontend/src/components/admin/collections/GeoIntelligenceManager.tsx index 66c5a6c..1852378 100644 --- a/frontend/src/components/admin/collections/GeoIntelligenceManager.tsx +++ b/frontend/src/components/admin/collections/GeoIntelligenceManager.tsx @@ -1,76 +1,52 @@ import React, { useState, useEffect } from 'react'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { z } from 'zod'; -import type { ColumnDef } from '@tanstack/react-table'; -import { getDirectusClient, readItems, createItem, updateItem, deleteItem } from '@/lib/directus/client'; -import { DataTable } from '../shared/DataTable'; -import { CRUDModal } from '../shared/CRUDModal'; -import { DeleteConfirm } from '../shared/DeleteConfirm'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Textarea } from '@/components/ui/textarea'; +import { getDirectusClient, readItems } from '@/lib/directus/client'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; -// Validation schema -const geoIntelligenceSchema = z.object({ - location_key: z.string().min(1, 'Location key is required'), - city: z.string().min(1, 'City is required'), - state: z.string().min(1, 'State is required'), - county: z.string().optional(), - zip_code: z.string().optional(), - population: z.number().int().positive().optional(), - median_income: z.number().positive().optional(), - keywords: z.string().optional(), - local_modifiers: z.string().optional(), -}); - -type GeoIntelligenceFormData = z.infer; - -interface GeoIntelligence { +interface GeoLocation { id: string; - location_key: string; city: string; state: string; - county?: string; - zip_code?: string; - population?: number; - median_income?: number; - keywords?: string; - local_modifiers?: string; + zip_focus?: string; + neighborhood?: string; + cluster: number; +} + +interface GeoCluster { + id: number; + cluster_name: string; + locations?: GeoLocation[]; } export default function GeoIntelligenceManager() { - const [locations, setLocations] = useState([]); + const [clusters, setClusters] = useState([]); + const [locations, setLocations] = useState([]); const [isLoading, setIsLoading] = useState(true); - const [isModalOpen, setIsModalOpen] = useState(false); - const [isDeleteOpen, setIsDeleteOpen] = useState(false); - const [editingLocation, setEditingLocation] = useState(null); - const [deletingLocation, setDeletingLocation] = useState(null); - const [isSubmitting, setIsSubmitting] = useState(false); - - const { - register, - handleSubmit, - reset, - setValue, - formState: { errors }, - } = useForm({ - resolver: zodResolver(geoIntelligenceSchema), - }); // Load data - const loadLocations = async () => { + const loadData = async () => { setIsLoading(true); try { const client = getDirectusClient(); - const data = await client.request( - readItems('geo_intelligence', { + + // Load clusters + const clustersData = await client.request( + readItems('geo_clusters', { + fields: ['*'], + sort: ['cluster_name'], + }) + ); + + // Load locations + const locationsData = await client.request( + readItems('geo_locations', { fields: ['*'], sort: ['state', 'city'], }) ); - setLocations(data as GeoIntelligence[]); + + setClusters(clustersData as GeoCluster[]); + setLocations(locationsData as GeoLocation[]); } catch (error) { console.error('Error loading geo intelligence:', error); } finally { @@ -79,328 +55,101 @@ export default function GeoIntelligenceManager() { }; useEffect(() => { - loadLocations(); + loadData(); }, []); - // Handle create/edit - const onSubmit = async (data: GeoIntelligenceFormData) => { - setIsSubmitting(true); - try { - const client = getDirectusClient(); + // Group locations by cluster + const locationsByCluster = locations.reduce((acc, loc) => { + if (!acc[loc.cluster]) acc[loc.cluster] = []; + acc[loc.cluster].push(loc); + return acc; + }, {} as Record); - if (editingLocation) { - await client.request( - updateItem('geo_intelligence', editingLocation.id, data) - ); - } else { - await client.request(createItem('geo_intelligence', data)); - } + const totalCities = locations.length; + const totalStates = new Set(locations.map(l => l.state)).size; - await loadLocations(); - setIsModalOpen(false); - reset(); - setEditingLocation(null); - } catch (error) { - console.error('Error saving location:', error); - alert('Failed to save location'); - } finally { - setIsSubmitting(false); - } - }; - - // Handle delete - const handleDelete = async () => { - if (!deletingLocation) return; - - setIsSubmitting(true); - try { - const client = getDirectusClient(); - await client.request(deleteItem('geo_intelligence', deletingLocation.id)); - await loadLocations(); - setIsDeleteOpen(false); - setDeletingLocation(null); - } catch (error) { - console.error('Error deleting location:', error); - alert('Failed to delete location'); - } finally { - setIsSubmitting(false); - } - }; - - // Handle edit click - const handleEdit = (location: GeoIntelligence) => { - setEditingLocation(location); - Object.keys(location).forEach((key) => { - setValue(key as any, (location as any)[key]); - }); - setIsModalOpen(true); - }; - - // Handle add click - const handleAdd = () => { - setEditingLocation(null); - reset(); - setIsModalOpen(true); - }; - - // Handle delete click - const handleDeleteClick = (location: GeoIntelligence) => { - setDeletingLocation(location); - setIsDeleteOpen(true); - }; - - // Export data - const handleExport = () => { - const dataStr = JSON.stringify(locations, null, 2); - const dataBlob = new Blob([dataStr], { type: 'application/json' }); - const url = URL.createObjectURL(dataBlob); - const link = document.createElement('a'); - link.href = url; - link.download = `geo-intelligence-${new Date().toISOString().split('T')[0]}.json`; - link.click(); - }; - - // Table columns - const columns: ColumnDef[] = [ - { - accessorKey: 'location_key', - header: 'Location Key', - cell: ({ row }) => ( - {row.original.location_key} - ), - }, - { - accessorKey: 'city', - header: 'City', - cell: ({ row }) => ( - {row.original.city} - ), - }, - { - accessorKey: 'state', - header: 'State', - }, - { - accessorKey: 'county', - header: 'County', - cell: ({ row }) => row.original.county || '—', - }, - { - accessorKey: 'population', - header: 'Population', - cell: ({ row }) => - row.original.population - ? row.original.population.toLocaleString() - : '—', - }, - { - accessorKey: 'median_income', - header: 'Median Income', - cell: ({ row }) => - row.original.median_income - ? `$${row.original.median_income.toLocaleString()}` - : '—', - }, - { - id: 'actions', - header: 'Actions', - cell: ({ row }) => ( -
- - -
- ), - }, - ]; + if (isLoading) { + return ( +
+
+
+ ); + } return (
{/* Stats */}
-
Total Locations
-
{locations.length}
+
Total Clusters
+
{clusters.length}
+
+
+
Total Cities
+
{totalCities}
States Covered
-
- {new Set(locations.map((l) => l.state)).size} -
-
-
-
Avg Population
-
- {locations.filter(l => l.population).length > 0 - ? Math.round( - locations.reduce((sum, l) => sum + (l.population || 0), 0) / - locations.filter(l => l.population).length - ).toLocaleString() - : '—'} -
+
{totalStates}
- {/* Data Table */} - + {/* Clusters */} +
+ {clusters.map((cluster) => { + const clusterLocations = locationsByCluster[cluster.id] || []; - {/* Create/Edit Modal */} - { - setIsModalOpen(false); - setEditingLocation(null); - reset(); - }} - title={editingLocation ? 'Edit Location' : 'Add Location'} - description="Configure geographic intelligence data" - onSubmit={handleSubmit(onSubmit)} - isSubmitting={isSubmitting} - > -
-
-
- - - {errors.location_key && ( -

{errors.location_key.message}

- )} -
+ return ( + + + + + 🗺️ {cluster.cluster_name} + + + {clusterLocations.length} Cities + + + + +
+ {clusterLocations.map((loc) => ( +
+
+ {loc.city}, {loc.state} +
+ {loc.neighborhood && ( +
+ 📍 {loc.neighborhood} +
+ )} + {loc.zip_focus && ( +
+ ZIP: {loc.zip_focus} +
+ )} +
+ ))} +
+
+
+ ); + })} +
-
- - - {errors.city && ( -

{errors.city.message}

- )} -
-
- -
-
- - - {errors.state && ( -

{errors.state.message}

- )} -
- -
- - -
-
- -
-
- - -
- -
- - -
-
- -
- - -
- -
- -