Forced merge: merge ai-dev into master
This commit is contained in:
commit
00ad62f916
@ -390,25 +390,18 @@ module.exports = class EventsDBApi {
|
||||
|
||||
if (filter.start_datetimeRange) {
|
||||
const [start, end] = filter.start_datetimeRange;
|
||||
const rangeCondition = {};
|
||||
|
||||
if (start !== undefined && start !== null && start !== '') {
|
||||
where = {
|
||||
...where,
|
||||
start_datetime: {
|
||||
...where.start_datetime,
|
||||
[Op.gte]: start,
|
||||
},
|
||||
};
|
||||
rangeCondition[Op.gte] = start;
|
||||
}
|
||||
|
||||
if (end !== undefined && end !== null && end !== '') {
|
||||
where = {
|
||||
...where,
|
||||
start_datetime: {
|
||||
...where.start_datetime,
|
||||
[Op.lte]: end,
|
||||
},
|
||||
};
|
||||
rangeCondition[Op.lte] = end;
|
||||
}
|
||||
|
||||
if (Object.keys(rangeCondition).length > 0) {
|
||||
where.start_datetime = { ...where.start_datetime, ...rangeCondition };
|
||||
}
|
||||
}
|
||||
|
||||
@ -533,6 +526,14 @@ module.exports = class EventsDBApi {
|
||||
|
||||
|
||||
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
if (currentUser && currentUser.organizationId && (!currentUser.app_role || !currentUser.app_role.globalAccess)) {
|
||||
where = {
|
||||
...where,
|
||||
organizationId: currentUser.organizationId,
|
||||
};
|
||||
}
|
||||
|
||||
const queryOptions = {
|
||||
where,
|
||||
include,
|
||||
@ -541,7 +542,6 @@ module.exports = class EventsDBApi {
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['createdAt', 'desc']],
|
||||
transaction: options?.transaction,
|
||||
logging: console.log
|
||||
};
|
||||
|
||||
if (!options?.countOnly) {
|
||||
|
||||
@ -15,430 +15,11 @@ const {
|
||||
checkCrudPermissions,
|
||||
} = require('../middlewares/check-permissions');
|
||||
|
||||
router.get('/public', wrapAsync(async (req, res) => {
|
||||
const payload = await EventsDBApi.findAll(req.query);
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.use(checkCrudPermissions('events'));
|
||||
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Events:
|
||||
* type: object
|
||||
* properties:
|
||||
|
||||
* name:
|
||||
* type: string
|
||||
* default: name
|
||||
* description:
|
||||
* type: string
|
||||
* default: description
|
||||
|
||||
|
||||
* budget_total:
|
||||
* type: integer
|
||||
* format: int64
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Events
|
||||
* description: The Events managing API
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/events:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Events]
|
||||
* summary: Add new item
|
||||
* description: Add new item
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
* description: Invalid input data
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post('/', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await EventsService.create(req.body.data, req.currentUser, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/budgets/bulk-import:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Events]
|
||||
* summary: Bulk import items
|
||||
* description: Bulk import items
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* data:
|
||||
* description: Data of the updated items
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items were successfully imported
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
* description: Invalid input data
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*
|
||||
*/
|
||||
router.post('/bulk-import', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await EventsService.bulkImport(req, res, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/events/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Events]
|
||||
* summary: Update the data of the selected item
|
||||
* description: Update the data of the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to update
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* requestBody:
|
||||
* description: Set new item data
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* id:
|
||||
* description: ID of the updated item
|
||||
* type: string
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item data was successfully updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.put('/:id', wrapAsync(async (req, res) => {
|
||||
await EventsService.update(req.body.data, req.body.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/events/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Events]
|
||||
* summary: Delete the selected item
|
||||
* description: Delete the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to delete
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.delete('/:id', wrapAsync(async (req, res) => {
|
||||
await EventsService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/events/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Events]
|
||||
* summary: Delete the selected item list
|
||||
* description: Delete the selected item list
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* ids:
|
||||
* description: IDs of the updated items
|
||||
* type: array
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Items not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post('/deleteByIds', wrapAsync(async (req, res) => {
|
||||
await EventsService.deleteByIds(req.body.data, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/events:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Events]
|
||||
* summary: Get all events
|
||||
* description: Get all events
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Events list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/', wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype
|
||||
|
||||
const currentUser = req.currentUser;
|
||||
|
||||
const { sort, ...otherFilters } = req.query;
|
||||
let order = {};
|
||||
if (sort) {
|
||||
const [field, direction] = sort.split(',');
|
||||
order = { field, sort: direction };
|
||||
}
|
||||
|
||||
const payload = await EventsDBApi.findAll(
|
||||
{ ...otherFilters, ...order }, { currentUser }
|
||||
);
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id','name','description',
|
||||
|
||||
'budget_total',
|
||||
'start_datetime','end_datetime',
|
||||
];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
res.status(200).attachment(csv);
|
||||
res.send(csv)
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
} else {
|
||||
res.status(200).send(payload);
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/events/count:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Events]
|
||||
* summary: Count all events
|
||||
* description: Count all events
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Events count successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/count', wrapAsync(async (req, res) => {
|
||||
|
||||
const currentUser = req.currentUser;
|
||||
const payload = await EventsDBApi.findAll(
|
||||
req.query,
|
||||
null,
|
||||
{ countOnly: true, currentUser }
|
||||
);
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/events/autocomplete:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Events]
|
||||
* summary: Find all events that match search criteria
|
||||
* description: Find all events that match search criteria
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Events list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const payload = await EventsDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
|
||||
);
|
||||
|
||||
res.status(200).send(payload);
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/events/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Events]
|
||||
* summary: Get selected item
|
||||
* description: Get selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: ID of item to get
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Selected item successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Events"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await EventsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
);
|
||||
|
||||
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
25
frontend/src/components/Events/EventCard.tsx
Normal file
25
frontend/src/components/Events/EventCard.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Event } from '../../interfaces/event';
|
||||
|
||||
const EventCard = ({ event }: { event: Event }) => {
|
||||
const { name, description, start_datetime } = event;
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-lg overflow-hidden transform hover:scale-105 transition-transform duration-300">
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-semibold mb-2">{name}</h3>
|
||||
<p className="text-gray-600 mb-4">{description}</p>
|
||||
<div className="text-sm text-gray-500">
|
||||
{new Date(start_datetime).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventCard;
|
||||
@ -46,7 +46,7 @@ const InteractiveBackground = () => {
|
||||
default: 'bounce',
|
||||
},
|
||||
random: false,
|
||||
speed: 2,
|
||||
speed: 0.5,
|
||||
straight: false,
|
||||
},
|
||||
number: {
|
||||
|
||||
@ -7,6 +7,11 @@ const menuAside: MenuAsideItem[] = [
|
||||
icon: icon.mdiViewDashboardOutline,
|
||||
label: 'Dashboard',
|
||||
},
|
||||
{
|
||||
href: '/public-events',
|
||||
icon: icon.mdiCalendar,
|
||||
label: 'Public Events',
|
||||
},
|
||||
|
||||
{
|
||||
href: '/users/users-list',
|
||||
|
||||
@ -96,7 +96,9 @@ const Dashboard = () => {
|
||||
events.map((event) => (
|
||||
<tr key={event.id}>
|
||||
<td className="px-6 py-4 whitespace-nowrap">{event.name}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">{new Date(event.start_datetime).toLocaleDateString()}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
{new Date(event.start_datetime).toLocaleDateString()}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">{event.location}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import axios from 'axios';
|
||||
import BaseButton from '../components/BaseButton';
|
||||
import LayoutGuest from '../layouts/Guest';
|
||||
import { getPageTitle } from '../config';
|
||||
import SectionTitle from '../components/SectionTitle';
|
||||
import EventCard from '../components/Events/EventCard';
|
||||
import { Event } from '../interfaces/event';
|
||||
|
||||
const Feature = ({ icon, title, text }) => (
|
||||
<div className="p-6 text-center bg-white rounded-lg shadow-lg border border-gray-200">
|
||||
@ -18,6 +21,24 @@ const Feature = ({ icon, title, text }) => (
|
||||
|
||||
export default function LandingPage() {
|
||||
const title = 'EventCoord Hub'
|
||||
const [events, setEvents] = useState<Event[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchEvents = async () => {
|
||||
try {
|
||||
const response = await axios.get(`/api/events/public`);
|
||||
setEvents(response.data.rows);
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchEvents();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="text-gray-800">
|
||||
@ -79,6 +100,34 @@ export default function LandingPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Upcoming Events Section */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-6">
|
||||
<h2 className="text-4xl font-bold text-center mb-12">Upcoming Events</h2>
|
||||
{loading && <p className="text-center">Loading events...</p>}
|
||||
{error && <p className="text-center text-red-500">Error loading events. Please try again later.</p>}
|
||||
{!loading && !error && (
|
||||
<>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 mt-12">
|
||||
{events.map((event) => (
|
||||
<EventCard key={event.id} event={event} />
|
||||
))}
|
||||
</div>
|
||||
<div className="text-center mt-12">
|
||||
<BaseButton
|
||||
href="/public-events"
|
||||
label="View All Events"
|
||||
color="info"
|
||||
outline
|
||||
roundedFull
|
||||
className="px-8 py-3 text-lg"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className='text-black'>
|
||||
<div className='container mx-auto px-6 py-8 flex flex-col md:flex-row justify-between items-center'>
|
||||
|
||||
38
frontend/src/pages/public-events.tsx
Normal file
38
frontend/src/pages/public-events.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import axios from 'axios'
|
||||
import LayoutGuest from '../layouts/Guest'
|
||||
|
||||
const PublicEventsPage = () => {
|
||||
const [events, setEvents] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
const fetchEvents = async () => {
|
||||
try {
|
||||
const response = await axios.get('/api/events/public')
|
||||
setEvents(response.data.rows)
|
||||
} catch (error) {
|
||||
console.error('Error fetching events:', error)
|
||||
}
|
||||
}
|
||||
|
||||
fetchEvents()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<LayoutGuest>
|
||||
<div className="container mx-auto py-8">
|
||||
<h1 className="text-3xl font-bold mb-4">Public Events</h1>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{events.map((event: any) => (
|
||||
<div key={event.id} className="border rounded-lg p-4">
|
||||
<h2 className="text-xl font-bold">{event.name}</h2>
|
||||
<p>{event.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</LayoutGuest>
|
||||
)
|
||||
}
|
||||
|
||||
export default PublicEventsPage
|
||||
Loading…
x
Reference in New Issue
Block a user