export base44 - Nov 18
This commit is contained in:
@@ -1,31 +1,38 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { base44 } from "@/api/base44Client";
|
||||
import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { createPageUrl } from "@/utils";
|
||||
import EventFormWizard from "@/components/events/EventFormWizard";
|
||||
import AIOrderAssistant from "@/components/events/AIOrderAssistant";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Sparkles, FileText, X } from "lucide-react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { X, AlertTriangle } from "lucide-react";
|
||||
import { detectAllConflicts, ConflictAlert } from "@/components/scheduling/ConflictDetection";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export default function CreateEvent() {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { toast } = useToast();
|
||||
const [useAI, setUseAI] = useState(false);
|
||||
const [aiExtractedData, setAiExtractedData] = useState(null);
|
||||
const [pendingEvent, setPendingEvent] = React.useState(null);
|
||||
const [showConflictWarning, setShowConflictWarning] = React.useState(false);
|
||||
|
||||
const { data: currentUser } = useQuery({
|
||||
queryKey: ['current-user-create-event'],
|
||||
queryFn: () => base44.auth.me(),
|
||||
});
|
||||
|
||||
const { data: allEvents = [] } = useQuery({
|
||||
queryKey: ['events-for-conflict-check'],
|
||||
queryFn: () => base44.entities.Event.list(),
|
||||
initialData: [],
|
||||
});
|
||||
|
||||
const createEventMutation = useMutation({
|
||||
mutationFn: (eventData) => base44.entities.Event.create(eventData),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['events'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['client-events'] });
|
||||
toast({
|
||||
title: "✅ Event Created",
|
||||
description: "Your event has been created successfully.",
|
||||
@@ -42,107 +49,98 @@ export default function CreateEvent() {
|
||||
});
|
||||
|
||||
const handleSubmit = (eventData) => {
|
||||
createEventMutation.mutate(eventData);
|
||||
// Detect conflicts before creating
|
||||
const conflicts = detectAllConflicts(eventData, allEvents);
|
||||
|
||||
if (conflicts.length > 0) {
|
||||
setPendingEvent({ ...eventData, detected_conflicts: conflicts });
|
||||
setShowConflictWarning(true);
|
||||
} else {
|
||||
createEventMutation.mutate(eventData);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAIDataExtracted = (extractedData) => {
|
||||
setAiExtractedData(extractedData);
|
||||
setUseAI(false);
|
||||
const handleConfirmWithConflicts = () => {
|
||||
if (pendingEvent) {
|
||||
createEventMutation.mutate(pendingEvent);
|
||||
setShowConflictWarning(false);
|
||||
setPendingEvent(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelConflicts = () => {
|
||||
setShowConflictWarning(false);
|
||||
setPendingEvent(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
|
||||
<div className="max-w-7xl mx-auto p-4 md:p-8">
|
||||
{/* Header with AI Toggle */}
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-[#1C323E]">Create New Order</h1>
|
||||
<h1 className="text-3xl font-bold text-[#1C323E]">Create Standard Order</h1>
|
||||
<p className="text-slate-600 mt-1">
|
||||
{useAI ? "Use AI to create your order naturally" : "Fill out the form to create your order"}
|
||||
Fill out the details for your planned event
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant={useAI ? "default" : "outline"}
|
||||
onClick={() => setUseAI(true)}
|
||||
className={useAI ? "bg-gradient-to-r from-[#0A39DF] to-purple-600" : ""}
|
||||
>
|
||||
<Sparkles className="w-4 h-4 mr-2" />
|
||||
AI Assistant
|
||||
</Button>
|
||||
<Button
|
||||
variant={!useAI ? "default" : "outline"}
|
||||
onClick={() => setUseAI(false)}
|
||||
className={!useAI ? "bg-[#1C323E]" : ""}
|
||||
>
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
Form
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => navigate(createPageUrl("Events"))}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => navigate(createPageUrl("ClientDashboard"))}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* AI Assistant Interface */}
|
||||
<AnimatePresence>
|
||||
{useAI && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
>
|
||||
<AIOrderAssistant
|
||||
onOrderDataExtracted={handleAIDataExtracted}
|
||||
onClose={() => setUseAI(false)}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Wizard Form */}
|
||||
{!useAI && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
>
|
||||
{aiExtractedData && (
|
||||
<div className="mb-6 p-4 bg-green-50 border border-green-200 rounded-xl">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Sparkles className="w-5 h-5 text-green-600" />
|
||||
<span className="font-semibold text-green-900">AI Pre-filled Data</span>
|
||||
{/* Conflict Warning Modal */}
|
||||
{showConflictWarning && pendingEvent && (
|
||||
<Card className="mb-6 border-2 border-orange-500">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-start gap-4 mb-4">
|
||||
<div className="w-12 h-12 bg-orange-100 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
<AlertTriangle className="w-6 h-6 text-orange-600" />
|
||||
</div>
|
||||
<p className="text-sm text-green-700 mb-3">
|
||||
The form has been pre-filled with information from your conversation. Review and edit as needed.
|
||||
</p>
|
||||
<div>
|
||||
<h3 className="font-bold text-lg text-slate-900 mb-1">
|
||||
Scheduling Conflicts Detected
|
||||
</h3>
|
||||
<p className="text-sm text-slate-600">
|
||||
This event has {pendingEvent.detected_conflicts.length} potential conflict{pendingEvent.detected_conflicts.length !== 1 ? 's' : ''}
|
||||
with existing bookings. Review the conflicts below and decide how to proceed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<ConflictAlert conflicts={pendingEvent.detected_conflicts} />
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setAiExtractedData(null);
|
||||
setUseAI(true);
|
||||
}}
|
||||
className="border-green-300 text-green-700 hover:bg-green-100"
|
||||
onClick={handleCancelConflicts}
|
||||
>
|
||||
<Sparkles className="w-4 h-4 mr-2" />
|
||||
Chat with AI Again
|
||||
Go Back & Edit
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleConfirmWithConflicts}
|
||||
className="bg-orange-600 hover:bg-orange-700"
|
||||
>
|
||||
Create Anyway
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EventFormWizard
|
||||
event={aiExtractedData}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={createEventMutation.isPending}
|
||||
currentUser={currentUser}
|
||||
onCancel={() => navigate(createPageUrl("Events"))}
|
||||
/>
|
||||
</motion.div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<EventFormWizard
|
||||
event={null}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={createEventMutation.isPending}
|
||||
currentUser={currentUser}
|
||||
onCancel={() => navigate(createPageUrl("ClientDashboard"))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user