import React, { useState, useRef, useEffect } from 'react';
import { Send, CheckCircle, Loader } from 'lucide-react';
export default function SuperintendentLog() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [conversationHistory, setConversationHistory] = useState([]);
const [showConfig, setShowConfig] = useState(true);
const [apiKey, setApiKey] = useState('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');
const [showSubmit, setShowSubmit] = useState(false);
const [qbConfig] = useState({
realm: 'willowbrook-9559.quickbase.com',
tableId: 'bvp4p5y9h',
userToken: 'cadq4n_rgpx_0_d6z2wjncrtb4mviam5sdcyg22b'
});
const messagesEndRef = useRef(null);
const scrollToBottom = () => messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
useEffect(() => {
scrollToBottom();
}, [messages]);
function getTodayDate() {
const today = new Date();
const day = String(today.getDate()).padStart(2, '0');
const month = String(today.getMonth() + 1).padStart(2, '0');
const year = today.getFullYear();
return `${day}-${month}-${year}`;
}
const startConversation = () => {
if (!apiKey.trim()) {
alert('Please enter your Anthropic API key first');
return;
}
setShowConfig(false);
const systemPrompt = `You are helping a superintendent complete their daily log. Today's date is ${getTodayDate()}.
REQUIRED FIELDS TO COLLECT:
- project (project name)
- date (assume today: ${getTodayDate()} unless told otherwise)
- name (superintendent's name)
- weather_summary
- sub_and_crew_count
- issues_delays (if none mentioned, ask once)
- visitors (if none mentioned, ask once)
- notes_photos (optional)
CONVERSATION RULES:
1. The superintendent may provide multiple pieces of information at once - extract everything they mention
2. Ask one or two questions at a time for ONLY the missing information
3. Be concise and mobile-friendly
4. Start by asking which project this is for
5. After each response, identify what's been provided and what's still needed
6. When all REQUIRED fields are collected, ask: "Do you want me to submit this log now?"
7. Keep track internally of what information has been provided
Start the conversation now.`;
const welcomeMsg = {
role: 'assistant',
content: `Hi! Let's get your daily log started for ${getTodayDate()}. Tell me about your day - which project were you on and what happened?`
};
setMessages([welcomeMsg]);
setConversationHistory([
{ role: 'user', content: systemPrompt },
welcomeMsg
]);
};
const callClaudeAPI = async (userMessage) => {
const newHistory = [
...conversationHistory,
{ role: 'user', content: userMessage }
];
try {
console.log('Calling Claude API with message:', userMessage);
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01'
},
body: JSON.stringify({
model: 'claude-sonnet-4-20250514',
max_tokens: 1000,
messages: newHistory.slice(1)
})
});
if (!response.ok) {
const errorData = await response.json();
console.error('Claude API error:', errorData);
throw new Error(`API Error: ${errorData.error?.message || 'Unknown error'}`);
}
const data = await response.json();
if (data.content && data.content[0]) {
const assistantMessage = data.content[0].text;
console.log('Claude response:', assistantMessage);
setConversationHistory([
...newHistory,
{ role: 'assistant', content: assistantMessage }
]);
// Check if ready to submit
const lowerMsg = assistantMessage.toLowerCase();
if (lowerMsg.includes('submit') && (lowerMsg.includes('log') || lowerMsg.includes('now') || lowerMsg.includes('?'))) {
console.log('🟢 SUBMIT BUTTON SHOULD SHOW NOW');
setShowSubmit(true);
}
return assistantMessage;
}
} catch (error) {
console.error('Claude API error:', error);
return `Error: ${error.message}. Please check your API key and try again.`;
}
};
const handleSend = async () => {
if (!input.trim() || isLoading) return;
const userMessage = input.trim();
setInput('');
console.log('User sent:', userMessage);
setMessages(prev => [...prev, { role: 'user', content: userMessage }]);
setIsLoading(true);
// Check if user is confirming submission
if (showSubmit && (userMessage.toLowerCase().includes('yes') ||
userMessage.toLowerCase().includes('submit') ||
userMessage.toLowerCase().includes('confirm'))) {
console.log('🟢 USER CONFIRMED - SUBMITTING TO QUICKBASE');
await handleSubmitToQuickbase();
setIsLoading(false);
return;
}
const response = await callClaudeAPI(userMessage);
setMessages(prev => [...prev, { role: 'assistant', content: response }]);
setIsLoading(false);
};
const handleSubmitToQuickbase = async () => {
setIsLoading(true);
setMessages(prev => [...prev, {
role: 'assistant',
content: '⏳ Extracting data and submitting to Quickbase...'
}]);
try {
// Extract final data from conversation
const dataExtractionPrompt = `Based on our entire conversation, extract the following fields in JSON format.
Use the information provided by the superintendent. If something wasn't mentioned, use "Not provided".
{
"project": "project name",
"date": "${getTodayDate()}",
"name": "superintendent name or 'Not provided'",
"weather_summary": "weather description or 'Not provided'",
"sub_and_crew_count": "count details or 'Not provided'",
"issues_delays": "any issues or 'None'",
"visitors": "visitor info or 'None'",
"notes_photos": "notes or 'None'"
}
Return ONLY the JSON object, no other text.`;
const extractResponse = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01'
},
body: JSON.stringify({
model: 'claude-sonnet-4-20250514',
max_tokens: 500,
messages: [
...conversationHistory.slice(1),
{ role: 'user', content: dataExtractionPrompt }
]
})
});
const extractData = await extractResponse.json();
const extractedText = extractData.content[0].text;
const jsonMatch = extractedText.match(/\{[\s\S]*\}/);
const logDataExtracted = JSON.parse(jsonMatch[0]);
console.log('Extracted data:', logDataExtracted);
// Create conversation transcript
const transcript = conversationHistory.slice(1).map(msg =>
`${msg.role === 'user' ? 'Superintendent' : 'AI'}: ${msg.content}`
).join('\n\n');
const qbPayload = {
to: qbConfig.tableId,
data: [{
16: { value: logDataExtracted.project },
6: { value: logDataExtracted.date },
7: { value: logDataExtracted.name },
8: { value: logDataExtracted.weather_summary },
9: { value: logDataExtracted.sub_and_crew_count },
10: { value: logDataExtracted.issues_delays },
11: { value: logDataExtracted.visitors },
12: { value: logDataExtracted.notes_photos },
13: { value: transcript }
}]
};
console.log('Submitting to Quickbase:', qbPayload);
const qbResponse = await fetch(
'https://api.quickbase.com/v1/records',
{
method: 'POST',
headers: {
'QB-Realm-Hostname': qbConfig.realm,
'Authorization': `QB-USER-TOKEN ${qbConfig.userToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(qbPayload)
}
);
console.log('QB Response status:', qbResponse.status);
if (qbResponse.ok) {
const result = await qbResponse.json();
console.log('QB Success:', result);
setMessages(prev => [...prev, {
role: 'assistant',
content: `✅ Daily log submitted successfully to Quickbase!\n\nSubmitted data:\n${JSON.stringify(logDataExtracted, null, 2)}`
}]);
setShowSubmit(false);
} else {
const errorText = await qbResponse.text();
console.error('QB Error:', errorText);
setMessages(prev => [...prev, {
role: 'assistant',
content: `❌ Quickbase error: ${errorText}`
}]);
}
} catch (error) {
console.error('Submission error:', error);
setMessages(prev => [...prev, {
role: 'assistant',
content: `❌ Error: ${error.message}`
}]);
}
setIsLoading(false);
};
if (showConfig) {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 p-4 flex items-center justify-center">
<div className="bg-white rounded-lg shadow-xl p-8 max-w-md w-full">
<h1 className="text-2xl font-bold text-gray-800 mb-6">Superintendent Daily Log</h1>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Anthropic API Key *
</label>
<input
type="password"
placeholder="sk-ant-..."
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
<p className="text-xs text-gray-500 mt-1">
Get your API key from <a href="https://console.anthropic.com" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">console.anthropic.com</a>
</p>
</div>
</div>
<div className="bg-green-50 border border-green-200 rounded-lg p-4 my-4">
<p className="text-sm text-green-800">✓ Quickbase connected</p>
<p className="text-xs text-green-600">Table: {qbConfig.tableId}</p>
</div>
<button
onClick={startConversation}
disabled={!apiKey.trim()}
className="w-full bg-blue-600 text-white py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
Start Daily Log
</button>
<div className="mt-4 text-xs text-gray-600 bg-blue-50 border border-blue-200 rounded p-3">
<p className="font-semibold mb-1">💡 How it works:</p>
<p>Speak naturally! Say everything at once like: "Main Street project, sunny weather, 12 crew members, no issues today"</p>
<p className="mt-2">AI will understand and only ask about what's missing.</p>
</div>
</div>
</div>
);
}
return (
<div className="flex flex-col h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
<div className="bg-white shadow-md p-4 border-b">
<h1 className="text-xl font-bold text-gray-800">Superintendent Daily Log</h1>
<p className="text-sm text-gray-600">Chat naturally - AI will extract all information</p>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, idx) => (
<div
key={idx}
className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[80%] rounded-lg p-3 ${
msg.role === 'user'
? 'bg-blue-600 text-white'
: 'bg-white text-gray-800 shadow-md'
}`}
>
<p className="whitespace-pre-wrap">{msg.content}</p>
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-white rounded-lg p-3 shadow-md">
<Loader className="w-5 h-5 animate-spin text-blue-600" />
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{showSubmit && (
<div className="p-4 bg-green-50 border-t border-green-200">
<p className="text-sm text-green-800 mb-2">✓ Ready to submit! Just say "yes" or click below:</p>
<button
onClick={handleSubmitToQuickbase}
disabled={isLoading}
className="w-full bg-green-600 text-white py-3 rounded-lg font-semibold hover:bg-green-700 transition-colors flex items-center justify-center gap-2"
>
<CheckCircle className="w-5 h-5" />
Submit to Quickbase Now
</button>
</div>
)}
<div className="p-4 bg-white border-t">
<div className="flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
placeholder="Type your response..."
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
disabled={isLoading}
/>
<button
onClick={handleSend}
disabled={isLoading || !input.trim()}
className="bg-blue-600 text-white p-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<Send className="w-5 h-5" />
</button>
</div>
</div>
</div>
);
}