From bbe9a6b8111d42aee2e2509777feec668d084722 Mon Sep 17 00:00:00 2001 From: kelinfoxy <67766943+kelinfoxy@users.noreply.github.com> Date: Mon, 30 Mar 2026 00:01:51 -0400 Subject: [PATCH] Import mockup --- aistudio.google.project/.env.example | 9 + aistudio.google.project/.gitignore | 8 + .../README-FIRST.md | 0 aistudio.google.project/README.md | 20 + aistudio.google.project/index.html | 13 + aistudio.google.project/metadata.json | 5 + aistudio.google.project/package.json | 34 + aistudio.google.project/src/App.tsx | 25 + .../src/context/AppContext.tsx | 177 ++++ aistudio.google.project/src/data/centers.ts | 36 + aistudio.google.project/src/index.css | 1 + aistudio.google.project/src/main.tsx | 13 + .../src/services/gemini.ts | 97 ++ aistudio.google.project/src/types.ts | 63 ++ .../src/views/AdminView.tsx | 76 ++ .../src/views/CenterView.tsx | 832 ++++++++++++++++++ .../src/views/DonorView.tsx | 107 +++ .../src/views/LoginPortal.tsx | 197 +++++ .../src/views/RescueIntakeView.tsx | 470 ++++++++++ aistudio.google.project/tsconfig.json | 26 + aistudio.google.project/vite.config.ts | 24 + 21 files changed, 2233 insertions(+) create mode 100644 aistudio.google.project/.env.example create mode 100644 aistudio.google.project/.gitignore rename README-FIRST.md => aistudio.google.project/README-FIRST.md (100%) create mode 100644 aistudio.google.project/README.md create mode 100644 aistudio.google.project/index.html create mode 100644 aistudio.google.project/metadata.json create mode 100644 aistudio.google.project/package.json create mode 100644 aistudio.google.project/src/App.tsx create mode 100644 aistudio.google.project/src/context/AppContext.tsx create mode 100644 aistudio.google.project/src/data/centers.ts create mode 100644 aistudio.google.project/src/index.css create mode 100644 aistudio.google.project/src/main.tsx create mode 100644 aistudio.google.project/src/services/gemini.ts create mode 100644 aistudio.google.project/src/types.ts create mode 100644 aistudio.google.project/src/views/AdminView.tsx create mode 100644 aistudio.google.project/src/views/CenterView.tsx create mode 100644 aistudio.google.project/src/views/DonorView.tsx create mode 100644 aistudio.google.project/src/views/LoginPortal.tsx create mode 100644 aistudio.google.project/src/views/RescueIntakeView.tsx create mode 100644 aistudio.google.project/tsconfig.json create mode 100644 aistudio.google.project/vite.config.ts diff --git a/aistudio.google.project/.env.example b/aistudio.google.project/.env.example new file mode 100644 index 0000000..7a550fe --- /dev/null +++ b/aistudio.google.project/.env.example @@ -0,0 +1,9 @@ +# GEMINI_API_KEY: Required for Gemini AI API calls. +# AI Studio automatically injects this at runtime from user secrets. +# Users configure this via the Secrets panel in the AI Studio UI. +GEMINI_API_KEY="MY_GEMINI_API_KEY" + +# APP_URL: The URL where this applet is hosted. +# AI Studio automatically injects this at runtime with the Cloud Run service URL. +# Used for self-referential links, OAuth callbacks, and API endpoints. +APP_URL="MY_APP_URL" diff --git a/aistudio.google.project/.gitignore b/aistudio.google.project/.gitignore new file mode 100644 index 0000000..5a86d2a --- /dev/null +++ b/aistudio.google.project/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +build/ +dist/ +coverage/ +.DS_Store +*.log +.env* +!.env.example diff --git a/README-FIRST.md b/aistudio.google.project/README-FIRST.md similarity index 100% rename from README-FIRST.md rename to aistudio.google.project/README-FIRST.md diff --git a/aistudio.google.project/README.md b/aistudio.google.project/README.md new file mode 100644 index 0000000..5a8f844 --- /dev/null +++ b/aistudio.google.project/README.md @@ -0,0 +1,20 @@ +
+GHBanner +
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/d4aafa4b-ae59-48de-940c-56b6d37e5785 + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/aistudio.google.project/index.html b/aistudio.google.project/index.html new file mode 100644 index 0000000..21dfe69 --- /dev/null +++ b/aistudio.google.project/index.html @@ -0,0 +1,13 @@ + + + + + + My Google AI Studio App + + +
+ + + + diff --git a/aistudio.google.project/metadata.json b/aistudio.google.project/metadata.json new file mode 100644 index 0000000..1bd8d94 --- /dev/null +++ b/aistudio.google.project/metadata.json @@ -0,0 +1,5 @@ +{ + "name": "Rescue-to-Rehab Engine", + "description": "Matches high-needs rescue dogs with specialized rehabilitation centers using AI.", + "requestFramePermissions": [] +} diff --git a/aistudio.google.project/package.json b/aistudio.google.project/package.json new file mode 100644 index 0000000..3a21a27 --- /dev/null +++ b/aistudio.google.project/package.json @@ -0,0 +1,34 @@ +{ + "name": "react-example", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3000 --host=0.0.0.0", + "build": "vite build", + "preview": "vite preview", + "clean": "rm -rf dist", + "lint": "tsc --noEmit" + }, + "dependencies": { + "@google/genai": "^1.29.0", + "@tailwindcss/vite": "^4.1.14", + "@vitejs/plugin-react": "^5.0.4", + "lucide-react": "^0.546.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "vite": "^6.2.0", + "express": "^4.21.2", + "dotenv": "^17.2.3", + "motion": "^12.23.24" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "autoprefixer": "^10.4.21", + "tailwindcss": "^4.1.14", + "tsx": "^4.21.0", + "typescript": "~5.8.2", + "vite": "^6.2.0", + "@types/express": "^4.17.21" + } +} diff --git a/aistudio.google.project/src/App.tsx b/aistudio.google.project/src/App.tsx new file mode 100644 index 0000000..7501e3c --- /dev/null +++ b/aistudio.google.project/src/App.tsx @@ -0,0 +1,25 @@ +/** + * @license + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from 'react'; +import LoginPortal, { UserRole } from './views/LoginPortal'; +import RescueIntakeView from './views/RescueIntakeView'; +import DonorView from './views/DonorView'; +import CenterView from './views/CenterView'; +import AdminView from './views/AdminView'; + +export default function App() { + const [role, setRole] = useState(null); + + const handleLogout = () => setRole(null); + + if (!role) return ; + if (role === 'uploader') return ; + if (role === 'donor') return ; + if (role === 'center') return ; + if (role === 'admin') return ; + + return null; +} diff --git a/aistudio.google.project/src/context/AppContext.tsx b/aistudio.google.project/src/context/AppContext.tsx new file mode 100644 index 0000000..00fedea --- /dev/null +++ b/aistudio.google.project/src/context/AppContext.tsx @@ -0,0 +1,177 @@ +import React, { createContext, useContext, useState, ReactNode } from 'react'; +import { DogWithMatches, InboxItem, CenterProfile } from '../types'; +import { CENTERS } from '../data/centers'; + +interface AppContextType { + dogs: DogWithMatches[]; + inboxItems: InboxItem[]; + centers: CenterProfile[]; + addDog: (dog: DogWithMatches) => void; + updateDogPlacement: (dogId: string, updates: Partial) => void; + updateDog: (dogId: string, updates: Partial) => void; + updateInboxStatus: (itemId: string, status: InboxItem['status']) => void; + addInboxItems: (items: InboxItem[]) => void; +} + +const AppContext = createContext(undefined); + +// Initial Mock Data +const initialDogs: DogWithMatches[] = [ + { + id: 'DOG-B8N2X9Y', + name: 'Barnaby', + breed: 'Pitbull Mix', + age: '4 years', + vetNotes: 'Found as a stray with severe bilateral cruciate ligament tears. Requires TPLO surgery on both hind legs. Currently non-weight bearing. Very sweet temperament, no bite history, but highly stressed in the shelter environment. Needs strict crate rest and underwater treadmill therapy post-op.', + status: 'analyzed', + dateAdded: '2026-03-15', + documents: ['Intake_Exam.pdf', 'X-Rays_Hind_Legs.jpg', 'Behavioral_Eval.pdf'], + placementStatus: 'unplaced', + matches: [ + { + center_id: 'c1', + center_name: 'Paws in Motion Rehab', + decision: { + match_score: 95, + reasoning: 'This center is a perfect fit for bilateral TPLO recovery. The facility has the underwater treadmill and carts needed for his non-weight bearing status.', + status_options: { + accept_immediately: false, + conditional_needs: { + funding_required: 4500, + resources_required: ['Custom Sling', 'Orthopedic Bed'], + donor_pitch: 'Barnaby needs double knee surgery to walk again. Your donation secures his spot at a premier mobility rehab center.' + } + } + } + } + ] + }, + { + id: 'DOG-L4M9P2Q', + name: 'Luna', + breed: 'German Shepherd', + age: '2 years', + vetNotes: 'Hit by car. Pelvic fractures (healing), requires physical therapy to rebuild muscle mass in hindquarters. High energy, gets frustrated easily during confinement.', + status: 'analyzed', + dateAdded: '2026-03-16', + documents: ['Vet_Records.pdf'], + placementStatus: 'unplaced', + matches: [ + { + center_id: 'c1', + center_name: 'Paws in Motion Rehab', + decision: { + match_score: 82, + reasoning: 'Good match for orthopedic rehab capabilities, though her high energy might require extra behavioral management during confinement.', + status_options: { + accept_immediately: true, + conditional_needs: { + funding_required: 1200, + resources_required: ['Enrichment Toys'], + donor_pitch: 'Luna survived a car accident and needs physical therapy. Help fund her recovery journey.' + } + } + } + } + ] + }, + { + id: 'DOG-M7X1V5Z', + name: 'Max', + breed: 'Rottweiler', + age: '3 years', + vetNotes: 'Severe resource guarding with food and high-value toys. Has bitten a volunteer when a bowl was removed. Otherwise affectionate. Needs an experienced behavioral rehabilitation center with strict protocols.', + status: 'analyzed', + dateAdded: '2026-03-17', + documents: ['Bite_Report.pdf', 'Behavioral_Assessment.pdf'], + placementStatus: 'conditional', + placedCenterId: 'c2', + placedCenterName: 'Behavioral Crossroads', + fundingGoal: 2500, + fundingRaised: 450, + resourcesNeeded: ['Heavy Duty Muzzle', 'Secure Kenneling'], + donorPitch: 'Max is a misunderstood boy who needs intensive behavioral training to become adoptable. Your support gives him a second chance.', + centerDescription: 'We are prepared to take Max into our intensive behavioral modification program. Our staff is trained in severe resource guarding protocols.' + }, + { + id: 'DOG-B3K8J4W', + name: 'Bella', + breed: 'Golden Retriever', + age: '11 years', + vetNotes: 'Diagnosed with terminal osteosarcoma. Given 3-6 months. Needs palliative care, pain management, and a quiet hospice environment. Cannot handle stairs.', + status: 'analyzed', + dateAdded: '2026-03-18', + documents: ['Oncology_Report.pdf'], + placementStatus: 'conditional', + placedCenterId: 'c3', + placedCenterName: 'Serenity Senior Sanctuary', + fundingGoal: 1500, + fundingRaised: 1500, + resourcesNeeded: ['Pain Medication', 'Orthopedic Mattress'], + donorPitch: 'Bella deserves to spend her final months in comfort and peace. Help us provide palliative care for this sweet senior.', + centerDescription: 'We will provide Bella with a warm, loving hospice environment and manage her pain for the remainder of her days.' + } +]; + +const initialInbox: InboxItem[] = [ + { + id: 'i1', + dogId: 'DOG-B8N2X9Y', + centerId: 'c1', + matchScore: 95, + reasoning: 'This center is a perfect fit for bilateral TPLO recovery. The facility has the underwater treadmill and carts needed for his non-weight bearing status.', + status: 'unreviewed', + dateAdded: '2026-03-18', + decisionOptions: initialDogs[0].matches![0].decision + }, + { + id: 'i2', + dogId: 'DOG-L4M9P2Q', + centerId: 'c1', + matchScore: 82, + reasoning: 'Good match for orthopedic rehab capabilities, though her high energy might require extra behavioral management during confinement.', + status: 'saved_for_later', + dateAdded: '2026-03-17', + decisionOptions: initialDogs[1].matches![0].decision + } +]; + +export const AppProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [dogs, setDogs] = useState(initialDogs); + const [inboxItems, setInboxItems] = useState(initialInbox); + const [centers] = useState(CENTERS); + + const addDog = (dog: DogWithMatches) => { + setDogs(prev => [dog, ...prev]); + }; + + const updateDogPlacement = (dogId: string, updates: Partial) => { + setDogs(prev => prev.map(d => d.id === dogId ? { ...d, ...updates } : d)); + }; + + const updateDog = (dogId: string, updates: Partial) => { + setDogs(prev => prev.map(d => d.id === dogId ? { ...d, ...updates } : d)); + }; + + const updateInboxStatus = (itemId: string, status: InboxItem['status']) => { + setInboxItems(prev => prev.map(i => i.id === itemId ? { ...i, status } : i)); + }; + + const addInboxItems = (items: InboxItem[]) => { + setInboxItems(prev => [...items, ...prev]); + }; + + return ( + + {children} + + ); +}; + +export const useAppContext = () => { + const context = useContext(AppContext); + if (context === undefined) { + throw new Error('useAppContext must be used within an AppProvider'); + } + return context; +}; diff --git a/aistudio.google.project/src/data/centers.ts b/aistudio.google.project/src/data/centers.ts new file mode 100644 index 0000000..58e0fbc --- /dev/null +++ b/aistudio.google.project/src/data/centers.ts @@ -0,0 +1,36 @@ +import { CenterProfile } from '../types'; + +export const CENTERS: CenterProfile[] = [ + { + id: 'c1', + name: 'Paws in Motion Rehab', + specialties: ['Mobility', 'Post-Op'], + equipment: ['Underwater Treadmill', 'Laser Therapy', 'Carts'], + capacity: 'High (1:3 staff-to-animal ratio)', + description: 'Premier facility for dogs recovering from orthopedic surgeries or suffering from severe mobility issues.' + }, + { + id: 'c2', + name: 'Serenity Sanctuary', + specialties: ['Hospice', 'Behavioral', 'Medical Stabilization'], + equipment: ['Isolation Ward', 'Quiet Rooms', 'Oxygen Cages'], + capacity: 'Medium (1:5 staff-to-animal ratio)', + description: 'A quiet, low-stress environment ideal for end-of-life care or dogs needing severe behavioral decompression.' + }, + { + id: 'c3', + name: 'Second Chance Behavioral Center', + specialties: ['Behavioral', 'Reactivity'], + equipment: ['Secure Play Yards', 'Agility Equipment', 'Muzzle Training Gear'], + capacity: 'Low (1:8 staff-to-animal ratio)', + description: 'Specializes in rehabilitating dogs with bite histories or severe reactivity to humans/other dogs.' + }, + { + id: 'c4', + name: 'Hope Medical Rescue', + specialties: ['Post-Op', 'Medical Stabilization', 'Infectious Disease'], + equipment: ['Isolation Ward', 'Surgical Suite', 'IV Pumps'], + capacity: 'High (1:2 staff-to-animal ratio)', + description: 'Intensive care unit for dogs coming straight from severe neglect or trauma situations.' + } +]; diff --git a/aistudio.google.project/src/index.css b/aistudio.google.project/src/index.css new file mode 100644 index 0000000..f1d8c73 --- /dev/null +++ b/aistudio.google.project/src/index.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/aistudio.google.project/src/main.tsx b/aistudio.google.project/src/main.tsx new file mode 100644 index 0000000..9795ed5 --- /dev/null +++ b/aistudio.google.project/src/main.tsx @@ -0,0 +1,13 @@ +import {StrictMode} from 'react'; +import {createRoot} from 'react-dom/client'; +import App from './App.tsx'; +import { AppProvider } from './context/AppContext'; +import './index.css'; + +createRoot(document.getElementById('root')!).render( + + + + + , +); diff --git a/aistudio.google.project/src/services/gemini.ts b/aistudio.google.project/src/services/gemini.ts new file mode 100644 index 0000000..eeae847 --- /dev/null +++ b/aistudio.google.project/src/services/gemini.ts @@ -0,0 +1,97 @@ +import { GoogleGenAI, Type, Schema } from '@google/genai'; +import { DogProfile, CenterProfile, MatchResult } from '../types'; + +const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); + +const decisionSchema: Schema = { + type: Type.OBJECT, + properties: { + matches: { + type: Type.ARRAY, + items: { + type: Type.OBJECT, + properties: { + center_id: { type: Type.STRING }, + center_name: { type: Type.STRING }, + decision: { + type: Type.OBJECT, + properties: { + match_score: { type: Type.NUMBER, description: "0-100" }, + reasoning: { type: Type.STRING, description: "Brief explanation of why this center fits." }, + status_options: { + type: Type.OBJECT, + properties: { + accept_immediately: { type: Type.BOOLEAN }, + conditional_needs: { + type: Type.OBJECT, + properties: { + funding_required: { type: Type.NUMBER, description: "in USD" }, + resources_required: { + type: Type.ARRAY, + items: { type: Type.STRING } + }, + donor_pitch: { type: Type.STRING, description: "A 2-sentence emotional appeal for the donor view." } + } + } + }, + required: ["accept_immediately"] + } + }, + required: ["match_score", "reasoning", "status_options"] + } + }, + required: ["center_id", "center_name", "decision"] + } + } + }, + required: ["matches"] +}; + +export async function evaluateDogProfile(dog: DogProfile, centers: CenterProfile[]): Promise { + const prompt = ` +Please evaluate the following rescue dog profile against the available rehabilitation centers. + +DOG PROFILE: +Name: ${dog.name} +Breed: ${dog.breed} +Age: ${dog.age} +Vet Notes / Description: ${dog.vetNotes} + +AVAILABLE CENTERS: +${JSON.stringify(centers, null, 2)} + +Provide a decision object for EACH center evaluating how well they match this dog's needs. + +CRITICAL: Use neutral, third-person language for the "reasoning" field (e.g., "This center has...", "The facility is equipped with..."). DO NOT use first-person pronouns like "we", "our", or "us". The reasoning is a system analysis, not a statement from the center itself. +`; + + const response = await ai.models.generateContent({ + model: 'gemini-3.1-pro-preview', + contents: prompt, + config: { + systemInstruction: `You are the "Rescue-to-Rehab Intelligence Engine." Your goal is to facilitate the matching of high-needs rescue dogs with specialized rehabilitation centers. + +OPERATIONAL LOGIC: +1. PROFILE ANALYSIS: When a dog profile is submitted, extract 'Care Intensity Level' (1-10) and 'Specialized Needs' (Mobility, Behavioral, Post-Op, Hospice). +2. MATCHING: Evaluate dogs against Center Profiles based on: + - Equipment Match (e.g., Underwater Treadmill, Isolation Ward). + - Capacity (Staff-to-animal ratio). + - Expertise (Specialization in specific breeds or conditions). + +TONE & CONSTRAINTS: +- Professional, efficient, and empathetic. +- Prioritize animal welfare over "perfect matches" (if no perfect match exists, suggest the next best stabilization center). +- Do not provide medical advice; strictly categorize based on the provided vet data. +- Use neutral, third-person language. Never use "we" or "our".`, + responseMimeType: 'application/json', + responseSchema: decisionSchema, + temperature: 0.2, + } + }); + + const text = response.text; + if (!text) throw new Error("No response from AI"); + + const parsed = JSON.parse(text); + return parsed.matches; +} diff --git a/aistudio.google.project/src/types.ts b/aistudio.google.project/src/types.ts new file mode 100644 index 0000000..8f1342d --- /dev/null +++ b/aistudio.google.project/src/types.ts @@ -0,0 +1,63 @@ +export interface CenterProfile { + id: string; + name: string; + specialties: string[]; + equipment: string[]; + capacity: string; + description: string; +} + +export interface DogProfile { + id?: string; + name: string; + breed: string; + age: string; + vetNotes: string; + status?: 'pending_analysis' | 'analyzed' | 'placed' | 'deceased'; + documents?: string[]; +} + +export interface DecisionObject { + match_score: number; + reasoning: string; + status_options: { + accept_immediately: boolean; + conditional_needs?: { + funding_required: number; + resources_required: string[]; + donor_pitch: string; + }; + }; +} + +export interface MatchResult { + center_id: string; + center_name: string; + decision: DecisionObject; +} + +export interface DogWithMatches extends DogProfile { + id: string; + matches?: MatchResult[]; + dateAdded: string; + placementStatus: 'unplaced' | 'conditional' | 'immediate'; + placedCenterId?: string; + placedCenterName?: string; + fundingGoal?: number; + fundingRaised?: number; + resourcesNeeded?: string[]; + donorPitch?: string; + centerDescription?: string; +} + +export interface InboxItem { + id: string; + dogId: string; + centerId: string; + matchScore: number; + reasoning: string; + status: 'unreviewed' | 'contact_requested' | 'confirmed' | 'denied' | 'saved_for_later'; + dateAdded: string; + decisionOptions: DecisionObject; +} + diff --git a/aistudio.google.project/src/views/AdminView.tsx b/aistudio.google.project/src/views/AdminView.tsx new file mode 100644 index 0000000..b1f11b6 --- /dev/null +++ b/aistudio.google.project/src/views/AdminView.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { LineChart, LogOut, Activity, Users, Building2, HeartPulse } from 'lucide-react'; + +interface AdminViewProps { + onLogout: () => void; +} + +export default function AdminView({ onLogout }: AdminViewProps) { + const stats = [ + { label: 'Total Dogs Placed', value: '1,248', icon: HeartPulse, trend: '+12%' }, + { label: 'Active Centers', value: '42', icon: Building2, trend: '+3' }, + { label: 'Funding Raised', value: '$842k', icon: Activity, trend: '+24%' }, + { label: 'Active Donors', value: '3,190', icon: Users, trend: '+8%' }, + ]; + + return ( +
+
+
+
+ +
+

Rescue-to-Rehab Engine

+

Company Backend / Admin

+
+
+ +
+
+ +
+
+

Network Overview

+

System insights, matching metrics, and network oversight.

+
+ +
+ {stats.map((stat, i) => { + const Icon = stat.icon; + return ( +
+
+
+ +
+ + {stat.trend} + +
+

{stat.value}

+

{stat.label}

+
+ ); + })} +
+ +
+
+

Placement Success Rate

+
+

[ Chart Visualization Placeholder ]

+
+
+
+

Center Capacity Heatmap

+
+

[ Map/Heatmap Visualization Placeholder ]

+
+
+
+
+
+ ); +} diff --git a/aistudio.google.project/src/views/CenterView.tsx b/aistudio.google.project/src/views/CenterView.tsx new file mode 100644 index 0000000..4ecff86 --- /dev/null +++ b/aistudio.google.project/src/views/CenterView.tsx @@ -0,0 +1,832 @@ +import React, { useState, useMemo } from 'react'; +import { Building2, LogOut, CheckCircle2, XCircle, MessageSquare, Clock, Save, ShieldAlert, HeartPulse, ChevronRight, Info, Search, Filter, X, Plus, ChevronLeft, UploadCloud, FileText, ImageIcon } from 'lucide-react'; +import { InboxItem, DogWithMatches, DogProfile } from '../types'; +import { useAppContext } from '../context/AppContext'; + +interface CenterViewProps { + onLogout: () => void; +} + +type InboxTab = 'all' | 'unreviewed' | 'saved_for_later' | 'confirmed' | 'denied'; + +export default function CenterView({ onLogout }: CenterViewProps) { + const { inboxItems, dogs, updateInboxStatus, updateDogPlacement, updateDog, addDog } = useAppContext(); + const [activeMainTab, setActiveMainTab] = useState<'inbox' | 'enrolled' | 'profile'>('inbox'); + const [activeInboxTab, setActiveInboxTab] = useState('unreviewed'); + const [searchQuery, setSearchQuery] = useState(''); + + // Enrolled State + const [enrolledView, setEnrolledView] = useState<'list' | 'add' | 'detail' | 'edit'>('list'); + const [editingDog, setEditingDog] = useState(null); + const [enrolledForm, setEnrolledForm] = useState>({ name: '', breed: '', age: '', vetNotes: '' }); + + // Modal State + const [isAcceptModalOpen, setIsAcceptModalOpen] = useState(false); + const [selectedInboxItem, setSelectedInboxItem] = useState<(InboxItem & { dog: DogWithMatches }) | null>(null); + const [acceptType, setAcceptType] = useState<'immediate' | 'conditional'>('immediate'); + const [conditionalForm, setConditionalForm] = useState({ + description: '', + fundingRequired: '', + resourcesRequired: '' + }); + + // Profile State + const [profile, setProfile] = useState({ + name: 'Paws in Motion Rehab', + capacity: 'Medium (10-20 dogs)', + medicalCapabilities: ['Post-Op Orthopedic', 'Neurological', 'Amputee Care', 'Wound Management'], + behavioralCapabilities: ['Fearful/Shy', 'Resource Guarding (Mild)'], + equipment: ['Underwater Treadmill', 'Laser Therapy', 'Custom Slings/Carts', 'Isolation Ward'], + idealIntake: 'We excel with dogs needing intensive physical therapy post-surgery, especially TPLO or spinal surgeries. We prefer dogs under 80lbs due to lifting requirements.', + dealbreakers: 'Severe human aggression, active infectious diseases (parvo, distemper).' + }); + + const centerId = 'c1'; // Mock logged-in center + + const enrolledDogs = useMemo(() => { + return dogs.filter(d => d.placedCenterId === centerId && d.status !== 'deceased'); + }, [dogs]); + + const handleAddEnrolled = (e: React.FormEvent) => { + e.preventDefault(); + if (!enrolledForm.name) return; + const newDog: DogWithMatches = { + id: `DOG-${Math.random().toString(36).substr(2, 9).toUpperCase()}`, + name: enrolledForm.name, + breed: enrolledForm.breed || '', + age: enrolledForm.age || '', + vetNotes: enrolledForm.vetNotes || '', + status: 'placed', + dateAdded: new Date().toISOString().split('T')[0], + placementStatus: 'immediate', + placedCenterId: centerId, + placedCenterName: profile.name + }; + addDog(newDog); + setEnrolledView('list'); + setEnrolledForm({ name: '', breed: '', age: '', vetNotes: '' }); + }; + + const handleEditEnrolled = (e: React.FormEvent) => { + e.preventDefault(); + if (!editingDog) return; + updateDog(editingDog.id, enrolledForm); + setEnrolledView('list'); + setEditingDog(null); + }; + + const myInbox = useMemo(() => { + return inboxItems + .filter(i => i.centerId === centerId) + .map(item => ({ + ...item, + dog: dogs.find(d => d.id === item.dogId)! + })) + .filter(item => item.dog); // Ensure dog exists + }, [inboxItems, dogs]); + + const filteredInbox = useMemo(() => { + let filtered = myInbox; + if (activeInboxTab !== 'all') { + filtered = filtered.filter(i => i.status === activeInboxTab); + } + if (searchQuery) { + const q = searchQuery.toLowerCase(); + filtered = filtered.filter(i => + i.dog.name.toLowerCase().includes(q) || + i.dog.breed.toLowerCase().includes(q) + ); + } + return filtered; + }, [myInbox, activeInboxTab, searchQuery]); + + const handleStatusChange = (id: string, newStatus: InboxItem['status']) => { + updateInboxStatus(id, newStatus); + }; + + const openAcceptModal = (item: InboxItem & { dog: DogWithMatches }) => { + setSelectedInboxItem(item); + setAcceptType(item.decisionOptions.status_options.accept_immediately ? 'immediate' : 'conditional'); + setConditionalForm({ + description: item.decisionOptions.status_options.conditional_needs?.donor_pitch || '', + fundingRequired: item.decisionOptions.status_options.conditional_needs?.funding_required?.toString() || '', + resourcesRequired: item.decisionOptions.status_options.conditional_needs?.resources_required?.join(', ') || '' + }); + setIsAcceptModalOpen(true); + }; + + const submitAccept = (e: React.FormEvent) => { + e.preventDefault(); + if (!selectedInboxItem) return; + + updateInboxStatus(selectedInboxItem.id, 'confirmed'); + + if (acceptType === 'immediate') { + updateDogPlacement(selectedInboxItem.dogId, { + placementStatus: 'immediate', + placedCenterId: centerId, + placedCenterName: profile.name, + centerDescription: 'We are prepared to take this dog immediately and have all necessary resources.' + }); + } else { + updateDogPlacement(selectedInboxItem.dogId, { + placementStatus: 'conditional', + placedCenterId: centerId, + placedCenterName: profile.name, + fundingGoal: Number(conditionalForm.fundingRequired) || 0, + fundingRaised: 0, + resourcesNeeded: conditionalForm.resourcesRequired.split(',').map(s => s.trim()).filter(Boolean), + donorPitch: conditionalForm.description, + centerDescription: conditionalForm.description + }); + } + + setIsAcceptModalOpen(false); + setSelectedInboxItem(null); + }; + + const handleProfileSave = (e: React.FormEvent) => { + e.preventDefault(); + alert('Profile updated successfully. This will improve your future match accuracy.'); + }; + + const toggleArrayItem = (arrayName: 'medicalCapabilities' | 'behavioralCapabilities' | 'equipment', item: string) => { + setProfile(prev => { + const array = prev[arrayName] as string[]; + if (array.includes(item)) { + return { ...prev, [arrayName]: array.filter(i => i !== item) }; + } else { + return { ...prev, [arrayName]: [...array, item] }; + } + }); + }; + + const unreviewedCount = myInbox.filter(i => i.status === 'unreviewed').length; + + return ( +
+
+
+
+ +
+

{profile.name}

+

Center Management Portal

+
+
+ +
+
+ +
+ + {/* Navigation Tabs */} +
+ + + +
+ + {/* INBOX TAB */} + {activeMainTab === 'inbox' && ( +
+
+
+

Intake Inbox

+

Review dogs where your center is a top 5 match.

+
+ +
+ + setSearchQuery(e.target.value)} + className="outline-none bg-transparent text-sm w-full md:w-64" + /> +
+
+ + {/* Sub-tabs for Inbox */} +
+ {[ + { id: 'all', label: 'All' }, + { id: 'unreviewed', label: 'New' }, + { id: 'saved_for_later', label: 'Saved' }, + { id: 'confirmed', label: 'Accepted' }, + { id: 'denied', label: 'Denied' } + ].map(tab => ( + + ))} +
+ + {filteredInbox.length === 0 ? ( +
+

No matches found in this category.

+
+ ) : ( +
+ {filteredInbox.map(item => ( +
+ {item.status === 'unreviewed' && ( +
+ Action Required: Unreviewed Match +
+ )} + {item.status === 'confirmed' && ( +
+ Accepted Intake +
+ )} +
+ + {/* Dog Info */} +
+
+
+

{item.dog.name}

+

{item.dog.breed} • {item.dog.age}

+
+
+
+ {item.matchScore} + /100 +
+ Match Score +
+
+ +
+

+ Why you matched: + {item.reasoning} +

+
+ +
+

Veterinary Notes

+

{item.dog.vetNotes}

+
+ + {item.dog.documents && item.dog.documents.length > 0 && ( +
+

Attached Documents

+
+ {item.dog.documents.map((doc, i) => ( + + {doc} + + ))} +
+
+ )} +
+ + {/* Actions */} +
+
+

Review Decision

+
+ {item.status !== 'confirmed' && ( + + )} + {item.status !== 'contact_requested' && item.status !== 'confirmed' && ( + + )} + {item.status !== 'saved_for_later' && item.status !== 'confirmed' && ( + + )} + {item.status !== 'denied' && item.status !== 'confirmed' && ( + + )} +
+
+
+ Added {item.dateAdded} +
+
+ +
+
+ ))} +
+ )} +
+ )} + + {/* ENROLLED TAB */} + {activeMainTab === 'enrolled' && ( +
+ {enrolledView === 'list' && ( + <> +
+
+

Enrolled Animals

+

Manage dogs currently in your care.

+
+ +
+ + {enrolledDogs.length === 0 ? ( +
+

You have no animals currently enrolled.

+
+ ) : ( +
+ {enrolledDogs.map(dog => ( +
{ setEditingDog(dog); setEnrolledView('detail'); }} + className="bg-white rounded-2xl shadow-sm border border-slate-200 p-6 cursor-pointer hover:border-amber-300 hover:shadow-md transition-all group" + > +

{dog.name}

+

ID: {dog.id} • {dog.breed} • {dog.age}

+

{dog.vetNotes}

+
+ ))} +
+ )} + + )} + + {enrolledView === 'add' && ( +
+ +
+
+

Add Enrolled Animal

+
+
+
+
+ + setEnrolledForm({...enrolledForm, name: e.target.value})} className="w-full rounded-xl border-slate-300 border px-4 py-2.5 focus:ring-2 focus:ring-amber-500 outline-none" /> +
+
+ + setEnrolledForm({...enrolledForm, breed: e.target.value})} className="w-full rounded-xl border-slate-300 border px-4 py-2.5 focus:ring-2 focus:ring-amber-500 outline-none" /> +
+
+ + setEnrolledForm({...enrolledForm, age: e.target.value})} className="w-full rounded-xl border-slate-300 border px-4 py-2.5 focus:ring-2 focus:ring-amber-500 outline-none" /> +
+
+
+ +