Fix hydration

This commit is contained in:
chark1es 2025-03-01 04:22:15 -08:00
parent 74b76ee053
commit eb77c00540
3 changed files with 30 additions and 46 deletions

View file

@ -4,6 +4,7 @@ import { Get } from "../../scripts/pocketbase/Get";
import EventRequestForm from "./Officer_EventRequestForm/EventRequestForm"; import EventRequestForm from "./Officer_EventRequestForm/EventRequestForm";
import UserEventRequests from "./Officer_EventRequestForm/UserEventRequests"; import UserEventRequests from "./Officer_EventRequestForm/UserEventRequests";
import { Collections } from "../../schemas/pocketbase/schema"; import { Collections } from "../../schemas/pocketbase/schema";
import { Toaster } from "react-hot-toast";
// Import the EventRequest type from UserEventRequests to ensure consistency // Import the EventRequest type from UserEventRequests to ensure consistency
import type { EventRequest as UserEventRequest } from "./Officer_EventRequestForm/UserEventRequests"; import type { EventRequest as UserEventRequest } from "./Officer_EventRequestForm/UserEventRequests";
@ -117,6 +118,9 @@ if (auth.isAuthenticated()) {
</div> </div>
</div> </div>
<!-- Toast container for notifications -->
<Toaster client:load position="bottom-right" />
<script> <script>
// Import the DataSyncService for client-side use // Import the DataSyncService for client-side use
import { DataSyncService } from "../../scripts/database/DataSyncService"; import { DataSyncService } from "../../scripts/database/DataSyncService";

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import toast, { Toaster } from 'react-hot-toast'; import toast from 'react-hot-toast';
import { Authentication } from '../../../scripts/pocketbase/Authentication'; import { Authentication } from '../../../scripts/pocketbase/Authentication';
import { Update } from '../../../scripts/pocketbase/Update'; import { Update } from '../../../scripts/pocketbase/Update';
import { FileManager } from '../../../scripts/pocketbase/FileManager'; import { FileManager } from '../../../scripts/pocketbase/FileManager';
@ -774,28 +774,6 @@ const EventRequestForm: React.FC = () => {
return ( return (
<> <>
<Toaster
position="top-right"
toastOptions={{
duration: 4000,
style: {
background: '#333',
color: '#fff',
},
success: {
duration: 3000,
style: {
background: 'green',
},
},
error: {
duration: 5000,
style: {
background: 'red',
},
},
}}
/>
<motion.div <motion.div
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"

View file

@ -50,6 +50,16 @@ const InvoiceBuilder: React.FC<InvoiceBuilderProps> = ({ invoiceData, onChange }
unitPrice: 0 unitPrice: 0
}); });
// Use a counter for generating IDs to avoid hydration issues
const [idCounter, setIdCounter] = useState(1);
// Generate a unique ID for new items without using non-deterministic functions
const generateId = () => {
const id = `item-${idCounter}`;
setIdCounter(prev => prev + 1);
return id;
};
// State for validation errors // State for validation errors
const [errors, setErrors] = useState<{ const [errors, setErrors] = useState<{
description?: string; description?: string;
@ -58,11 +68,6 @@ const InvoiceBuilder: React.FC<InvoiceBuilderProps> = ({ invoiceData, onChange }
vendor?: string; vendor?: string;
}>({}); }>({});
// Generate a unique ID for new items
const generateId = () => {
return Date.now().toString(36) + Math.random().toString(36).substring(2);
};
// Calculate totals whenever invoice data changes // Calculate totals whenever invoice data changes
useEffect(() => { useEffect(() => {
calculateTotals(); calculateTotals();
@ -92,12 +97,13 @@ const InvoiceBuilder: React.FC<InvoiceBuilderProps> = ({ invoiceData, onChange }
}); });
}; };
// Validate new item // Validate new item before adding
const validateNewItem = () => { const validateNewItem = () => {
const newErrors: { const newErrors: {
description?: string; description?: string;
quantity?: string; quantity?: string;
unitPrice?: string; unitPrice?: string;
vendor?: string;
} = {}; } = {};
if (!newItem.description.trim()) { if (!newItem.description.trim()) {
@ -112,24 +118,22 @@ const InvoiceBuilder: React.FC<InvoiceBuilderProps> = ({ invoiceData, onChange }
newErrors.unitPrice = 'Unit price must be 0 or greater'; newErrors.unitPrice = 'Unit price must be 0 or greater';
} }
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
// Add a new item
const handleAddItem = () => {
if (!validateNewItem()) {
return;
}
// Check for duplicate description // Check for duplicate description
const isDuplicate = invoiceData.items.some( const isDuplicate = invoiceData.items.some(
item => item.description.toLowerCase() === newItem.description.toLowerCase() item => item.description.toLowerCase() === newItem.description.toLowerCase()
); );
if (isDuplicate) { if (isDuplicate) {
setErrors({ description: 'An item with this description already exists' }); newErrors.description = 'An item with this description already exists';
toast.error('An item with this description already exists'); }
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
// Add a new item to the invoice
const handleAddItem = () => {
if (!validateNewItem()) {
return; return;
} }
@ -320,17 +324,14 @@ const InvoiceBuilder: React.FC<InvoiceBuilderProps> = ({ invoiceData, onChange }
</label> </label>
<input <input
type="number" type="number"
id="quantity"
className={`input input-bordered input-sm ${errors.quantity ? 'input-error' : ''}`} className={`input input-bordered input-sm ${errors.quantity ? 'input-error' : ''}`}
value={newItem.quantity} value={newItem.quantity}
onChange={(e) => setNewItem({ ...newItem, quantity: parseInt(e.target.value) || 0 })} onChange={(e) => setNewItem({ ...newItem, quantity: parseInt(e.target.value) || 0 })}
min="1" min="1"
step="1" step="1"
/> />
{errors.quantity && ( {errors.quantity && <div className="text-error text-xs mt-1">{errors.quantity}</div>}
<label className="label">
<span className="label-text-alt text-error">{errors.quantity}</span>
</label>
)}
</div> </div>
<div className="form-control"> <div className="form-control">
<label className="label"> <label className="label">
@ -338,6 +339,7 @@ const InvoiceBuilder: React.FC<InvoiceBuilderProps> = ({ invoiceData, onChange }
</label> </label>
<input <input
type="number" type="number"
id="unitPrice"
className={`input input-bordered input-sm ${errors.unitPrice ? 'input-error' : ''}`} className={`input input-bordered input-sm ${errors.unitPrice ? 'input-error' : ''}`}
value={newItem.unitPrice} value={newItem.unitPrice}
onChange={(e) => setNewItem({ ...newItem, unitPrice: parseFloat(e.target.value) || 0 })} onChange={(e) => setNewItem({ ...newItem, unitPrice: parseFloat(e.target.value) || 0 })}