import React, { useState, useEffect, useReducer } from 'react';
import { Link, useParams } from 'react-router-dom';
import './light.css';
import { Label, Button, Container, Dropdown, Form, Header, Icon, Input, Segment, Table } from 'semantic-ui-react';
import moment from 'moment-timezone';
import { apiUrl, isAdmin, getInstructor, BasicTable, requester, useIsMobile } from './utils.js';
import { NotFound } from './Misc.js';
import { InstructorClassDetail, InstructorClassAttendance } from './InstructorClasses.js';
import { PayPalPayNow } from './PayPal.js';
import { PayWithProtocoin } from './Paymaster.js';
import { tags } from './Courses.js';
function ClassTable(props) {
const { classes } = props;
const isMobile = useIsMobile();
const now = new Date().toISOString();
return (isMobile ?
{classes.length ?
classes.map(x =>
{x.course_data.name}
Date:
{moment.utc(x.datetime).tz('America/Edmonton').format('ll')}
Time: {x.is_cancelled ? 'Cancelled' : moment.utc(x.datetime).tz('America/Edmonton').format('LT')}
Cost: {x.cost === '0.00' ? 'Free' : '$'+x.cost}
Students: {!!x.max_students ?
x.max_students <= x.student_count ?
'Full'
:
x.student_count + ' / ' + x.max_students
:
x.student_count
}
Instructor: {getInstructor(x)}
)
:
None
}
:
Name
Date
Time
Instructor
Cost
Students
{classes.length ?
classes.map(x =>
{x.course_data.name}
{moment.utc(x.datetime).tz('America/Edmonton').format('ll')}
{x.is_cancelled ? 'Cancelled' : moment.utc(x.datetime).tz('America/Edmonton').format('LT')}
{getInstructor(x)}
{x.cost === '0.00' ? 'Free' : '$'+x.cost}
{!!x.max_students ?
x.max_students <= x.student_count ?
'Full'
:
x.student_count + ' / ' + x.max_students
:
x.student_count
}
)
:
None
}
);
};
function NewClassTableCourse(props) {
const {course, classes, token, user, refreshUser} = props;
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [interested, setInterested] = useState(course.num_interested || 0);
const handleInterest = () => {
if (loading) return;
setLoading(true);
const data = { course: course.id };
requester('/interest/', 'POST', token, data)
.then(res => {
setError(false);
refreshUser();
setInterested(interested+1);
})
.catch(err => {
console.log(err);
setError(true);
});
};
const now = new Date().toISOString();
return (
{!!course.tags && course.tags.split(',').map(name =>
)}
{user &&
{user.interests.filter(x => !x.satisfied_by).map(x => x.course).includes(course.id) ?
:
}
}
{error && Error.
}
{classes ?
Date
Cost
Students
{classes.map(x =>
{moment.utc(x.datetime).tz('America/Edmonton').format(' MMM Do')}
{' - '}{x.is_cancelled ? 'Cancelled' : moment.utc(x.datetime).tz('America/Edmonton').format('LT')}
{x.cost === '0.00' ? 'Free' : '$'+x.cost.slice(0,-3)}
{!!x.max_students ?
x.max_students <= x.student_count ?
'Full'
:
x.student_count + ' / ' + x.max_students
:
x.student_count
}
)}
:
<>
No upcoming classes.
>
}
);
}
function NewClassTable(props) {
const { classes, courses, token, user, refreshUser } = props;
let sortedClasses = [];
let seenCourseIds = [];
if (classes.length && courses.length) {
for (const clazz of classes) {
const course_data = clazz.course_data;
const course = sortedClasses.find(x => x?.course?.id === course_data?.id);
if (course) {
course.classes.push(clazz);
} else {
sortedClasses.push({
course: courses.find(x => x.id === course_data.id),
classes: [clazz],
});
seenCourseIds.push(
course_data.id
);
}
}
}
return (
<>
{sortedClasses.map(x =>
)}
{courses.filter(x => !seenCourseIds.includes(x.id)).map(x =>
)}
>
);
};
let classesCache = false;
let sortCache = true;
let tagFilterCache = false;
export function ClassFeed(props) {
const [classes, setClasses] = useState(classesCache);
useEffect(() => {
const get = async() => {
requester('/sessions/', 'GET', '')
.then(res => {
setClasses(res.results);
classesCache = res.results;
})
.catch(err => {
console.log(err);
});
};
get();
const interval = setInterval(get, 60000);
return () => clearInterval(interval);
}, []);
const now = new Date().toISOString();
return (
Upcoming Protospace Classes
{classes ?
x.datetime > now).sort((a, b) => a.datetime > b.datetime ? 1 : -1)} />
:
Loading...
}
);
};
export function Classes(props) {
const [classes, setClasses] = useState(classesCache);
const [courses, setCourses] = useState(false);
const [search, setSearch] = useState('');
const [sortByCourse, setSortByCourse] = useState(sortCache);
const [tagFilter, setTagFilter] = useState(tagFilterCache);
const { token, user, refreshUser } = props;
useEffect(() => {
requester('/courses/', 'GET', token)
.then(res => {
setCourses(res.results);
})
.catch(err => {
console.log(err);
});
}, []);
useEffect(() => {
requester('/sessions/', 'GET', token)
.then(res => {
setClasses(res.results);
classesCache = res.results;
})
.catch(err => {
console.log(err);
});
}, []);
const byTeaching = (x) => x.instructor_id === user.member.id;
const byDate = (a, b) => a.datetime > b.datetime ? 1 : -1;
const classesByTag = (x) => tagFilter ? x.course_data.tags.includes(tagFilter) : true;
const coursesByTag = (x) => tagFilter ? x.tags.includes(tagFilter) : true;
const classesBySearch = (x) => search ? x.course_data.name.toLowerCase().includes(search.toLowerCase()) : true;
const coursesBySearch = (x) => search ? x.name.toLowerCase().includes(search.toLowerCase()) : true;
return (
View the list of all courses.
{!!user && !!classes.length && !!classes.filter(byTeaching).length &&
<>
>
}
setSearch(event.target.value)}
aria-label='search products'
style={{ margin: '0.5rem 0.5rem 0.5rem 0' }}
/>
{!!search.length &&
Filter by tag:
{Object.entries(tags).map(([name, color]) =>
)}
{classes.length && courses.length ?
sortByCourse ?
:
:
Loading...
}
);
};
export function ICalButtons(props) {
const { token, clazz } = props;
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);
const handleDownload = (e) => {
e.preventDefault();
window.location = apiUrl + '/sessions/' + clazz.id + '/download_ical/';
}
const handleEmail = (e) => {
e.preventDefault();
if (loading) return;
setLoading(true);
setSuccess(false);
requester('/sessions/' + clazz.id + '/email_ical/', 'POST', token, {})
.then(res => {
setLoading(false);
setSuccess(true);
})
.catch(err => {
setLoading(false);
console.log(err);
setError(true);
});
};
const addToGoogleCalendar = (e) => {
e.preventDefault();
// construct and set the dates format that google calendar links require
let starttime = moment(clazz.datetime);
let endtime = starttime.clone().add(1, 'hour');
const datestringfmt = 'YYYYMMDDTkkmmss';
let dates = `${starttime.format(datestringfmt)}/${endtime.format(datestringfmt)}`
// send user to google calendar
window.location = `https://www.google.com/calendar/render?action=TEMPLATE&text=${clazz.course_data.name}&dates=${dates}`;
};
const options = [
{ key: 'email', icon: 'mail outline', text: 'Email ICS Event', value: 'Email', action: handleEmail },
{ key: 'download', icon: 'download', text: 'Download ICS Event', value: 'Download', action: handleDownload },
{ key: 'google', icon: 'google', text: 'Add to Google Calendar', value: 'Google', action: addToGoogleCalendar },
];
// get default option from local storage or default to first item in options list
const calendarValue = localStorage.getItem('calendarPreference') || 'Email';
const defaultOption = options.find(x => x.value === calendarValue);
const [selectedOption, setOption] = useState(defaultOption);
const onChange = (e, data) => {
const newOption = options.find(x => x.value === data.value);
setOption(newOption);
// set the option as users preference
localStorage.setItem('calendarPreference', newOption.value);
};
return (
<>
{success ?
Sent!
:
>}
selectOnBlur={false}
/>
}
{error && Error.
}
>
);
};
export function ClassDetail(props) {
const [clazz, setClass] = useState(false);
const [refreshCount, refreshClass] = useReducer(x => x + 1, 0);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [override, setOverride] = useState(false);
const { token, user, refreshUser } = props;
const { id } = useParams();
const userTraining = clazz && clazz.students.find(x => x.user === user.id);
useEffect(() => {
requester('/sessions/'+id+'/', 'GET', token)
.then(res => {
setClass(res);
})
.catch(err => {
console.log(err);
setError(true);
});
}, [refreshCount]);
const handleSignup = () => {
if (loading) return;
setLoading(true);
const data = { attendance_status: 'Waiting for payment', session: id };
requester('/training/', 'POST', token, data)
.then(res => {
refreshClass();
refreshUser();
})
.catch(err => {
console.log(err);
});
};
const handleToggle = (newStatus) => {
if (loading) return;
setLoading(true);
const data = { attendance_status: newStatus, session: id };
requester('/training/'+userTraining.id+'/', 'PUT', token, data)
.then(res => {
refreshClass();
refreshUser();
})
.catch(err => {
console.log(err);
setError(true);
});
};
useEffect(() => {
setLoading(false);
}, [userTraining]);
const now = new Date().toISOString();
const signupDisabled = clazz && clazz.datetime < now && !override;
return (
{!error ?
clazz ?
{(isAdmin(user) || clazz.instructor === user.id) &&
}
Name:
{clazz.course_data.name}
Date:
{moment.utc(clazz.datetime).tz('America/Edmonton').format('ll')}
Time:
{clazz.is_cancelled ? 'Cancelled' : moment.utc(clazz.datetime).tz('America/Edmonton').format('LT')}
Instructor:
{getInstructor(clazz)}
Cost:
{clazz.cost === '0.00' ? 'Free' : '$'+clazz.cost}
Students:
{clazz.student_count} {!!clazz.max_students && '/ '+clazz.max_students}
Event:
{clazz.course_data.is_old ?
clazz.course_data.description.split('\n').map((x, i) =>
{x}
)
:
}
{(isAdmin(user) || clazz.instructor === user.id) &&
}
{clazz.instructor !== user.id &&
(userTraining ?
Status: {userTraining.attendance_status}
{userTraining.attendance_status === 'Withdrawn' ?
:
}
{clazz.cost !== '0.00' && !userTraining.paid_date && userTraining.attendance_status !== 'Withdrawn' &&
{userTraining.attendance_status === 'Waiting for payment' ?
Please pay the course fee of ${clazz.cost} to confirm your attendance:
:
In case you haven't paid the course fee of ${clazz.cost} yet, you can do that here:
}
Current balance: ₱ {user.member.protocoin.toFixed(2)}
{
refreshUser();
refreshClass();
}}
custom={{ category: 'OnAcct', training: userTraining.id }}
/>
}
:
(clazz.is_cancelled ?
The class is cancelled.
:
((clazz.max_students && clazz.student_count >= clazz.max_students) ?
The class is full.
:
<>
{clazz.datetime < now &&
<>
This class has already ran.
setOverride(v.checked)}
/>
>
}
>
)
)
)
}
:
Loading...
:
}
);
};