fix events time issue
This commit is contained in:
parent
b5e9e599aa
commit
0c2fd1a8c2
5 changed files with 314 additions and 27 deletions
89
src/components/dashboard/Officer_EmailManagement.astro
Normal file
89
src/components/dashboard/Officer_EmailManagement.astro
Normal file
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import EmailRequestSettings from "./SettingsSection/EmailRequestSettings";
|
||||
|
||||
// Import environment variables for debugging if needed
|
||||
const logtoApiEndpoint = import.meta.env.LOGTO_API_ENDPOINT || "";
|
||||
---
|
||||
<div id="officer-email-section" class="">
|
||||
<div class="mb-6">
|
||||
<h2 class="text-2xl font-bold">IEEE Email Management</h2>
|
||||
<p class="opacity-70">Manage your official IEEE UCSD email address</p>
|
||||
</div>
|
||||
|
||||
<!-- IEEE Email Management Card -->
|
||||
<div
|
||||
class="card bg-card shadow-xl border border-border hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-6"
|
||||
>
|
||||
<div class="card-body">
|
||||
<h3 class="card-title flex items-center gap-3">
|
||||
<div
|
||||
class="inline-flex items-center justify-center p-3 rounded-full bg-primary text-primary-foreground"
|
||||
>
|
||||
<Icon name="heroicons:envelope" class="h-5 w-5" />
|
||||
</div>
|
||||
IEEE Email Address
|
||||
</h3>
|
||||
<p class="text-sm opacity-70 mb-4">
|
||||
Request and manage your official IEEE UCSD email address. This email can be used for official IEEE communications and professional purposes.
|
||||
</p>
|
||||
<div class="h-px w-full bg-border my-4"></div>
|
||||
<EmailRequestSettings client:load />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Guidelines Card -->
|
||||
<div
|
||||
class="card bg-card shadow-xl border border-border hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-6"
|
||||
>
|
||||
<div class="card-body">
|
||||
<h3 class="card-title flex items-center gap-3">
|
||||
<div
|
||||
class="inline-flex items-center justify-center p-3 rounded-full bg-info text-info-foreground"
|
||||
>
|
||||
<Icon name="heroicons:information-circle" class="h-5 w-5" />
|
||||
</div>
|
||||
Email Usage Guidelines
|
||||
</h3>
|
||||
<div class="space-y-4 text-sm">
|
||||
<div class="alert alert-info">
|
||||
<Icon name="heroicons:information-circle" class="h-4 w-4" />
|
||||
<div>
|
||||
<h4 class="font-bold">Officer Email Access</h4>
|
||||
<p>IEEE email addresses are only available to active IEEE UCSD officers. Your officer status is automatically verified when you request an email.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold">Acceptable Use:</h4>
|
||||
<ul class="list-disc list-inside space-y-1 opacity-80">
|
||||
<li>Official IEEE UCSD communications</li>
|
||||
<li>Professional networking related to IEEE activities</li>
|
||||
<li>Event coordination and planning</li>
|
||||
<li>Communications with sponsors and external partners</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold">Email Features:</h4>
|
||||
<ul class="list-disc list-inside space-y-1 opacity-80">
|
||||
<li>Webmail access at <a href="https://mail.ieeeucsd.org" target="_blank" rel="noopener noreferrer" class="link link-primary">https://mail.ieeeucsd.org</a></li>
|
||||
<li>IMAP/SMTP support for email clients</li>
|
||||
<li>5GB storage space</li>
|
||||
<li>Professional @ieeeucsd.org domain</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold">Important Notes:</h4>
|
||||
<ul class="list-disc list-inside space-y-1 opacity-80">
|
||||
<li>Your email username is based on your personal email address</li>
|
||||
<li>Passwords can be reset through this interface</li>
|
||||
<li>Email access may be revoked when officer status changes</li>
|
||||
<li>Contact the webmaster for any technical issues</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -129,7 +129,27 @@ const EventDetailsSection: React.FC<EventDetailsSectionProps> = ({ formData, onD
|
|||
type="datetime-local"
|
||||
className="input input-bordered focus:input-primary transition-all duration-300 mt-2"
|
||||
value={formData.start_date_time}
|
||||
onChange={(e) => onDataChange({ start_date_time: e.target.value })}
|
||||
onChange={(e) => {
|
||||
const newStartDateTime = e.target.value;
|
||||
onDataChange({ start_date_time: newStartDateTime });
|
||||
|
||||
// If there's already an end time set, update it to use the new start date
|
||||
if (formData.end_date_time && newStartDateTime) {
|
||||
try {
|
||||
const existingEndDate = new Date(formData.end_date_time);
|
||||
const newStartDate = new Date(newStartDateTime);
|
||||
|
||||
if (!isNaN(existingEndDate.getTime()) && !isNaN(newStartDate.getTime())) {
|
||||
// Keep the same time but update to the new date
|
||||
const updatedEndDate = new Date(newStartDate);
|
||||
updatedEndDate.setHours(existingEndDate.getHours(), existingEndDate.getMinutes(), 0, 0);
|
||||
onDataChange({ end_date_time: updatedEndDate.toISOString() });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating end date when start date changed:', error);
|
||||
}
|
||||
}
|
||||
}}
|
||||
required
|
||||
whileHover="hover"
|
||||
variants={inputHoverVariants}
|
||||
|
@ -155,25 +175,59 @@ const EventDetailsSection: React.FC<EventDetailsSectionProps> = ({ formData, onD
|
|||
<motion.input
|
||||
type="time"
|
||||
className="input input-bordered focus:input-primary transition-all duration-300"
|
||||
value={formData.end_date_time ? new Date(formData.end_date_time).toTimeString().substring(0, 5) : ''}
|
||||
value={formData.end_date_time ? (() => {
|
||||
try {
|
||||
const endDate = new Date(formData.end_date_time);
|
||||
if (isNaN(endDate.getTime())) return '';
|
||||
return endDate.toTimeString().substring(0, 5);
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
})() : ''}
|
||||
onChange={(e) => {
|
||||
if (formData.start_date_time) {
|
||||
// Create a new date object from start_date_time
|
||||
const startDate = new Date(formData.start_date_time);
|
||||
// Parse the time value
|
||||
const [hours, minutes] = e.target.value.split(':').map(Number);
|
||||
// Set the hours and minutes on the date
|
||||
startDate.setHours(hours, minutes);
|
||||
// Update end_date_time with the new time but same date as start
|
||||
onDataChange({ end_date_time: startDate.toISOString() });
|
||||
const timeValue = e.target.value;
|
||||
if (timeValue && formData.start_date_time) {
|
||||
try {
|
||||
// Create a new date object from start_date_time
|
||||
const startDate = new Date(formData.start_date_time);
|
||||
if (isNaN(startDate.getTime())) {
|
||||
console.error('Invalid start date time');
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the time value
|
||||
const [hours, minutes] = timeValue.split(':').map(Number);
|
||||
|
||||
// Validate hours and minutes
|
||||
if (isNaN(hours) || isNaN(minutes) || hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
||||
console.error('Invalid time values');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new date with the same date as start but different time
|
||||
const endDate = new Date(startDate);
|
||||
endDate.setHours(hours, minutes, 0, 0);
|
||||
|
||||
// Update end_date_time with the new time but same date as start
|
||||
onDataChange({ end_date_time: endDate.toISOString() });
|
||||
} catch (error) {
|
||||
console.error('Error setting end time:', error);
|
||||
}
|
||||
} else if (!timeValue) {
|
||||
// Clear end_date_time if time is cleared
|
||||
onDataChange({ end_date_time: '' });
|
||||
}
|
||||
}}
|
||||
required
|
||||
disabled={!formData.start_date_time}
|
||||
whileHover="hover"
|
||||
variants={inputHoverVariants}
|
||||
/>
|
||||
<p className="text-xs text-base-content/60">
|
||||
The end time will use the same date as the start date.
|
||||
{!formData.start_date_time
|
||||
? "Please set the start date and time first."
|
||||
: "The end time will use the same date as the start date."
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
|
|
@ -176,9 +176,28 @@ const EventRequestForm: React.FC = () => {
|
|||
}
|
||||
|
||||
setFormData(prevData => {
|
||||
// Save to localStorage
|
||||
const updatedData = { ...prevData, ...sectionData };
|
||||
localStorage.setItem('eventRequestFormData', JSON.stringify(updatedData));
|
||||
|
||||
// Save to localStorage
|
||||
try {
|
||||
const dataToStore = {
|
||||
...updatedData,
|
||||
// Remove file objects before saving to localStorage
|
||||
other_logos: [],
|
||||
room_booking: null,
|
||||
invoice: null,
|
||||
invoice_files: []
|
||||
};
|
||||
localStorage.setItem('eventRequestFormData', JSON.stringify(dataToStore));
|
||||
|
||||
// Also update the preview data
|
||||
window.dispatchEvent(new CustomEvent('formDataUpdated', {
|
||||
detail: { formData: updatedData }
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error saving form data to localStorage:', error);
|
||||
}
|
||||
|
||||
return updatedData;
|
||||
});
|
||||
};
|
||||
|
@ -267,8 +286,36 @@ const EventRequestForm: React.FC = () => {
|
|||
requested_user: userId,
|
||||
name: formData.name,
|
||||
location: formData.location,
|
||||
start_date_time: new Date(formData.start_date_time).toISOString(),
|
||||
end_date_time: formData.end_date_time ? new Date(formData.end_date_time).toISOString() : new Date(formData.start_date_time).toISOString(),
|
||||
start_date_time: (() => {
|
||||
try {
|
||||
const startDate = new Date(formData.start_date_time);
|
||||
if (isNaN(startDate.getTime())) {
|
||||
throw new Error('Invalid start date');
|
||||
}
|
||||
return startDate.toISOString();
|
||||
} catch (e) {
|
||||
throw new Error('Invalid start date format');
|
||||
}
|
||||
})(),
|
||||
end_date_time: (() => {
|
||||
try {
|
||||
if (formData.end_date_time) {
|
||||
const endDate = new Date(formData.end_date_time);
|
||||
if (isNaN(endDate.getTime())) {
|
||||
throw new Error('Invalid end date');
|
||||
}
|
||||
return endDate.toISOString();
|
||||
} else {
|
||||
// Fallback to start date if no end date (should not happen with validation)
|
||||
const startDate = new Date(formData.start_date_time);
|
||||
return startDate.toISOString();
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback to start date
|
||||
const startDate = new Date(formData.start_date_time);
|
||||
return startDate.toISOString();
|
||||
}
|
||||
})(),
|
||||
event_description: formData.event_description,
|
||||
flyers_needed: formData.flyers_needed,
|
||||
photography_needed: formData.photography_needed,
|
||||
|
@ -277,7 +324,14 @@ const EventRequestForm: React.FC = () => {
|
|||
itemized_invoice: formData.itemized_invoice,
|
||||
flyer_type: formData.flyer_type,
|
||||
other_flyer_type: formData.other_flyer_type,
|
||||
flyer_advertising_start_date: formData.flyer_advertising_start_date ? new Date(formData.flyer_advertising_start_date).toISOString() : '',
|
||||
flyer_advertising_start_date: formData.flyer_advertising_start_date ? (() => {
|
||||
try {
|
||||
const advertDate = new Date(formData.flyer_advertising_start_date);
|
||||
return isNaN(advertDate.getTime()) ? '' : advertDate.toISOString();
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
})() : '',
|
||||
flyer_additional_requests: formData.flyer_additional_requests,
|
||||
required_logos: formData.required_logos,
|
||||
advertising_format: formData.advertising_format,
|
||||
|
@ -407,11 +461,47 @@ const EventRequestForm: React.FC = () => {
|
|||
if (!formData.start_date_time || formData.start_date_time.trim() === '') {
|
||||
errors.push('Event start date and time is required');
|
||||
valid = false;
|
||||
} else {
|
||||
// Validate start date format
|
||||
try {
|
||||
const startDate = new Date(formData.start_date_time);
|
||||
if (isNaN(startDate.getTime())) {
|
||||
errors.push('Invalid start date and time format');
|
||||
valid = false;
|
||||
} else {
|
||||
// Check if start date is in the future
|
||||
const now = new Date();
|
||||
if (startDate <= now) {
|
||||
errors.push('Event start date must be in the future');
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
errors.push('Invalid start date and time format');
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!formData.end_date_time) {
|
||||
if (!formData.end_date_time || formData.end_date_time.trim() === '') {
|
||||
errors.push('Event end time is required');
|
||||
valid = false;
|
||||
} else if (formData.start_date_time) {
|
||||
// Validate end date format and logic
|
||||
try {
|
||||
const startDate = new Date(formData.start_date_time);
|
||||
const endDate = new Date(formData.end_date_time);
|
||||
|
||||
if (isNaN(endDate.getTime())) {
|
||||
errors.push('Invalid end date and time format');
|
||||
valid = false;
|
||||
} else if (!isNaN(startDate.getTime()) && endDate <= startDate) {
|
||||
errors.push('Event end time must be after the start time');
|
||||
valid = false;
|
||||
}
|
||||
} catch (e) {
|
||||
errors.push('Invalid end date and time format');
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!formData.location || formData.location.trim() === '') {
|
||||
|
@ -419,7 +509,7 @@ const EventRequestForm: React.FC = () => {
|
|||
valid = false;
|
||||
}
|
||||
|
||||
if (formData.will_or_have_room_booking === undefined) {
|
||||
if (formData.will_or_have_room_booking === undefined || formData.will_or_have_room_booking === null) {
|
||||
errors.push('Room booking status is required');
|
||||
valid = false;
|
||||
}
|
||||
|
|
|
@ -1644,6 +1644,11 @@ const EventRequestDetails = ({
|
|||
<label className="text-xs text-gray-400">Start Date & Time</label>
|
||||
<p className="text-white">{formatDate(request.start_date_time)}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs text-gray-400">End Date & Time</label>
|
||||
<p className="text-white">{formatDate(request.end_date_time)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -231,6 +231,50 @@ const EventRequestManagementTable = ({
|
|||
}
|
||||
};
|
||||
|
||||
// Format date and time range for display
|
||||
const formatDateTimeRange = (startDateString: string, endDateString: string) => {
|
||||
if (!startDateString) return 'Not specified';
|
||||
|
||||
try {
|
||||
const startDate = new Date(startDateString);
|
||||
const endDate = endDateString ? new Date(endDateString) : null;
|
||||
|
||||
const startFormatted = startDate.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
if (endDate && endDate.getTime() !== startDate.getTime()) {
|
||||
// Check if it's the same day
|
||||
const isSameDay = startDate.toDateString() === endDate.toDateString();
|
||||
|
||||
if (isSameDay) {
|
||||
// Same day, just show end time
|
||||
const endTime = endDate.toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
return `${startFormatted} - ${endTime}`;
|
||||
} else {
|
||||
// Different day, show full end date
|
||||
const endFormatted = endDate.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
return `${startFormatted} - ${endFormatted}`;
|
||||
}
|
||||
}
|
||||
|
||||
return startFormatted;
|
||||
} catch (e) {
|
||||
return startDateString;
|
||||
}
|
||||
};
|
||||
|
||||
// Get status badge class based on status
|
||||
const getStatusBadge = (status?: "submitted" | "pending" | "completed" | "declined") => {
|
||||
if (!status) return 'badge-warning';
|
||||
|
@ -489,7 +533,7 @@ const EventRequestManagementTable = ({
|
|||
height: "auto"
|
||||
}}
|
||||
>
|
||||
<table className="table table-zebra w-full">
|
||||
<table className="table table-zebra w-full min-w-[600px]">
|
||||
<thead className="bg-base-300/50 sticky top-0 z-10">
|
||||
<tr>
|
||||
<th
|
||||
|
@ -510,7 +554,7 @@ const EventRequestManagementTable = ({
|
|||
onClick={() => handleSortChange('start_date_time')}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
Date
|
||||
Date & Time
|
||||
{sortField === 'start_date_time' && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={sortDirection === 'asc' ? "M5 15l7-7 7 7" : "M19 9l-7 7-7-7"} />
|
||||
|
@ -559,7 +603,7 @@ const EventRequestManagementTable = ({
|
|||
)}
|
||||
</div>
|
||||
</th>
|
||||
<th>Actions</th>
|
||||
<th className="w-20 min-w-[5rem]">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -570,7 +614,11 @@ const EventRequestManagementTable = ({
|
|||
{truncateText(request.name, 30)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="hidden md:table-cell">{formatDate(request.start_date_time)}</td>
|
||||
<td className="hidden md:table-cell">
|
||||
<div className="text-sm">
|
||||
{formatDateTimeRange(request.start_date_time, request.end_date_time)}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{(() => {
|
||||
const { name, email } = getUserDisplayInfo(request);
|
||||
|
@ -603,16 +651,17 @@ const EventRequestManagementTable = ({
|
|||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
className="btn btn-sm btn-primary btn-outline btn-sm gap-2"
|
||||
className="btn btn-sm btn-primary btn-outline gap-1 min-h-[2rem] max-w-[5rem] flex-shrink-0"
|
||||
onClick={() => openDetailModal(request)}
|
||||
title="View Event Details"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
View
|
||||
<span className="hidden sm:inline">View</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
|
Loading…
Reference in a new issue