Fix hydration
This commit is contained in:
parent
74b76ee053
commit
eb77c00540
3 changed files with 30 additions and 46 deletions
|
@ -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";
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 })}
|
||||||
|
|
Loading…
Reference in a new issue