├── .eslintignore ├── Dockerfile ├── .prettierignore ├── static ├── favicon.ico ├── images │ ├── cms_logo.png │ └── icons │ │ ├── icon-72x72.png │ │ ├── icon-96x96.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ └── icon-512x512.png └── manifest.json ├── .gitignore ├── .prettierrc ├── components ├── admin │ ├── useModal.js │ ├── alertComponent.js │ └── addRemoveCard.js ├── loadingScreen.js ├── breadCrumbGenerator.js ├── titlebar.js ├── header.js ├── reset-password │ └── resetForm.js ├── authRequired.js ├── links.js ├── login │ └── loginForm.js ├── base.js ├── event │ └── eventForm.js ├── account │ ├── changePassword.js │ └── basicSettings.js ├── register │ └── registerForm.js ├── sidebar.js └── blog │ └── addBlog.js ├── pages ├── _app.js ├── index.js ├── _document.js ├── register.js ├── login.js ├── reset-password.js ├── calendar │ └── create-event.js ├── logout.js ├── attendance │ ├── stats.js │ ├── live-report.js │ ├── dashboard.js │ └── daily-report.js ├── status-updates │ ├── stats.js │ ├── daily-report.js │ ├── messages.js │ └── individual-report.js ├── blog │ └── create-blog.js ├── 404.js ├── account │ └── settings.js ├── form │ └── [id] │ │ ├── index.js │ │ └── entries.js ├── events │ └── check-in.js ├── forms │ └── view-forms.js └── admin │ └── manage-users.js ├── .gitlab-ci.yml ├── README.md ├── utils ├── queries.js ├── fileUpload.js ├── mutations.js └── dataFetch.js ├── .eslintrc ├── next.config.js ├── modules ├── forms │ ├── formFields.js │ ├── entryDetails.js │ └── fieldEditor.js ├── attendance │ └── components │ │ ├── ActiveStatusBar.js │ │ ├── LiveReportCard.js │ │ ├── Overview.js │ │ ├── TrendGraph.js │ │ ├── DailyReportCard.js │ │ ├── TrendAttendanceGraph.js │ │ └── Rankings.js └── statusUpdates │ └── components │ ├── DailyStatusReportCard.js │ ├── Overview.js │ ├── StatusGraph.js │ ├── TrendStatusGraph.js │ └── Ranking.js ├── styles └── styles.sass └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | EXPOSE 3000 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | .next/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /static/images/cms_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/cms_logo.png -------------------------------------------------------------------------------- /static/images/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-72x72.png -------------------------------------------------------------------------------- /static/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /static/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /static/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /static/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /static/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /static/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /static/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/web-app/HEAD/static/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | node_modules 4 | .idea 5 | .next 6 | out 7 | package-lock.json 8 | public/workbox-*.js 9 | public/workbox-*.js.* 10 | public/sw.js.* 11 | public/sw.js -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 85, 4 | "arrowParens": "always", 5 | "semi": true, 6 | "tabWidth": 2, 7 | "bracketSpacing": true, 8 | "endOfLine": "auto" 9 | } -------------------------------------------------------------------------------- /components/admin/useModal.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | const useModal = () => { 4 | const [show, setShow] = useState(false); 5 | 6 | function toggle() { 7 | setShow(!show); 8 | } 9 | 10 | return { 11 | show, 12 | toggle, 13 | }; 14 | }; 15 | 16 | export default useModal; 17 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import '../styles/styles.sass'; 4 | import 'bootstrap/dist/css/bootstrap.min.css'; 5 | import 'react-quill/dist/quill.snow.css'; 6 | import 'antd/dist/antd.min.css'; 7 | import 'react-markdown-editor-lite/lib/index.css'; 8 | 9 | function MyApp({ Component, pageProps }) { 10 | return ; 11 | } 12 | 13 | export default MyApp; 14 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: registry.gitlab.com/harshithpabbati/webapp:latest 2 | 3 | cache: 4 | key: ${CI_COMMIT_REF_SLUG} 5 | paths: 6 | - node_modules/ 7 | - .next/cache/ 8 | 9 | pages: 10 | stage: deploy 11 | script: 12 | - npm install 13 | - npm run export 14 | - rm -rf public 15 | - mv out public 16 | 17 | artifacts: 18 | paths: 19 | - public # mandatory, other folder won't work 20 | only: 21 | - master 22 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Base from '../components/base'; 3 | import TitleBar from '../components/titlebar'; 4 | 5 | const Index = () => { 6 | const routes = [ 7 | { 8 | path: '/', 9 | name: 'Home', 10 | }, 11 | ]; 12 | 13 | return ( 14 |
15 | 16 | 17 | 18 |
19 | ); 20 | }; 21 | 22 | export default Index; 23 | -------------------------------------------------------------------------------- /components/loadingScreen.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactLoading from 'react-loading'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const LoadingScreen = ({ text }) => { 6 | return ( 7 |
8 | 9 |
{text}
10 |
11 | ); 12 | }; 13 | 14 | LoadingScreen.propTypes = { 15 | text: PropTypes.string, 16 | }; 17 | 18 | export default LoadingScreen; 19 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Document, { Html, Head, Main, NextScript } from 'next/document'; 3 | 4 | class MyDocument extends Document { 5 | static async getInitialProps(ctx) { 6 | const initialProps = await Document.getInitialProps(ctx); 7 | return { ...initialProps }; 8 | } 9 | 10 | render() { 11 | return ( 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | ); 20 | } 21 | } 22 | 23 | export default MyDocument; 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebApp 2 | 3 | [![pipeline status](https://gitlab.com/amfoss/WebApp/badges/master/pipeline.svg)](https://gitlab.com/amfoss/WebApp/-/commits/master) 4 | 5 | Webapp for FOSS@Amrita 6 | 7 | #### NPM Commands 8 | * ```npm install``` - install all node modules (dependencies) to run the app 9 | * ``npm run dev`` - runs the app in development mode in port 3000 10 | * ```npm run build``` - builds the app for production 11 | 12 | #### Tech Stack 13 | * Next / React / JSX / SASS 14 | * **Linting & Quality:** ESlint (ESlin Airbnb Config), Prettier 15 | * **UI Library:** Ant Design 16 | -------------------------------------------------------------------------------- /utils/queries.js: -------------------------------------------------------------------------------- 1 | export const clubAttendance = ` 2 | query{ 3 | clubAttendance{ 4 | dailyLog{ 5 | avgDuration 6 | date 7 | membersPresent 8 | members{ 9 | user{ 10 | username 11 | firstName 12 | lastName 13 | } 14 | duration 15 | start 16 | end 17 | } 18 | } 19 | } 20 | }`; 21 | 22 | export const getApplicant = ` 23 | query getApplicant($hash: String!){ 24 | getApplicant(hash: $hash){ 25 | id 26 | name 27 | formData{ 28 | key 29 | value 30 | } 31 | } 32 | } 33 | `; 34 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "plugins": [ 9 | "react", 10 | "react-hooks" 11 | ], 12 | "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:prettier/recommended"], 13 | "parserOptions": { 14 | "sourceType": "module", 15 | "ecmaVersion": 2018 16 | }, 17 | "settings": { 18 | "react": { 19 | "version": "detect" 20 | } 21 | }, 22 | "rules": { 23 | "linebreak-style": ["error", "unix"], 24 | "react-hooks/rules-of-hooks": "error", 25 | "react-hooks/exhaustive-deps": "warn" 26 | } 27 | } -------------------------------------------------------------------------------- /components/breadCrumbGenerator.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | import PropTypes from 'prop-types'; 4 | 5 | // antd components 6 | import Breadcrumb from 'antd/lib/breadcrumb'; 7 | 8 | const BreadCrumbGenerator = ({ routes }) => { 9 | return ( 10 | 11 | {routes 12 | ? routes.map((r, i) => ( 13 | 14 | {r.name} 15 | 16 | )) 17 | : null} 18 | 19 | ); 20 | }; 21 | 22 | BreadCrumbGenerator.propTypes = { 23 | routes: PropTypes.array, 24 | }; 25 | 26 | export default BreadCrumbGenerator; 27 | -------------------------------------------------------------------------------- /pages/register.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import RegisterForm from '../components/register/registerForm'; 4 | import Header from '../components/header'; 5 | 6 | const Register = () => { 7 | return ( 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 | 21 | ); 22 | }; 23 | 24 | export default Register; 25 | -------------------------------------------------------------------------------- /pages/login.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import LoginForm from '../components/login/loginForm'; 4 | import Header from '../components/header'; 5 | 6 | const Login = ({ lastLocation }) => { 7 | return ( 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 | 21 | ); 22 | }; 23 | 24 | export default Login; 25 | -------------------------------------------------------------------------------- /pages/reset-password.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ResetForm from '../components/reset-password/resetForm'; 4 | import Header from '../components/header'; 5 | 6 | const ResetPassword = () => { 7 | return ( 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 | 21 | ); 22 | }; 23 | 24 | export default ResetPassword; 25 | -------------------------------------------------------------------------------- /utils/fileUpload.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import Cookies from 'universal-cookie'; 3 | 4 | const cookies = new Cookies(); 5 | const API_URL = 'https://api.amfoss.in/'; 6 | 7 | export default ({ data }) => { 8 | const token = cookies.get('token'); 9 | return axios({ 10 | method: 'post', 11 | url: API_URL, 12 | data, 13 | headers: { 14 | Authorization: token ? `JWT ${token}` : null, 15 | }, 16 | config: { 17 | headers: { 18 | 'Content-Tranfer-Encoding': 'multipart/form-data', 19 | 'Content-Type': 'application/graphql', 20 | }, 21 | }, 22 | }) 23 | .then(function (response) { 24 | return response.data; 25 | }) 26 | .catch(function (response) { 27 | throw response; 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /pages/calendar/create-event.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import TitleBar from '../../components/titlebar'; 4 | import Base from '../../components/base'; 5 | import EventForm from '../../components/event/eventForm'; 6 | 7 | const CreateEvent = (props) => { 8 | const routes = [ 9 | { 10 | path: '/', 11 | name: 'Home', 12 | }, 13 | { 14 | path: '/calendar', 15 | name: 'Calendar', 16 | }, 17 | { 18 | path: '/calendar/create-event', 19 | name: 'Create Event', 20 | }, 21 | ]; 22 | return ( 23 | 24 | 25 | 26 | 27 | ); 28 | }; 29 | 30 | export default CreateEvent; 31 | -------------------------------------------------------------------------------- /pages/logout.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Cookies from 'universal-cookie'; 3 | import { useRouter } from 'next/router'; 4 | 5 | import LoadingScreen from '../components/loadingScreen'; 6 | 7 | const cookies = new Cookies(); 8 | 9 | const LogoutPage = () => { 10 | const router = useRouter(); 11 | const [loggedOut, setLoggedOut] = useState(false); 12 | 13 | useEffect(() => { 14 | cookies.remove('token'); 15 | cookies.remove('refreshToken'); 16 | cookies.remove('username'); 17 | cookies.remove('expiry'); 18 | if (!loggedOut) { 19 | setLoggedOut(true); 20 | router.push('/login'); 21 | } 22 | }); 23 | 24 | return ; 25 | }; 26 | 27 | export default LogoutPage; 28 | -------------------------------------------------------------------------------- /pages/attendance/stats.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Base from '../../components/base'; 4 | import Overview from '../../modules/attendance/components/Overview'; 5 | import TitleBar from '../../components/titlebar'; 6 | 7 | const Stats = (props) => { 8 | const routes = [ 9 | { 10 | path: '/', 11 | name: 'Home', 12 | }, 13 | { 14 | path: '/attendance', 15 | name: 'Attendance', 16 | }, 17 | { 18 | path: '/attendance-stats', 19 | name: 'Attendance Statistics', 20 | }, 21 | ]; 22 | return ( 23 | 24 | 25 |
26 | 27 |
28 | 29 | ); 30 | }; 31 | 32 | export default Stats; 33 | -------------------------------------------------------------------------------- /pages/status-updates/stats.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Base from '../../components/base'; 4 | import Overview from '../../modules/statusUpdates/components/Overview'; 5 | import TitleBar from '../../components/titlebar'; 6 | 7 | const StatusStats = (props) => { 8 | const routes = [ 9 | { 10 | path: '/', 11 | name: 'Home', 12 | }, 13 | { 14 | path: '/status', 15 | name: 'Status Update', 16 | }, 17 | { 18 | path: '/status-stats', 19 | name: 'Status Update Statistics', 20 | }, 21 | ]; 22 | return ( 23 | 24 | 25 |
26 | 27 |
28 | 29 | ); 30 | }; 31 | 32 | export default StatusStats; 33 | -------------------------------------------------------------------------------- /pages/blog/create-blog.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TitleBar from '../../components/titlebar'; 3 | import Base from '../../components/base'; 4 | import AddBlog from '../../components/blog/addBlog'; 5 | 6 | import Card from 'antd/lib/card'; 7 | 8 | const CreateBlog = (props) => { 9 | const routes = [ 10 | { 11 | path: '/', 12 | name: 'Home', 13 | }, 14 | { 15 | path: '/blog', 16 | name: 'Blog', 17 | }, 18 | { 19 | path: '/blog/create-blog', 20 | name: 'Create Blog', 21 | }, 22 | ]; 23 | 24 | return ( 25 | 26 | 27 |
28 | 29 | 30 | 31 |
32 | 33 | ); 34 | }; 35 | 36 | export default CreateBlog; 37 | -------------------------------------------------------------------------------- /pages/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Router from 'next/router'; 3 | 4 | // antd components 5 | import Result from 'antd/lib/result'; 6 | import Button from 'antd/lib/button'; 7 | 8 | import Base from '../components/base'; 9 | 10 | export default function Page404(props) { 11 | return ( 12 | 13 |
17 | Router.push('/')}> 23 | Back Home 24 | 25 | } 26 | /> 27 |
28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /utils/mutations.js: -------------------------------------------------------------------------------- 1 | export const TokenAuth = ` 2 | mutation TokenAuth($username: String!, $password: String!) { 3 | tokenAuth(username: $username, password: $password) { 4 | token 5 | refreshToken 6 | payload 7 | refreshExpiresIn 8 | } 9 | }`; 10 | 11 | export const CheckIn = ` 12 | mutation CheckIn($appID: Int!){ 13 | checkIn(appID: $appID){ 14 | status 15 | } 16 | } 17 | `; 18 | 19 | export const Register = `mutation ($email: String!, $username: String!, $password: String!){ 20 | createUser(email: $email, username: $username, password: $password){ 21 | user{ 22 | id 23 | } 24 | } 25 | }`; 26 | 27 | export const verifyUserMutation = `mutation ($usernames: [String]){ 28 | approveUsers(usernames: $usernames){ 29 | status 30 | } 31 | }`; 32 | 33 | export const addToPlatformMutation = `mutation ($usernames: [String], $platform: String!){ 34 | addToPlatform(usernames: $usernames, platform: $platform){ 35 | status 36 | } 37 | }`; 38 | -------------------------------------------------------------------------------- /components/titlebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | // antd components 5 | import PageHeader from 'antd/lib/page-header'; 6 | 7 | import BreadcrumbGenerator from './breadCrumbGenerator'; 8 | 9 | const TitleBar = ({ 10 | routes, 11 | title, 12 | subTitle, 13 | pageHeaderProps, 14 | pageHeaderContent, 15 | }) => { 16 | return ( 17 |
18 |
19 | 20 |
21 | 22 | {pageHeaderContent ? ( 23 |
24 |
{pageHeaderContent.children}
25 |
{pageHeaderContent.extra}
26 |
27 | ) : null} 28 |
29 |
30 | ); 31 | }; 32 | 33 | TitleBar.propTypes = { 34 | routes: PropTypes.array, 35 | title: PropTypes.string, 36 | subTitle: PropTypes.string, 37 | }; 38 | 39 | export default TitleBar; 40 | -------------------------------------------------------------------------------- /utils/dataFetch.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | import Cookies from 'universal-cookie'; 3 | 4 | const cookies = new Cookies(); 5 | const API_URL = 'https://api.amfoss.in/'; 6 | 7 | export default ({ query, variables }) => { 8 | const body = { 9 | query, 10 | variables, 11 | }; 12 | 13 | const token = cookies.get('token'); 14 | 15 | const apiConfig = { 16 | method: 'POST', 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | Authorization: token ? `JWT ${token}` : null, 20 | }, 21 | body: JSON.stringify(body), 22 | }; 23 | 24 | return fetch(API_URL, apiConfig).then(function (response) { 25 | const contentType = response.headers.get('content-type'); 26 | if (response.ok) { 27 | if (contentType && contentType.indexOf('application/json') !== -1) { 28 | return response.json().then((json) => json); 29 | } 30 | if (contentType && contentType.indexOf('text') !== -1) { 31 | return response.text().then((text) => text); 32 | } 33 | return response; 34 | } 35 | console.error( 36 | `Response status ${response.status} during dataFetch for url ${response.url}.` 37 | ); 38 | throw response; 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amFOSS CMS", 3 | "short_name": "CMS", 4 | "theme_color": "#001529", 5 | "background_color": "#eeeeee", 6 | "display": "standalone", 7 | "Scope": "/", 8 | "start_url": "/", 9 | "icons": [ 10 | { 11 | "src": "/static/images/icons/icon-72x72.png", 12 | "sizes": "72x72", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "/static/images/icons/icon-96x96.png", 17 | "sizes": "96x96", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "/static/images/icons/icon-128x128.png", 22 | "sizes": "128x128", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "/static/images/icons/icon-144x144.png", 27 | "sizes": "144x144", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "/static/images/icons/icon-152x152.png", 32 | "sizes": "152x152", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "/static/images/icons/icon-192x192.png", 37 | "sizes": "192x192", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "/static/images/icons/icon-384x384.png", 42 | "sizes": "384x384", 43 | "type": "image/png" 44 | }, 45 | { 46 | "src": "/static/images/icons/icon-512x512.png", 47 | "sizes": "512x512", 48 | "type": "image/png" 49 | } 50 | ], 51 | "splash_pages": null 52 | } 53 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withPlugins = require('next-compose-plugins'); 2 | const withOffline = require('next-offline'); 3 | const optimizedImages = require('next-optimized-images'); 4 | const path = require('path'); 5 | 6 | const nextConfig = { 7 | target: 'serverless', 8 | devIndicators: { 9 | autoPrerender: false, 10 | }, 11 | sassOptions: { 12 | includePaths: [path.join(__dirname, 'styles')], 13 | }, 14 | productionBrowserSourceMaps: true, 15 | }; 16 | 17 | module.exports = withPlugins( 18 | [ 19 | [optimizedImages], 20 | [ 21 | withOffline({ 22 | workboxOpts: { 23 | swDest: 'service-worker.js', 24 | runtimeCaching: [ 25 | { 26 | urlPattern: /[.](png|jpg|ico|css)/, 27 | handler: 'CacheFirst', 28 | options: { 29 | cacheName: 'assets-cache', 30 | cacheableResponse: { 31 | statuses: [0, 200], 32 | }, 33 | }, 34 | }, 35 | { 36 | urlPattern: /^https?.*/, 37 | handler: 'NetworkFirst', 38 | options: { 39 | cacheName: 'offlineCache', 40 | expiration: { 41 | maxEntries: 200, 42 | }, 43 | }, 44 | }, 45 | ], 46 | }, 47 | }), 48 | ], 49 | ], 50 | nextConfig 51 | ); 52 | -------------------------------------------------------------------------------- /pages/attendance/live-report.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | 3 | import dataFetch from '../../utils/dataFetch'; 4 | import Base from '../../components/base'; 5 | import LiveReportCard from '../../modules/attendance/components/LiveReportCard'; 6 | import TitleBar from '../../components/titlebar'; 7 | 8 | const LiveReport = (props) => { 9 | const [data, setData] = useState([]); 10 | const [isLoaded, setLoaded] = useState(false); 11 | 12 | const query = `query{ 13 | liveAttendance{ 14 | membersPresent 15 | { 16 | count 17 | members 18 | { 19 | firstName 20 | lastName 21 | username 22 | firstSeenToday 23 | duration 24 | } 25 | } 26 | } 27 | }`; 28 | 29 | const fetchData = async () => dataFetch({ query }); 30 | 31 | useEffect(() => { 32 | if (!isLoaded) { 33 | fetchData().then((r) => { 34 | setData(r.data.liveAttendance); 35 | setLoaded(true); 36 | }); 37 | } 38 | }); 39 | 40 | const routes = [ 41 | { 42 | path: '/', 43 | name: 'Home', 44 | }, 45 | { 46 | path: '/attendance', 47 | name: 'Attendance', 48 | }, 49 | { 50 | path: '/attendance/live-report', 51 | name: 'Live Attendance', 52 | }, 53 | ]; 54 | 55 | return ( 56 | 57 | 62 | 63 | 64 | ); 65 | }; 66 | 67 | export default LiveReport; 68 | -------------------------------------------------------------------------------- /modules/forms/formFields.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | // antd components 5 | import Collapse from 'antd/lib/collapse'; 6 | import Tag from 'antd/lib/tag'; 7 | 8 | import FieldEditor from './fieldEditor'; 9 | 10 | const FormFields = ({ data }) => { 11 | const renderField = (f) => ( 12 | {f.question}} 15 | > 16 | 17 | 18 | ); 19 | 20 | return ( 21 |
22 | 23 | 27 | Name Inbuilt 28 | 29 | } 30 | > 31 |
Inbuilt Field
32 |
33 | 37 | Phone Inbuilt 38 | 39 | } 40 | > 41 |
Inbuilt Field
42 |
43 | 47 | Email Inbuilt 48 | 49 | } 50 | > 51 |
Inbuilt Field
52 |
53 | {data.fields.map((f) => renderField(f))} 54 |
55 |
56 | ); 57 | }; 58 | 59 | FormFields.propTypes = { 60 | data: PropTypes.object, 61 | }; 62 | 63 | export default FormFields; 64 | -------------------------------------------------------------------------------- /styles/styles.sass: -------------------------------------------------------------------------------- 1 | body 2 | margin: 0 3 | a:hover 4 | text-decoration: none 5 | 6 | .page-container 7 | min-height: 100vh 8 | 9 | select 10 | background: white 11 | 12 | .menu-card 13 | background: linear-gradient(to bottom, #8e2de2, #4a00e0) 14 | box-shadow: 3px 1px 10px rgba(0, 0, 0, 0.3) 15 | padding: 3rem 16 | display: block 17 | text-align: center 18 | font-size: calc(1.2rem + 0.8vw) 19 | line-height: 1.2 20 | color: white 21 | 22 | .menu-card:hover 23 | color: white 24 | 25 | .ant-table-body 26 | min-height: 73vh!important 27 | cursor: pointer 28 | overflow: auto 29 | 30 | .loading 31 | background-color: #eeeeee 32 | min-height: 100vh 33 | display: flex 34 | flex-direction: column 35 | align-items: center 36 | justify-content: center 37 | 38 | .confirm-modal-overlay 39 | position: fixed 40 | top: 0 41 | left: 0 42 | z-index: 1050 43 | width: 100vw 44 | height: 100vh 45 | background-color: #000 46 | opacity: .5 47 | 48 | .confirm-modal-wrapper 49 | position: fixed 50 | top: 0 51 | left: 0 52 | z-index: 1050 53 | width: 100% 54 | height: 100% 55 | overflow-x: hidden 56 | overflow-y: auto 57 | outline: 0 58 | 59 | .confirm-modal 60 | z-index: 1050 61 | background: white 62 | position: relative 63 | margin: 18.75rem auto 64 | border-radius: 3px 65 | max-width: 500px 66 | padding: 2.5rem 67 | display: block 68 | 69 | .confirm-modal-header 70 | display: flex 71 | 72 | .confirm-modal-header-button 73 | display: flex 74 | justify-content: flex-end 75 | 76 | .platform-true 77 | color: #52c41a 78 | 79 | .platform-false 80 | color: #eb2f96 81 | 82 | .platform-icon 83 | display: inline-block 84 | vertical-align: middle 85 | -------------------------------------------------------------------------------- /modules/attendance/components/ActiveStatusBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | // antd components 5 | import Alert from 'antd/lib/alert'; 6 | 7 | const ActiveStatusBar = ({ lastSeen, membersPresentCount, isInLab }) => { 8 | return membersPresentCount === 0 && !isInLab ? ( 9 | 19 | ) : !isInLab && lastSeen !== null ? ( 20 | You were last recorded at ${lastSeen.toString()}`} 26 | type="warning" 27 | showIcon 28 | /> 29 | ) : lastSeen == null ? ( 30 | 38 | ) : ( 39 | 46 | ); 47 | }; 48 | 49 | ActiveStatusBar.propTypes = { 50 | lastSeen: PropTypes.dateTime, 51 | membersPresentCount: PropTypes.int, 52 | isInLab: PropTypes.bool, 53 | }; 54 | 55 | export default ActiveStatusBar; 56 | -------------------------------------------------------------------------------- /pages/account/settings.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | // antd components 4 | import Card from 'antd/lib/card'; 5 | import Menu from 'antd/lib/menu'; 6 | 7 | import Base from '../../components/base'; 8 | import TitleBar from '../../components/titlebar'; 9 | import BasicSettings from '../../components/account/basicSettings'; 10 | import ChangePassword from '../../components/account/changePassword'; 11 | 12 | const routes = [ 13 | { 14 | path: '/', 15 | name: 'Home', 16 | }, 17 | { 18 | path: '/account', 19 | name: 'Account', 20 | }, 21 | { 22 | path: '/account/settings', 23 | name: 'Settings', 24 | }, 25 | ]; 26 | const AccountSettings = (props) => { 27 | const [current, setCurrent] = useState('basic'); 28 | return ( 29 | 30 | 31 |
32 | 33 |
34 |
35 | setCurrent(e.key)} selectedKeys={[current]}> 36 | Update Profile 37 | Change Password 38 | 39 |
40 |
50 | {current === 'basic' ? : } 51 |
52 |
53 |
54 |
55 | 56 | ); 57 | }; 58 | 59 | export default AccountSettings; 60 | -------------------------------------------------------------------------------- /modules/forms/entryDetails.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const EntryDetails = ({ fields, data }) => { 5 | const getColSize = (a, b) => { 6 | if (a == null && b == null) return 'col-md-3 col-sm-4 col-12'; 7 | let value; 8 | if (a == null) value = b.length; 9 | else if (b == null) value = a.length; 10 | else if (a.length > b.length) value = a.length; 11 | else value = b.length; 12 | if (value > 100) return 'col-12'; 13 | if (value > 30) return 'col-md-6 col-lg-4'; 14 | return 'col-md-4 col-lg-3'; 15 | }; 16 | 17 | const getItem = (label, value) => ( 18 |
19 |
20 |
{label}
21 | {value} 22 |
23 |
24 | ); 25 | 26 | return ( 27 |
28 |
29 |
30 |
Entry #{data.id}
31 |

{data.name}

32 |
    33 |
  • 34 | Submission Time:{' '} 35 | {new Date(data.submissionTime).toLocaleString()} 36 |
  • 37 |
  • 38 | Email: {data.email} 39 |
  • 40 |
  • 41 | Phone Number: {data.phone} 42 |
  • 43 |
  • 44 | Details: {data.details} 45 |
  • 46 |
47 |
48 |
49 | {fields.map((f) => 50 | getItem(f.question, data.formData.find((d) => d.key === f.key).value) 51 | )} 52 |
53 | ); 54 | }; 55 | 56 | EntryDetails.propTypes = { 57 | fields: PropTypes.array, 58 | data: PropTypes.object, 59 | }; 60 | 61 | export default EntryDetails; 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cms-webapp", 3 | "version": "0.1.0", 4 | "description": "webapp for FOSS@Amrita", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "NODE_ENV='DEV' next", 8 | "build": "next build", 9 | "start": "next start", 10 | "purifyCSS": "node ./scripts/purifyCSS.js", 11 | "export": "npm run build && next export", 12 | "lint": "eslint .", 13 | "lint:fix": "eslint --fix .", 14 | "format": "prettier --write \"**/*.+(js|jsx|json)\"" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git://gitlab.com/amfoss/WebApp.git" 19 | }, 20 | "keywords": [ 21 | "webapp", 22 | "react", 23 | "club", 24 | "management" 25 | ], 26 | "author": "amfoss ", 27 | "license": "GPL-3.0-or-later", 28 | "dependencies": { 29 | "@ant-design/icons": "^4.0.6", 30 | "antd": "^4.1.1", 31 | "axios": "^0.19.2", 32 | "bootstrap": "^4.4.1", 33 | "chart.js": "^2.9.3", 34 | "classnames": "^2.2.6", 35 | "es6-promise": "^4.2.8", 36 | "imagemin-optipng": "^7.1.0", 37 | "isomorphic-fetch": "^2.2.1", 38 | "markdown-it": "^11.0.0", 39 | "moment": "^2.24.0", 40 | "moment-range": "^4.0.2", 41 | "next": "^9.3.5", 42 | "next-compose-plugins": "^2.2.0", 43 | "next-offline": "^5.0.1", 44 | "next-optimized-images": "^2.5.8", 45 | "prop-types": "^15.7.2", 46 | "react": "^16.13.1", 47 | "react-chartjs-2": "^2.9.0", 48 | "react-csv": "^2.0.3", 49 | "react-dom": "^16.13.1", 50 | "react-highlight-words": "^0.16.0", 51 | "react-loading": "^2.0.3", 52 | "react-markdown-editor-lite": "^1.1.5", 53 | "react-qr-reader": "^2.2.1", 54 | "react-quill": "^1.3.5", 55 | "sass": "^1.32.8", 56 | "universal-cookie": "^4.0.3" 57 | }, 58 | "devDependencies": { 59 | "eslint": "^6.8.0", 60 | "eslint-config-prettier": "^6.10.1", 61 | "eslint-plugin-prettier": "^3.1.3", 62 | "eslint-plugin-react": "^7.19.0", 63 | "eslint-plugin-react-hooks": "^3.0.0", 64 | "prettier": "^2.0.4" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /components/header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | import { string } from 'prop-types'; 4 | 5 | const content = 6 | 'FOSS@Amrita (also known as amFOSS) is a student community based in Amrita Vishwa Vidyapeetham, Amritapuri' + 7 | ' focused on contributing to Free and Open Source Software and mentoring students to achieve excellence in various ' + 8 | 'fields of Computer Science.'; 9 | 10 | const Header = (props) => ( 11 | 12 | {props.title} | amFOSS CMS 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | 41 | Header.propTypes = { 42 | title: string, 43 | description: string, 44 | keywords: string, 45 | url: string, 46 | ogImage: string, 47 | }; 48 | 49 | export default Header; 50 | -------------------------------------------------------------------------------- /modules/attendance/components/LiveReportCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Moment from 'moment'; 3 | import { extendMoment } from 'moment-range'; 4 | import PropTypes from 'prop-types'; 5 | 6 | // antd components 7 | import Card from 'antd/lib/card'; 8 | import List from 'antd/lib/list'; 9 | import Avatar from 'antd/lib/avatar'; 10 | import Badge from 'antd/lib/badge'; 11 | import Tabs from 'antd/lib/tabs'; 12 | 13 | const { TabPane } = Tabs; 14 | const moment = extendMoment(Moment); 15 | 16 | const LiveReportCard = ({ data, isLoaded }) => { 17 | const membersCard = (members) => ( 18 | ( 23 | 24 | 25 | } 27 | title={ 28 | 29 | 30 | {member.firstName} {member.lastName} 31 | 32 | 33 | } 34 | description={ 35 |
36 | Since {moment(member.firstSeenToday).format('HH:mm')}
37 | For {member.duration} 38 |
39 | } 40 | /> 41 |
42 |
43 | )} 44 | /> 45 | ); 46 | 47 | return ( 48 |
49 | 50 | 54 | Present Now 55 | 61 | 62 | } 63 | > 64 | {isLoaded ? membersCard(data.membersPresent.members) : null} 65 | 66 | 67 |
68 | ); 69 | }; 70 | 71 | LiveReportCard.propTypes = { 72 | data: PropTypes.object, 73 | isLoaded: PropTypes.bool, 74 | }; 75 | 76 | export default LiveReportCard; 77 | -------------------------------------------------------------------------------- /modules/statusUpdates/components/DailyStatusReportCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | // antd components 5 | import Card from 'antd/lib/card'; 6 | import List from 'antd/lib/list'; 7 | import Avatar from 'antd/lib/avatar'; 8 | import Badge from 'antd/lib/badge'; 9 | import Tabs from 'antd/lib/tabs'; 10 | 11 | const { TabPane } = Tabs; 12 | 13 | const DailyStatusReportCard = ({ data, isLoaded }) => { 14 | const membersCard = (members) => ( 15 | ( 20 | 21 | 22 | 31 | } 32 | title={ 33 | 34 | {m.member.fullName} 35 | 36 | } 37 | /> 38 | 39 | 40 | )} 41 | /> 42 | ); 43 | 44 | return ( 45 |
46 | 47 | 51 | Sent 52 | 56 | 57 | } 58 | > 59 | {isLoaded ? membersCard(data.membersSent, 'sent') : null} 60 | 61 | 66 | Did Not Send 67 | 71 | 72 | } 73 | > 74 | {isLoaded ? membersCard(data.memberDidNotSend, 'didNotSend') : null} 75 | 76 | 77 |
78 | ); 79 | }; 80 | 81 | DailyStatusReportCard.propTypes = { 82 | data: PropTypes.object, 83 | isLoaded: PropTypes.bool, 84 | }; 85 | 86 | export default DailyStatusReportCard; 87 | -------------------------------------------------------------------------------- /pages/attendance/dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import Link from 'next/link'; 3 | import Cookies from 'universal-cookie'; 4 | 5 | import TitleBar from '../../components/titlebar'; 6 | import Base from '../../components/base'; 7 | import ActiveStatusBar from '../../modules/attendance/components/ActiveStatusBar'; 8 | import dataFetch from '../../utils/dataFetch'; 9 | 10 | const cookies = new Cookies(); 11 | 12 | const AttendanceDashboard = (props) => { 13 | const [isInLab, setIsInLab] = useState(false); 14 | const [lastSeen, setLastSeen] = useState(false); 15 | const [membersPresentCount, setMembersPresentCount] = useState(0); 16 | 17 | const [isLoaded, setLoaded] = useState(false); 18 | const username = cookies.get('username'); 19 | 20 | const query = ` 21 | query($username: String!){ 22 | user(username: $username) 23 | { 24 | isInLab 25 | lastSeenInLab 26 | } 27 | liveAttendance 28 | { 29 | membersPresent 30 | { 31 | count 32 | } 33 | } 34 | }`; 35 | 36 | const fetchData = async (variables) => dataFetch({ query, variables }); 37 | 38 | useEffect(() => { 39 | if (!isLoaded) { 40 | fetchData({ username }).then((r) => { 41 | setIsInLab(r.data.user.isInLab); 42 | setLastSeen(r.data.user.lastSeenInLab); 43 | setMembersPresentCount(r.data.liveAttendance.membersPresent.count); 44 | setLoaded(true); 45 | }); 46 | } 47 | }); 48 | 49 | const routes = [ 50 | { 51 | path: '/', 52 | name: 'Home', 53 | }, 54 | { 55 | path: '/attendance', 56 | name: 'Attendance', 57 | }, 58 | ]; 59 | 60 | return ( 61 | 62 | 63 |
64 | 69 |
70 |
71 | 72 | Live Attendance 73 | 74 |
75 |
76 | 77 | Daily Reports 78 | 79 |
80 |
81 | 82 | Individual Reports 83 | 84 |
85 |
86 |
87 | 88 | ); 89 | }; 90 | 91 | export default AttendanceDashboard; 92 | -------------------------------------------------------------------------------- /components/reset-password/resetForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import dataFetch from '../../utils/dataFetch'; 3 | import Link from 'next/link'; 4 | 5 | import Result from 'antd/lib/result'; 6 | import Card from "antd/lib/card"; 7 | import Form from "antd/lib/form"; 8 | import Input from "antd/lib/input"; 9 | import { LockOutlined, UserOutlined } from "@ant-design/icons"; 10 | import Button from "antd/lib/button"; 11 | 12 | const ResetForm = () => { 13 | const [isLoading, setLoaded] = useState(false); 14 | const [error, setErrorText] = useState(''); 15 | const [success, setSuccessText] = useState(''); 16 | 17 | const query = ` 18 | mutation ($email: String!){ 19 | resetPassword(email:$email){ 20 | status 21 | } 22 | } 23 | `; 24 | 25 | const submitForm = async (variables) => await dataFetch({ query, variables }); 26 | 27 | const resetPassword = (values) => { 28 | submitForm(values).then((response) => { 29 | if (Object.prototype.hasOwnProperty.call(response, 'errors')) { 30 | setErrorText(response.errors[0].message); 31 | } else { 32 | setSuccessText(response.data.status); 33 | setErrorText(''); 34 | setLoaded(true); 35 | } 36 | }); 37 | }; 38 | 39 | const onFinishFailed = (errorInfo) => { 40 | console.error(errorInfo); 41 | }; 42 | 43 | return !isLoading ? ( 44 | 45 | CMS Logo 46 |
53 | 58 | } 60 | placeholder="Email" 61 | /> 62 | 63 | 64 | 71 | 72 |
73 | Already have an account? Login 74 |
75 | ) : ( 76 | 77 | {success !== '' ? ( 78 | 79 | ) : error !== '' ? ( 80 |
{error}
81 | ) : ( 82 |
Submitting. Please Wait
83 | )} 84 | Back to Login 85 |
86 | ); 87 | }; 88 | 89 | export default ResetForm; 90 | -------------------------------------------------------------------------------- /components/authRequired.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Result from 'antd/lib/result'; 3 | import Button from 'antd/lib/button'; 4 | import Router from 'next/router'; 5 | import PropTypes from 'prop-types'; 6 | 7 | import LoadingScreen from './loadingScreen'; 8 | import Base from './base'; 9 | 10 | const AuthRequired = ({ 11 | children, 12 | loaded, 13 | isAdmin, 14 | isClubMember, 15 | verificationRequired, 16 | adminRequired, 17 | }) => { 18 | return loaded ? ( 19 | adminRequired ? ( 20 | isAdmin ? ( 21 | children 22 | ) : ( 23 | 24 |
28 | Router.push('/')}> 34 | Back Home 35 | 36 | } 37 | /> 38 |
39 | 40 | ) 41 | ) : isAdmin || isClubMember ? ( 42 | children 43 | ) : ( 44 | 48 |
52 | 62 | {verificationRequired ? ( 63 | 69 | ) : ( 70 | 73 | )} 74 | 75 | } 76 | /> 77 |
78 | 79 | ) 80 | ) : ( 81 | 82 | ); 83 | }; 84 | 85 | AuthRequired.propTypes = { 86 | loaded: PropTypes.bool, 87 | children: PropTypes.node, 88 | isAdmin: PropTypes.bool, 89 | isClubMember: PropTypes.bool, 90 | verificationRequired: PropTypes.bool, 91 | adminRequired: PropTypes.bool, 92 | }; 93 | 94 | export default AuthRequired; 95 | -------------------------------------------------------------------------------- /pages/status-updates/daily-report.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import Moment from 'moment'; 3 | import { extendMoment } from 'moment-range'; 4 | 5 | // antd components 6 | import DatePicker from 'antd/lib/date-picker'; 7 | 8 | import Base from '../../components/base'; 9 | import DailyStatusReportCard from '../../modules/statusUpdates/components/DailyStatusReportCard'; 10 | import dataFetch from '../../utils/dataFetch'; 11 | import TitleBar from '../../components/titlebar'; 12 | 13 | const moment = extendMoment(Moment); 14 | 15 | const DailyStatusReport = (props) => { 16 | const [data, setData] = useState(''); 17 | const [date, setDate] = useState( 18 | moment().subtract(1, 'days').format('YYYY-MM-DD') 19 | ); 20 | const [isLoaded, setLoaded] = useState(false); 21 | 22 | const query = `query($date:Date!){ 23 | dailyStatusUpdates(date:$date){ 24 | date 25 | membersSent{ 26 | member{ 27 | username 28 | fullName 29 | statusUpdateCount 30 | profile{ 31 | profilePic 32 | } 33 | avatar{ 34 | githubUsername 35 | } 36 | } 37 | } 38 | memberDidNotSend{ 39 | member{ 40 | username 41 | fullName 42 | statusUpdateCount 43 | profile{ 44 | profilePic 45 | } 46 | avatar{ 47 | githubUsername 48 | } 49 | } 50 | } 51 | } 52 | }`; 53 | 54 | const fetchData = async (variables) => dataFetch({ query, variables }); 55 | 56 | useEffect(() => { 57 | if (!isLoaded) { 58 | fetchData({ date }).then((r) => { 59 | setData(r.data.dailyStatusUpdates); 60 | setLoaded(true); 61 | }); 62 | } 63 | }); 64 | 65 | const routes = [ 66 | { 67 | path: '/', 68 | name: 'Home', 69 | }, 70 | { 71 | path: '/status-update', 72 | name: 'Status Update', 73 | }, 74 | { 75 | path: '/status-update/daily-report', 76 | name: 'Daily Report', 77 | }, 78 | ]; 79 | 80 | return ( 81 | 82 | 87 |
88 |
89 |
90 | { 93 | setLoaded(false); 94 | setDate(e.format('YYYY-MM-DD')); 95 | }} 96 | format="DD-MM-YYYY" 97 | value={moment(date)} 98 | /> 99 |
100 |
101 |
102 | 103 |
104 | 105 | ); 106 | }; 107 | 108 | export default DailyStatusReport; 109 | -------------------------------------------------------------------------------- /pages/attendance/daily-report.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import Moment from 'moment'; 3 | import { extendMoment } from 'moment-range'; 4 | 5 | // antd components 6 | import DatePicker from 'antd/lib/date-picker'; 7 | 8 | import Base from '../../components/base'; 9 | import DailyReportCard from '../../modules/attendance/components/DailyReportCard'; 10 | import dataFetch from '../../utils/dataFetch'; 11 | import TitleBar from '../../components/titlebar'; 12 | 13 | const moment = extendMoment(Moment); 14 | 15 | const DailyReport = (props) => { 16 | const [data, setData] = useState(''); 17 | const [date, setDate] = useState(moment().format('YYYY-MM-DD')); 18 | const [isLoaded, setLoaded] = useState(false); 19 | 20 | const query = `query($date: Date!){ 21 | dailyAttendance(date: $date) 22 | { 23 | date 24 | membersPresent 25 | { 26 | member 27 | { 28 | username 29 | firstName 30 | lastName 31 | profile{ 32 | profilePic 33 | } 34 | } 35 | lastSeen 36 | firstSeen 37 | duration 38 | } 39 | membersAbsent 40 | { 41 | member 42 | { 43 | username 44 | firstName 45 | lastName 46 | profile{ 47 | profilePic 48 | } 49 | avatar 50 | { 51 | githubUsername 52 | } 53 | } 54 | lastSeen 55 | } 56 | } 57 | }`; 58 | 59 | const fetchData = async (variables) => dataFetch({ query, variables }); 60 | 61 | useEffect(() => { 62 | if (!isLoaded) { 63 | fetchData({ date }).then((r) => { 64 | setData(r.data.dailyAttendance); 65 | setLoaded(true); 66 | }); 67 | } 68 | }); 69 | 70 | const routes = [ 71 | { 72 | path: '/', 73 | name: 'Home', 74 | }, 75 | { 76 | path: '/attendance', 77 | name: 'Attendance', 78 | }, 79 | { 80 | path: '/attendance/daily-report', 81 | name: 'Daily Report', 82 | }, 83 | ]; 84 | 85 | return ( 86 | 87 | 92 |
93 |
94 |
95 | { 98 | setLoaded(false); 99 | setDate(e.format('YYYY-MM-DD')); 100 | }} 101 | format="DD-MM-YYYY" 102 | value={moment(date)} 103 | /> 104 |
105 |
106 |
107 | 108 |
109 | 110 | ); 111 | }; 112 | 113 | export default DailyReport; 114 | -------------------------------------------------------------------------------- /modules/statusUpdates/components/Overview.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import Moment from 'moment'; 3 | import { extendMoment } from 'moment-range'; 4 | 5 | // antd components 6 | import DatePicker from 'antd/lib/date-picker'; 7 | 8 | import dataFetch from '../../../utils/dataFetch'; 9 | import Rankings from './Ranking'; 10 | import TrendStatusGraph from './TrendStatusGraph'; 11 | import StatusGraph from './StatusGraph'; 12 | 13 | const { RangePicker } = DatePicker; 14 | const moment = extendMoment(Moment); 15 | 16 | const Overview = () => { 17 | const [data, setData] = useState([]); 18 | const [dailyLogData, setDailyLogData] = useState([]); 19 | const [startDate, setStartDate] = useState(new Date()); 20 | const [endDate, setEndDate] = useState(new Date()); 21 | 22 | const [rangeLoaded, setRangeLoaded] = useState(false); 23 | const [isLoaded, setLoaded] = useState(false); 24 | 25 | const query = `query ($startDate: Date!, $endDate: Date){ 26 | clubStatusUpdate(startDate: $startDate, endDate: $endDate){ 27 | dailyLog{ 28 | date 29 | membersSentCount 30 | } 31 | memberStats{ 32 | user{ 33 | username 34 | admissionYear 35 | } 36 | statusCount 37 | } 38 | } 39 | }`; 40 | 41 | const fetchData = async (variables) => dataFetch({ query, variables }); 42 | 43 | useEffect(() => { 44 | if (!rangeLoaded) { 45 | setStartDate(new Date(moment().subtract(1, 'weeks').format('YYYY-MM-DD'))); 46 | setRangeLoaded(true); 47 | } 48 | if (!isLoaded && rangeLoaded) { 49 | const variables = { 50 | startDate: moment(startDate).format('YYYY-MM-DD'), 51 | endDate: moment(endDate).format('YYYY-MM-DD'), 52 | }; 53 | fetchData(variables).then((r) => { 54 | setData(r.data.clubStatusUpdate.memberStats); 55 | setDailyLogData(r.data.clubStatusUpdate.dailyLog); 56 | setLoaded(true); 57 | }); 58 | } 59 | }); 60 | 61 | const handleRangeChange = (obj) => { 62 | if (obj[0] != null && obj[1] != null) { 63 | setStartDate(obj[0]); 64 | setEndDate(obj[1]); 65 | setLoaded(false); 66 | } 67 | }; 68 | 69 | return ( 70 |
71 |
72 |
73 |
74 | 81 |
82 |
83 |
84 |
85 |
86 | 87 |
88 |
89 | 90 |
91 |
92 | 97 |
98 |
99 |
100 | ); 101 | }; 102 | 103 | export default Overview; 104 | -------------------------------------------------------------------------------- /modules/attendance/components/Overview.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import Moment from 'moment'; 3 | import { extendMoment } from 'moment-range'; 4 | 5 | // antd components 6 | import DatePicker from 'antd/lib/date-picker'; 7 | 8 | import dataFetch from '../../../utils/dataFetch'; 9 | import Rankings from './Rankings'; 10 | import TrendGraph from './TrendGraph'; 11 | import TrendAttendanceGraph from './TrendAttendanceGraph'; 12 | 13 | const { RangePicker } = DatePicker; 14 | const moment = extendMoment(Moment); 15 | 16 | const Overview = () => { 17 | const [data, setData] = useState([]); 18 | const [memberStats, setMemberStats] = useState([]); 19 | const [startDate, setStartDate] = useState(new Date()); 20 | const [endDate, setEndDate] = useState(new Date()); 21 | 22 | const [rangeLoaded, setRangeLoaded] = useState(false); 23 | const [isLoaded, setLoaded] = useState(false); 24 | 25 | const query = `query($startDate: Date!, $endDate: Date){ 26 | clubAttendance(startDate: $startDate, endDate: $endDate) 27 | { 28 | dailyLog 29 | { 30 | date 31 | membersPresent 32 | avgDuration 33 | } 34 | memberStats{ 35 | user{ 36 | username 37 | admissionYear 38 | } 39 | presentCount 40 | } 41 | } 42 | }`; 43 | 44 | const fetchData = async (variables) => dataFetch({ query, variables }); 45 | 46 | useEffect(() => { 47 | if (!rangeLoaded) { 48 | setStartDate(new Date(moment().subtract(2, 'weeks').format('YYYY-MM-DD'))); 49 | setRangeLoaded(true); 50 | } 51 | if (!isLoaded && rangeLoaded) { 52 | const variables = { 53 | startDate: moment(startDate).format('YYYY-MM-DD'), 54 | endDate: moment(endDate).format('YYYY-MM-DD'), 55 | }; 56 | fetchData(variables).then((r) => { 57 | setData(r.data.clubAttendance.dailyLog); 58 | setMemberStats(r.data.clubAttendance.memberStats); 59 | setLoaded(true); 60 | }); 61 | } 62 | }); 63 | 64 | const handleRangeChange = (obj) => { 65 | if (obj[0] != null && obj[1] != null) { 66 | setStartDate(obj[0]); 67 | setEndDate(obj[1]); 68 | setLoaded(false); 69 | } 70 | }; 71 | 72 | return ( 73 |
74 |
75 |
76 |
77 | 84 |
85 |
86 |
87 |
88 |
89 | 90 |
91 |
92 | 93 |
94 |
95 | 100 |
101 |
102 |
103 | ); 104 | }; 105 | 106 | export default Overview; 107 | -------------------------------------------------------------------------------- /components/admin/alertComponent.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons'; 4 | 5 | import dataFetch from '../../utils/dataFetch'; 6 | 7 | const AlertComponent = ({ changedPlatforms, show, toggle, data }) => { 8 | const [result, setResult] = useState(null); 9 | 10 | const query = ` 11 | mutation changeUserPlatform($username: String!, $GitLab: Boolean, $GitHub: Boolean, $CloudFlare: Boolean, $Telegram: Boolean){ 12 | changeUserPlatform(username: $username, gitlab: $GitLab, github: $GitHub, cloudflare: $CloudFlare, telegram: $Telegram){ 13 | status 14 | } 15 | } 16 | `; 17 | const fetchData = async (variables) => dataFetch({ query, variables }); 18 | 19 | function getValue(obj, key) { 20 | let value; 21 | for (let i in obj) { 22 | if (!obj.hasOwnProperty(i)) continue; 23 | if (i === key) { 24 | value = obj[i]; 25 | } 26 | } 27 | return value; 28 | } 29 | 30 | function submit() { 31 | const variables = { username: data.user }; 32 | changedPlatforms.map((platform) => { 33 | variables[`${platform}`] = getValue(data, platform); 34 | }); 35 | fetchData(variables).then((r) => { 36 | setResult(r.data.changeUserPlatform.status); 37 | }); 38 | } 39 | 40 | return show ? ( 41 | 42 |
43 |
44 |
45 | {result ? ( 46 |
Successfully Updated
47 | ) : null} 48 |
49 |
Are you sure?
50 |
51 |

52 | You are updating status of {data.user} in the following platforms 53 |

54 | {changedPlatforms.map((platform, index) => ( 55 |
  • 60 | {getValue(data, platform) ? ( 61 | 62 | ) : ( 63 | 64 | )}{' '} 65 | {platform} 66 |
  • 67 | ))} 68 |
    69 | 78 | 86 |
    87 |
    88 |
    89 |
    90 | 91 | ) : null; 92 | }; 93 | 94 | AlertComponent.propTypes = { 95 | changedPlatforms: PropTypes.array, 96 | data: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), 97 | show: PropTypes.bool, 98 | toggle: PropTypes.func, 99 | }; 100 | 101 | export default AlertComponent; 102 | -------------------------------------------------------------------------------- /modules/statusUpdates/components/StatusGraph.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classnames from 'classnames'; 3 | import { Line } from 'react-chartjs-2'; 4 | import PropTypes from 'prop-types'; 5 | 6 | // antd components 7 | import Card from 'antd/lib/card'; 8 | 9 | const StatusGraph = ({ isLoaded, dailyLogData }) => { 10 | let options = { 11 | maintainAspectRatio: false, 12 | legend: { 13 | display: false, 14 | }, 15 | tooltips: { 16 | backgroundColor: '#f5f5f5', 17 | titleFontColor: '#333', 18 | bodyFontColor: '#666', 19 | bodySpacing: 4, 20 | xPadding: 12, 21 | mode: 'nearest', 22 | intersect: 0, 23 | position: 'nearest', 24 | }, 25 | responsive: true, 26 | scales: { 27 | yAxes: [ 28 | { 29 | barPercentage: 1.6, 30 | gridLines: { 31 | drawBorder: false, 32 | color: 'rgba(29,140,248,0.0)', 33 | zeroLineColor: 'transparent', 34 | }, 35 | ticks: { 36 | suggestedMin: 0, 37 | suggestedMax: 5, 38 | padding: 20, 39 | fontColor: '#9a9a9a', 40 | }, 41 | }, 42 | ], 43 | xAxes: [ 44 | { 45 | barPercentage: 1.6, 46 | gridLines: { 47 | drawBorder: false, 48 | color: 'rgba(29,140,248,0.1)', 49 | zeroLineColor: 'transparent', 50 | }, 51 | ticks: { 52 | padding: 20, 53 | fontColor: '#9a9a9a', 54 | }, 55 | }, 56 | ], 57 | }, 58 | }; 59 | 60 | const x = []; 61 | const y = []; 62 | dailyLogData.map((r) => { 63 | x.push(r.date); 64 | y.push(r.membersSentCount); 65 | }); 66 | let statusGraph = { 67 | data: (canvas) => { 68 | let ctx = canvas.getContext('2d'); 69 | 70 | let gradientStroke = ctx.createLinearGradient(0, 2300, 0, 50); 71 | 72 | gradientStroke.addColorStop(1, 'rgba(29,140,248,0.2)'); 73 | gradientStroke.addColorStop(0.4, 'rgba(29,140,248,0.0)'); 74 | gradientStroke.addColorStop(0, 'rgba(29,140,248,0)'); //blue colors 75 | 76 | return { 77 | labels: x, 78 | datasets: [ 79 | { 80 | label: 'Status Graph', 81 | fill: true, 82 | backgroundColor: gradientStroke, 83 | borderColor: '#1f8ef1', 84 | borderWidth: 2, 85 | borderDash: [], 86 | borderDashOffset: 0.0, 87 | pointBackgroundColor: '#1f8ef1', 88 | pointBorderColor: 'rgba(255,255,255,0)', 89 | pointHoverBackgroundColor: '#1f8ef1', 90 | pointBorderWidth: 20, 91 | pointHoverRadius: 4, 92 | pointHoverBorderWidth: 15, 93 | pointRadius: 4, 94 | data: y, 95 | }, 96 | ], 97 | }; 98 | }, 99 | }; 100 | 101 | return ( 102 | 103 |
    104 |
    105 |
    Status Trends
    106 |
    107 |
    108 |
    112 | {isLoaded ? : null} 113 |
    114 |
    115 | ); 116 | }; 117 | 118 | StatusGraph.propTypes = { 119 | isLoaded: PropTypes.bool, 120 | dailyLogData: PropTypes.array, 121 | }; 122 | 123 | export default StatusGraph; 124 | -------------------------------------------------------------------------------- /pages/status-updates/messages.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { extendMoment } from 'moment-range'; 3 | import Moment from 'moment'; 4 | 5 | // antd components 6 | import Card from 'antd/lib/card'; 7 | import DatePicker from 'antd/lib/date-picker'; 8 | import Avatar from 'antd/lib/avatar'; 9 | 10 | import Base from '../../components/base'; 11 | import dataFetch from '../../utils/dataFetch'; 12 | import TitleBar from '../../components/titlebar'; 13 | 14 | const moment = extendMoment(Moment); 15 | const { Meta } = Card; 16 | 17 | const Messages = (props) => { 18 | const [data, setData] = useState([]); 19 | const [date, setDate] = useState( 20 | moment().subtract(1, 'days').format('YYYY-MM-DD') 21 | ); 22 | const [isLoaded, setLoaded] = useState(false); 23 | 24 | const query = ` 25 | query($date: Date!){ 26 | getStatusUpdates(date: $date){ 27 | member{ 28 | username 29 | fullName 30 | profile{ 31 | profilePic 32 | } 33 | avatar{ 34 | githubUsername 35 | } 36 | } 37 | timestamp 38 | message 39 | } 40 | }`; 41 | 42 | const fetchData = async (variables) => dataFetch({ query, variables }); 43 | 44 | useEffect(() => { 45 | if (!isLoaded) { 46 | fetchData({ date }).then((r) => { 47 | setData(r.data.getStatusUpdates); 48 | setLoaded(true); 49 | }); 50 | } 51 | }); 52 | 53 | const routes = [ 54 | { 55 | path: '/', 56 | name: 'Home', 57 | }, 58 | { 59 | path: '/status-updates/dashboard', 60 | name: 'Status Update', 61 | }, 62 | { 63 | path: '/status-updates/messages', 64 | name: 'Messages', 65 | }, 66 | ]; 67 | 68 | return ( 69 | 70 | 75 | { 79 | setLoaded(false); 80 | setDate(e.format('YYYY-MM-DD')); 81 | }} 82 | format="DD-MM-YYYY" 83 | value={moment(date)} 84 | /> 85 |
    86 | {data.length > 0 ? ( 87 | data.map((message) => ( 88 |
    89 | 102 | } 103 | title={message.member.fullName} 104 | /> 105 | } 106 | > 107 |
    108 | 109 |
    110 | )) 111 | ) : ( 112 |
    113 | Can't find any status updates 114 |
    115 | )} 116 |
    117 | 118 | ); 119 | }; 120 | 121 | export default Messages; 122 | -------------------------------------------------------------------------------- /pages/status-updates/individual-report.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | // antd components 4 | import Card from 'antd/lib/card'; 5 | import Input from 'antd/lib/input'; 6 | import Avatar from 'antd/lib/avatar'; 7 | 8 | import dataFetch from '../../utils/dataFetch'; 9 | import Base from '../../components/base'; 10 | import TitleBar from '../../components/titlebar'; 11 | 12 | const { Search } = Input; 13 | const { Meta } = Card; 14 | 15 | const IndividualReport = (props) => { 16 | const [data, setData] = useState([]); 17 | const [username, setUsername] = useState(''); 18 | const [isLoaded, setLoaded] = useState(false); 19 | 20 | const query = ` 21 | query getMemberStatusUpdates($username: String!){ 22 | getMemberStatusUpdates(username:$username){ 23 | message 24 | date 25 | member{ 26 | fullName 27 | profile{ 28 | profilePic 29 | } 30 | avatar{ 31 | githubUsername 32 | } 33 | } 34 | } 35 | }`; 36 | 37 | const routes = [ 38 | { 39 | path: '/', 40 | name: 'Home', 41 | }, 42 | { 43 | path: '/status-updates/individual-report', 44 | name: 'Status Update', 45 | }, 46 | ]; 47 | 48 | const fetchData = async (variables) => dataFetch({ query, variables }); 49 | 50 | const getMemberUpdates = (username) => { 51 | setUsername(username); 52 | const variables = { username }; 53 | fetchData(variables).then((r) => { 54 | setData(r.data.getMemberStatusUpdates); 55 | setLoaded(true); 56 | }); 57 | }; 58 | 59 | return ( 60 | 61 | 66 |
    67 |
    68 | getMemberUpdates(value.toLowerCase())} 71 | style={{ width: 350 }} 72 | enterButton 73 | /> 74 |
    75 |
    76 | {data.length > 0 ? ( 77 |
    78 | No of status updates: {data.length} 79 |
    80 | ) : null} 81 |
    82 | {data.length > 0 ? ( 83 | data.map((report) => ( 84 |
    85 | 98 | } 99 | title={report.member.fullName} 100 | /> 101 | } 102 | extra={report.date} 103 | > 104 |
    105 | 106 |
    107 | )) 108 | ) : ( 109 |
    113 | Type the username to get the report 114 |
    115 | )} 116 | 117 | ); 118 | }; 119 | 120 | export default IndividualReport; 121 | -------------------------------------------------------------------------------- /pages/form/[id]/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import Link from 'next/link'; 3 | import { useRouter } from 'next/router'; 4 | 5 | // antd components 6 | import Button from 'antd/lib/button'; 7 | import Statistic from 'antd/lib/statistic'; 8 | import Result from 'antd/lib/result'; 9 | import Tabs from 'antd/lib/tabs'; 10 | 11 | import dataFetch from '../../../utils/dataFetch'; 12 | import Base from '../../../components/base'; 13 | import TitleBar from '../../../components/titlebar'; 14 | import FormFields from '../../../modules/forms/formFields'; 15 | 16 | const FormId = (props) => { 17 | const formID = useRouter().query.id; 18 | const [data, setData] = useState(''); 19 | const [isLoaded, setLoaded] = useState(false); 20 | 21 | const query = `query getFormDetails($formID: Int!){ 22 | getForm(formID: $formID) 23 | { 24 | name 25 | isActive 26 | allowMultiple 27 | submissionDeadline 28 | applicationLimit 29 | entriesCount 30 | fields 31 | { 32 | question 33 | required 34 | important 35 | type 36 | regex 37 | key 38 | } 39 | } 40 | }`; 41 | 42 | const fetchData = async (variables) => dataFetch({ query, variables }); 43 | useEffect(() => { 44 | if (!isLoaded && formID) { 45 | fetchData({ formID }).then((r) => { 46 | setLoaded(true); 47 | setData(r.data.getForm); 48 | }); 49 | } 50 | }); 51 | 52 | const routes = [ 53 | { 54 | path: '/', 55 | name: 'Home', 56 | }, 57 | { 58 | path: '/form/view-forms', 59 | name: 'Forms', 60 | }, 61 | { 62 | path: `#`, 63 | name: isLoaded ? `${data.name}` : formID, 64 | }, 65 | ]; 66 | 67 | const formStat = ( 68 |
    69 | 70 | new Date()) 77 | ? 'Active' 78 | : 'Inactive' 79 | } 80 | /> 81 | 82 |
    83 | ); 84 | 85 | const formActions = ( 86 |
    87 | 88 | 91 | 92 | 95 |
    96 | ); 97 | 98 | const headerContent = ( 99 |
    100 |
    101 |
    {formStat}
    102 |
    103 | ); 104 | 105 | const panes = ( 106 | 107 | 108 | We are working on it

    } 112 | /> 113 |
    114 | 115 | {isLoaded ? : null} 116 | 117 |
    118 | ); 119 | 120 | return ( 121 | 122 | 134 | 135 | ); 136 | }; 137 | 138 | export default FormId; 139 | -------------------------------------------------------------------------------- /components/links.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | DashboardOutlined, 4 | DeploymentUnitOutlined, 5 | ScheduleOutlined, 6 | CalendarOutlined, 7 | FormOutlined, 8 | GlobalOutlined, 9 | UserOutlined, 10 | SettingOutlined, 11 | LogoutOutlined, 12 | ContainerOutlined, 13 | } from '@ant-design/icons'; 14 | 15 | export const links = [ 16 | { 17 | title: 'Dashboard', 18 | key: '/', 19 | icon: , 20 | }, 21 | { 22 | title: 'Attendance', 23 | key: 'attendance', 24 | icon: , 25 | items: [ 26 | { 27 | key: 'dashboard', 28 | title: 'Dashboard', 29 | }, 30 | // { 31 | // key: 'individual-report', 32 | // title: 'Individual Report', 33 | // }, 34 | { 35 | key: 'live-report', 36 | title: 'Live Attendance', 37 | }, 38 | { 39 | key: 'daily-report', 40 | title: 'Daily Report', 41 | }, 42 | { 43 | key: 'stats', 44 | title: 'Stats', 45 | }, 46 | ], 47 | }, 48 | { 49 | title: 'Status Updates', 50 | key: 'status-updates', 51 | icon: , 52 | items: [ 53 | { 54 | key: 'dashboard', 55 | title: 'Dashboard', 56 | }, 57 | { 58 | key: 'individual-report', 59 | title: 'Individual Report', 60 | }, 61 | { 62 | key: 'daily-report', 63 | title: 'Daily Report', 64 | }, 65 | { 66 | key: 'messages', 67 | title: 'Messages', 68 | }, 69 | { 70 | key: 'stats', 71 | title: 'Stats', 72 | }, 73 | ], 74 | }, 75 | { 76 | title: 'Calendar', 77 | key: 'calendar', 78 | icon: , 79 | items: [ 80 | { 81 | key: 'create-event', 82 | title: 'Create Event', 83 | }, 84 | { 85 | key: 'view-calendar', 86 | title: 'View Calendar', 87 | }, 88 | ], 89 | }, 90 | { 91 | title: 'Forms', 92 | key: 'forms', 93 | icon: , 94 | items: [ 95 | { 96 | key: 'create-forms', 97 | title: 'Create Forms', 98 | }, 99 | { 100 | key: 'view-forms', 101 | title: 'View Forms', 102 | }, 103 | ], 104 | }, 105 | { 106 | title: 'Blog', 107 | key: 'blog', 108 | icon: , 109 | items: [ 110 | { 111 | key: 'create-blog', 112 | title: 'Create Blog', 113 | }, 114 | ], 115 | }, 116 | { 117 | title: 'Events', 118 | key: 'events', 119 | icon: , 120 | items: [ 121 | { 122 | key: 'check-in', 123 | title: 'QR Scanner', 124 | }, 125 | ], 126 | }, 127 | { 128 | title: 'Account', 129 | key: 'account', 130 | icon: , 131 | items: [ 132 | // { 133 | // key: 'profile', 134 | // title: 'My Profile', 135 | // }, 136 | { 137 | key: 'settings', 138 | title: 'Settings', 139 | }, 140 | ], 141 | }, 142 | { 143 | title: 'Admin', 144 | key: 'admin', 145 | icon: , 146 | adminExclusive: true, 147 | items: [ 148 | { 149 | key: 'manage-users', 150 | title: 'Manage Users', 151 | }, 152 | ], 153 | }, 154 | // { 155 | // title: 'Settings', 156 | // key: 'settings', 157 | // icon: , 158 | // items: [ 159 | // { 160 | // key: 'general', 161 | // title: 'General Settings', 162 | // }, 163 | // { 164 | // key: 'privacy', 165 | // title: 'Privacy', 166 | // }, 167 | // { 168 | // key: 'appearance', 169 | // title: 'Appearance', 170 | // }, 171 | // { 172 | // key: 'notifications', 173 | // title: 'Notifications', 174 | // }, 175 | // ], 176 | // }, 177 | { 178 | title: 'Logout', 179 | key: 'logout', 180 | icon: , 181 | }, 182 | ]; 183 | -------------------------------------------------------------------------------- /pages/events/check-in.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import dynamic from 'next/dynamic'; 3 | 4 | // antd components 5 | import Card from 'antd/lib/card'; 6 | import Button from 'antd/lib/button'; 7 | 8 | import { getApplicant as query } from '../../utils/queries.js'; 9 | import { CheckIn as Mutation } from '../../utils/mutations'; 10 | import TitleBar from '../../components/titlebar'; 11 | import Base from '../../components/base'; 12 | import dataFetch from '../../utils/dataFetch'; 13 | 14 | const QrReader = dynamic(() => import('react-qr-reader'), { ssr: false }); 15 | 16 | const CheckIn = (props) => { 17 | const [data, setData] = useState(''); 18 | const [result, setResult] = useState('No Results'); 19 | const [status, setStatus] = useState(''); 20 | const [errorText, setErrorText] = useState(''); 21 | const [submitError, setSubmitError] = useState(''); 22 | 23 | const delay = 200; 24 | 25 | function handleScan(scanResult) { 26 | if (scanResult) { 27 | if (result !== scanResult) { 28 | setResult(scanResult); 29 | setStatus(''); 30 | setErrorText(''); 31 | scanQR(); 32 | } 33 | } 34 | } 35 | 36 | const scanQR = async () => { 37 | const variables = { hash: result }; 38 | const response = await dataFetch({ query, variables }); 39 | if (!Object.prototype.hasOwnProperty.call(response, 'errors')) { 40 | setData(response.data); 41 | } else { 42 | setErrorText(response.errors[0].message); 43 | setData(''); 44 | setSubmitError(''); 45 | } 46 | }; 47 | 48 | const submitQR = async () => { 49 | const variables = { appID: data.getApplicant.id }; 50 | const response = await dataFetch({ query: Mutation, variables }); 51 | if (!Object.prototype.hasOwnProperty.call(response, 'errors')) { 52 | setStatus(response.data.checkIn.status); 53 | } else { 54 | setSubmitError(response.errors[0].message); 55 | setErrorText(''); 56 | } 57 | }; 58 | 59 | const routes = [ 60 | { 61 | path: '/', 62 | name: 'Home', 63 | }, 64 | { 65 | path: '/events', 66 | name: 'Events', 67 | }, 68 | { 69 | path: '/check-in', 70 | name: 'Check In', 71 | }, 72 | ]; 73 | 74 | return ( 75 | 76 | 77 |
    78 |
    { 80 | submitQR(); 81 | e.preventDefault(); 82 | }} 83 | > 84 | 85 | {status === 'success' ? ( 86 |
    Successfully Checked In
    87 | ) : null} 88 | {submitError !== '' ? ( 89 |
    {submitError}
    90 | ) : null} 91 | {status === '' && submitError === '' ? ( 92 | data !== '' ? ( 93 |
    94 | 95 |

    Details

    96 |
    {data.getApplicant.id}
    97 |
    {data.getApplicant.name}
    98 |
    {data.getApplicant.formData[1].value}
    99 |
    {data.getApplicant.formData[0].value}
    100 |
    101 |
    102 |
    110 |
    111 | ) : null 112 | ) : null} 113 | {errorText !== '' ? ( 114 |
    {errorText}
    115 | ) : null} 116 | 117 |
    118 | 119 | ); 120 | }; 121 | 122 | export default CheckIn; 123 | -------------------------------------------------------------------------------- /components/login/loginForm.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useRouter } from 'next/router'; 3 | import Cookies from 'universal-cookie'; 4 | import { UserOutlined, LockOutlined } from '@ant-design/icons'; 5 | import Link from 'next/link'; 6 | 7 | // antd components 8 | import Card from 'antd/lib/card'; 9 | import Button from 'antd/lib/button'; 10 | import Form from 'antd/lib/form'; 11 | import Checkbox from 'antd/lib/checkbox'; 12 | import Input from 'antd/lib/input'; 13 | 14 | import dataFetch from '../../utils/dataFetch'; 15 | import { TokenAuth as query } from '../../utils/mutations'; 16 | 17 | const cookies = new Cookies(); 18 | 19 | const LoginForm = () => { 20 | const router = useRouter(); 21 | const [cookiesSet, setCookies] = useState(false); 22 | const [authFail, setAuthFail] = useState(false); 23 | const [loading, setLoading] = useState(false); 24 | 25 | useEffect(() => { 26 | const token = cookies.get('token'); 27 | if (token != null) { 28 | setCookies(true); 29 | router.push('/'); 30 | } 31 | }); 32 | 33 | const login = async (variables) => await dataFetch({ query, variables }); 34 | 35 | const onFinish = (values) => { 36 | login(values).then((response) => { 37 | if (!Object.prototype.hasOwnProperty.call(response, 'errors')) { 38 | const tokenMaxAge = 39 | response.data.tokenAuth.payload.exp - 40 | response.data.tokenAuth.payload.origIat; 41 | cookies.set('token', response.data.tokenAuth.token, { 42 | path: '/', 43 | maxAge: tokenMaxAge, 44 | }); 45 | cookies.set('refreshToken', response.data.tokenAuth.refreshToken, { 46 | path: '/', 47 | maxAge: response.data.tokenAuth.refreshExpiresIn, 48 | }); 49 | cookies.set('username', values.username, { path: '/' }); 50 | cookies.set('expiry', response.data.tokenAuth.payload.exp); 51 | setCookies(true); 52 | router.push('/'); 53 | } else { 54 | setAuthFail(true); 55 | setLoading(false); 56 | } 57 | }); 58 | }; 59 | 60 | const errorMessage = ( 61 |
    Please enter vaild credentials
    62 | ); 63 | 64 | const onFinishFailed = (errorInfo) => { 65 | console.error(errorInfo); 66 | }; 67 | 68 | return !loading ? ( 69 | 70 | CMS Logo 71 | {authFail ? errorMessage : null} 72 |
    80 | 85 | } 87 | placeholder="Username" 88 | /> 89 | 90 | 95 | } 97 | type="password" 98 | placeholder="Password" 99 | /> 100 | 101 | 102 | Remember me 103 | 104 | 105 | 112 | 113 | Forgot Password? 114 | 115 |
    116 | Don't have account? Register 117 | 118 |
    119 | ) : ( 120 |

    Loading

    121 | ); 122 | }; 123 | 124 | export default LoginForm; 125 | -------------------------------------------------------------------------------- /components/base.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import Cookies from 'universal-cookie'; 3 | import { useRouter } from 'next/router'; 4 | import moment from 'moment'; 5 | import PropTypes from 'prop-types'; 6 | 7 | import Sidebar from '../components/sidebar'; 8 | import dataFetch from '../utils/dataFetch'; 9 | import Header from './header'; 10 | import LoadingScreen from './loadingScreen'; 11 | import AuthRequired from './authRequired'; 12 | 13 | const cookies = new Cookies(); 14 | 15 | const Base = ({ 16 | children, 17 | title, 18 | adminRequired = false, 19 | verificationRequired = true, 20 | }) => { 21 | const router = useRouter(); 22 | const [loaded, setLoaded] = useState(false); 23 | const [userLoaded, setUserLoaded] = useState(false); 24 | const [data, setData] = useState({}); 25 | 26 | const refreshTokenQuery = ` 27 | mutation refresh($refresh: String!) { 28 | refreshToken(refreshToken: $refresh) { 29 | token 30 | refreshToken 31 | payload 32 | refreshExpiresIn 33 | } 34 | } 35 | `; 36 | const refreshTokens = async (refreshTokenVariables) => 37 | dataFetch({ query: refreshTokenQuery, variables: refreshTokenVariables }); 38 | const fetchData = async () => 39 | await dataFetch({ query: `{ isAdmin isClubMember }` }); 40 | 41 | useEffect(() => { 42 | const token = cookies.get('token'); 43 | const refreshToken = cookies.get('refreshToken'); 44 | const expiry = cookies.get('expiry'); 45 | const fetchStatus = () => { 46 | if (!userLoaded) { 47 | fetchData().then((r) => { 48 | setData(r.data); 49 | }).finally(() => setUserLoaded(true)); 50 | } 51 | }; 52 | if (!loaded) { 53 | if (token !== undefined || refreshToken !== undefined) { 54 | fetchStatus(); 55 | if (moment().unix() + 50 > expiry) { 56 | const refreshTokenVariables = { refresh: refreshToken }; 57 | refreshTokens(refreshTokenVariables).then((response) => { 58 | if (!Object.prototype.hasOwnProperty.call(response, 'errors')) { 59 | const tokenMaxAge = 60 | response.data.refreshToken.payload.exp - 61 | response.data.refreshToken.payload.origIat; 62 | cookies.set('token', response.data.refreshToken.token, { 63 | path: '/', 64 | maxAge: tokenMaxAge, 65 | }); 66 | cookies.set('refreshToken', response.data.refreshToken.refreshToken, { 67 | path: '/', 68 | maxAge: response.data.refreshToken.refreshExpiresIn, 69 | }); 70 | cookies.set('username', response.data.refreshToken.payload.username, { 71 | path: '/', 72 | maxAge: response.data.refreshToken.refreshExpiresIn, 73 | }); 74 | cookies.set('expiry', response.data.refreshToken.payload.exp, { 75 | path: '/', 76 | maxAge: response.data.refreshToken.refreshExpiresIn, 77 | }); 78 | } else { 79 | router.push('/logout'); 80 | } 81 | }); 82 | } 83 | } else { 84 | router.push('/login'); 85 | } 86 | setLoaded(true); 87 | } 88 | }, []); 89 | 90 | return loaded ? ( 91 | adminRequired || verificationRequired ? ( 92 | 99 |
    100 | 101 |
    {children}
    102 |
    103 | 104 | ) : ( 105 | 106 |
    107 | 108 |
    {children}
    109 |
    110 | 111 | ) 112 | ) : ( 113 | 114 | ); 115 | }; 116 | 117 | Base.propTypes = { 118 | title: PropTypes.string, 119 | children: PropTypes.node, 120 | adminRequired: PropTypes.bool, 121 | verificationRequired: PropTypes.bool, 122 | }; 123 | 124 | export default Base; 125 | -------------------------------------------------------------------------------- /modules/attendance/components/TrendGraph.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import classnames from 'classnames'; 3 | import { Line } from 'react-chartjs-2'; 4 | import Moment from 'moment'; 5 | import { extendMoment } from 'moment-range'; 6 | import PropTypes from 'prop-types'; 7 | 8 | // antd components 9 | import Card from 'antd/lib/card'; 10 | 11 | const moment = extendMoment(Moment); 12 | 13 | const TrendGraph = ({ isLoaded, data }) => { 14 | const [type, setType] = useState('attendee'); 15 | 16 | const x = []; 17 | const y = []; 18 | data.map((r) => { 19 | x.push(r.date); 20 | y.push(r.membersPresent); 21 | }); 22 | 23 | const xdurr = []; 24 | const ydurr = []; 25 | data.map((r) => { 26 | xdurr.push(r.date); 27 | ydurr.push(moment.duration(r.avgDuration).asHours().toFixed(2)); 28 | }); 29 | let options = { 30 | maintainAspectRatio: false, 31 | legend: { 32 | display: false, 33 | }, 34 | tooltips: { 35 | backgroundColor: '#f5f5f5', 36 | titleFontColor: '#333', 37 | bodyFontColor: '#666', 38 | bodySpacing: 4, 39 | xPadding: 12, 40 | mode: 'nearest', 41 | intersect: 0, 42 | position: 'nearest', 43 | }, 44 | responsive: true, 45 | scales: { 46 | yAxes: [ 47 | { 48 | barPercentage: 1.6, 49 | gridLines: { 50 | drawBorder: false, 51 | color: 'rgba(29,140,248,0.0)', 52 | zeroLineColor: 'transparent', 53 | }, 54 | ticks: { 55 | suggestedMin: 0, 56 | suggestedMax: 5, 57 | padding: 20, 58 | fontColor: '#9a9a9a', 59 | }, 60 | }, 61 | ], 62 | xAxes: [ 63 | { 64 | barPercentage: 1.6, 65 | gridLines: { 66 | drawBorder: false, 67 | color: 'rgba(29,140,248,0.1)', 68 | zeroLineColor: 'transparent', 69 | }, 70 | ticks: { 71 | padding: 20, 72 | fontColor: '#9a9a9a', 73 | }, 74 | }, 75 | ], 76 | }, 77 | }; 78 | 79 | let AttendanceGraph = { 80 | data: (canvas) => { 81 | let ctx = canvas.getContext('2d'); 82 | 83 | let gradientStroke = ctx.createLinearGradient(0, 2300, 0, 50); 84 | 85 | gradientStroke.addColorStop(1, 'rgba(29,140,248,0.2)'); 86 | gradientStroke.addColorStop(0.4, 'rgba(29,140,248,0.0)'); 87 | gradientStroke.addColorStop(0, 'rgba(29,140,248,0)'); //blue colors 88 | 89 | return { 90 | labels: type === 'attendee' ? x : xdurr, 91 | datasets: [ 92 | { 93 | label: 'Attendance Graph', 94 | fill: true, 95 | backgroundColor: gradientStroke, 96 | borderColor: '#1f8ef1', 97 | borderWidth: 2, 98 | borderDash: [], 99 | borderDashOffset: 0.0, 100 | pointBackgroundColor: '#1f8ef1', 101 | pointBorderColor: 'rgba(255,255,255,0)', 102 | pointHoverBackgroundColor: '#1f8ef1', 103 | pointBorderWidth: 20, 104 | pointHoverRadius: 4, 105 | pointHoverBorderWidth: 15, 106 | pointRadius: 4, 107 | data: type === 'attendee' ? y : ydurr, 108 | }, 109 | ], 110 | }; 111 | }, 112 | }; 113 | 114 | return ( 115 | 116 |
    117 |
    118 |
    Attendance Trends
    119 |
    120 |
    121 | 130 |
    131 |
    132 |
    136 | {isLoaded ? : null} 137 |
    138 |
    139 | ); 140 | }; 141 | 142 | TrendGraph.propTypes = { 143 | data: PropTypes.object, 144 | isLoaded: PropTypes.bool, 145 | }; 146 | 147 | export default TrendGraph; 148 | -------------------------------------------------------------------------------- /modules/forms/fieldEditor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | // antd components 5 | import Select from 'antd/lib/select'; 6 | import Switch from 'antd/lib/switch'; 7 | 8 | const FieldEditor = ({ field }) => { 9 | const fieldTypes = [ 10 | { 11 | name: 'Commonly Used', 12 | options: [ 13 | { name: 'Email', value: 'email' }, 14 | { name: 'Phone', value: 'phone' }, 15 | { name: 'Age', value: 'age' }, 16 | { name: 'Gender', value: 'gender' }, 17 | ], 18 | }, 19 | { 20 | name: 'String', 21 | options: [ 22 | { name: 'String', value: 'string' }, 23 | { name: 'Paragraph', value: 'paragraph' }, 24 | { name: 'Email', value: 'email' }, 25 | { name: 'Phone', value: 'phone' }, 26 | ], 27 | }, 28 | { 29 | name: 'Number', 30 | options: [ 31 | { name: 'Number', value: 'number' }, 32 | { name: 'Integer', value: 'integer' }, 33 | { name: 'Whole Number', value: 'wholeNumber' }, 34 | ], 35 | }, 36 | { 37 | name: 'Select', 38 | options: [ 39 | { name: 'Select / Radio', value: 'radio' }, 40 | { name: 'Multi-Select / Checkbox', value: 'checkbox' }, 41 | ], 42 | }, 43 | { 44 | name: 'Upload', 45 | options: [ 46 | { name: 'Image Upload', value: 'image', disabled: true }, 47 | { name: 'File Upload', value: 'file', disabled: true }, 48 | ], 49 | }, 50 | { 51 | name: 'Other', 52 | options: [ 53 | { name: 'Custom Regex', value: 'regex' }, 54 | { name: 'Slot', value: 'slot' }, 55 | ], 56 | }, 57 | ]; 58 | 59 | const renderFieldTypeSelector = ( 60 |
    61 | 62 | 78 |
    79 | ); 80 | 81 | return ( 82 |
    83 |
    84 |
    85 | 86 | 87 | 88 | You cannot change key of a field, once entries for the form has been 89 | received. 90 | 91 |
    92 |
    {renderFieldTypeSelector}
    93 | {field.type === 'regex' ? ( 94 |
    95 | 96 | 97 |
    98 | ) : null} 99 |
    100 |
    101 |
    102 | 103 |
    104 | 105 |
    106 |
    107 |
    108 | 109 |
    110 | 111 |
    112 |
    113 |
    114 | 115 |
    116 | 117 |
    118 |
    119 |
    120 |
    121 |
    122 | 123 | 124 |
    125 |
    126 | 127 | 128 |
    129 |
    130 |
    131 | ); 132 | }; 133 | 134 | FieldEditor.propTypes = { 135 | field: PropTypes.object, 136 | }; 137 | 138 | export default FieldEditor; 139 | -------------------------------------------------------------------------------- /pages/forms/view-forms.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { 3 | CheckCircleTwoTone, 4 | CloseCircleTwoTone, 5 | EyeOutlined, 6 | LinkOutlined, 7 | EditOutlined, 8 | DeleteOutlined, 9 | } from '@ant-design/icons'; 10 | import Link from 'next/link'; 11 | 12 | // antd components 13 | import Table from 'antd/lib/table'; 14 | import Button from 'antd/lib/button'; 15 | import Badge from 'antd/lib/badge'; 16 | 17 | import dataFetch from '../../utils/dataFetch'; 18 | import Base from '../../components/base'; 19 | import TitleBar from '../../components/titlebar'; 20 | 21 | const ViewForms = (props) => { 22 | const [data, setData] = useState(''); 23 | const [isLoaded, setLoaded] = useState(false); 24 | 25 | const query = `query{ 26 | viewForms 27 | { 28 | id 29 | name 30 | isActive 31 | allowMultiple 32 | entriesCount 33 | submissionDeadline 34 | applicationLimit 35 | } 36 | }`; 37 | 38 | const fetchData = async () => dataFetch({ query }); 39 | 40 | useEffect(() => { 41 | if (!isLoaded) { 42 | fetchData().then((r) => { 43 | setData(r.data.viewForms); 44 | setLoaded(true); 45 | }); 46 | } 47 | }); 48 | 49 | const routes = [ 50 | { 51 | path: '/', 52 | name: 'Home', 53 | }, 54 | { 55 | path: '/forms', 56 | name: 'Forms', 57 | }, 58 | { 59 | path: '/forms/view-forms', 60 | name: 'View Forms', 61 | }, 62 | ]; 63 | 64 | const columns = [ 65 | { 66 | title: 'Name', 67 | dataIndex: 'name', 68 | key: 'name', 69 | sorter: (a, b) => a.name.localeCompare(b.name), 70 | render: (name, obj) => {name}, 71 | }, 72 | { 73 | title: 'Entries', 74 | dataIndex: 'entriesCount', 75 | key: 'entriesCount', 76 | render: (entriesCount, obj) => ( 77 | 78 | 79 | 80 | ), 81 | }, 82 | { 83 | title: 'Active', 84 | dataIndex: 'isActive', 85 | key: 'isActive', 86 | render: (status) => , 87 | }, 88 | { 89 | title: 'Multiple Entries', 90 | dataIndex: 'allowMultiple', 91 | key: 'allowMultiple', 92 | filters: [ 93 | { 94 | text: 'Allowed', 95 | value: true, 96 | }, 97 | { 98 | text: 'Denied', 99 | value: false, 100 | }, 101 | ], 102 | onFilter: (value, record) => record.allowMultiple === value, 103 | render: (status) => 104 | status ? ( 105 | 106 | ) : ( 107 | 108 | ), 109 | }, 110 | { 111 | title: 'Submission Deadline', 112 | dataIndex: 'submissionDeadline', 113 | key: 'submissionDeadline', 114 | render: (timestamp) => 115 | timestamp ? ( 116 | new Date(timestamp).toLocaleString() 117 | ) : ( 118 | 119 | ), 120 | }, 121 | { 122 | title: 'Admission Limit', 123 | dataIndex: 'applicationLimit', 124 | key: 'applicationLimit', 125 | }, 126 | { 127 | title: 'Actions', 128 | dataIndex: '', 129 | key: 'x', 130 | render: () => ( 131 |
    132 |
    137 | ), 138 | }, 139 | ]; 140 | 141 | return ( 142 | 143 | 148 |
    149 | 155 | 156 | 157 | ); 158 | }; 159 | 160 | export default ViewForms; 161 | -------------------------------------------------------------------------------- /modules/attendance/components/DailyReportCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Moment from 'moment'; 3 | import { extendMoment } from 'moment-range'; 4 | import PropTypes from 'prop-types'; 5 | 6 | // antd components 7 | import Card from 'antd/lib/card'; 8 | import List from 'antd/lib/list'; 9 | import Avatar from 'antd/lib/avatar'; 10 | import Badge from 'antd/lib/badge'; 11 | import Tabs from 'antd/lib/tabs'; 12 | 13 | const { TabPane } = Tabs; 14 | const moment = extendMoment(Moment); 15 | 16 | const DailyReportCard = ({ data, isLoaded }) => { 17 | const membersCard = (members, status) => ( 18 | ( 23 | 24 | 25 | 34 | } 35 | title={ 36 | 37 | 38 | {m.member.firstName} {m.member.lastName} 39 | 40 | 41 | } 42 | description={ 43 |
    44 | {status === 'late' 45 | ? `${moment 46 | .duration( 47 | moment(data.date).set('hour', 17).set('minute', 15) - 48 | moment(m.firstSeen) 49 | ) 50 | .asMinutes()}mins late, came to lab at ${moment( 51 | m.firstSeen 52 | ).format('hh:mm')}` 53 | : status === 'present' 54 | ? m.duration 55 | : status === 'absent' 56 | ? m.lastSeen 57 | ? `Last seen ${moment 58 | .duration(moment().diff(m.lastSeen)) 59 | .humanize()} ago` 60 | : 'No previous Record' 61 | : null} 62 |
    63 | } 64 | /> 65 |
    66 |
    67 | )} 68 | /> 69 | ); 70 | 71 | const lateMembers = isLoaded 72 | ? data.membersPresent.filter((m) => { 73 | if ( 74 | moment(m.firstSeen) > moment(data.date).set('hour', 17).set('minute', 15) 75 | ) 76 | return m; 77 | }) 78 | : null; 79 | 80 | return ( 81 |
    82 | 83 | 87 | Present 88 | 92 | 93 | } 94 | > 95 | {isLoaded ? membersCard(data.membersPresent, 'present') : null} 96 | 97 | 102 | Absent 103 | 107 | 108 | } 109 | > 110 | {isLoaded ? membersCard(data.membersAbsent, 'absent') : null} 111 | 112 | 117 | Late To Lab 118 | 122 | 123 | } 124 | > 125 | {isLoaded ? membersCard(lateMembers, 'late') : null} 126 | 127 | 128 |
    129 | ); 130 | }; 131 | 132 | DailyReportCard.propTypes = { 133 | data: PropTypes.object, 134 | isLoaded: PropTypes.bool, 135 | }; 136 | 137 | export default DailyReportCard; 138 | -------------------------------------------------------------------------------- /components/event/eventForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { extendMoment } from 'moment-range'; 3 | import Moment from 'moment'; 4 | import Link from 'next/link'; 5 | 6 | // antd components 7 | import DatePicker from 'antd/lib/date-picker'; 8 | import Button from 'antd/lib/button'; 9 | import Result from 'antd/lib/result'; 10 | 11 | import dataFetch from '../../utils/dataFetch'; 12 | 13 | const EventForm = () => { 14 | const moment = extendMoment(Moment); 15 | const [isLoading, setLoaded] = useState(false); 16 | const [name, setName] = useState(''); 17 | const [details, setDetails] = useState(''); 18 | const [startDate, setStartDate] = useState(moment().format('YYYY-MM-DD')); 19 | const [endDate, setEndDate] = useState(moment().format('YYYY-MM-DD')); 20 | const [error, setErrorText] = useState(''); 21 | const [success, setSuccessText] = useState(''); 22 | 23 | const query = ` 24 | mutation createEvent($name: String!, $details: String!, $startDate: Date!, $endDate: Date!){ 25 | createEvent(details:$details, endTimestamp:$endDate, startTimestamp: $startDate, name:$name){ 26 | id 27 | } 28 | } 29 | `; 30 | 31 | const submitForm = async (variables) => dataFetch({ query, variables }); 32 | 33 | const register = () => { 34 | const variables = { name, details, startDate, endDate }; 35 | submitForm(variables).then((r) => { 36 | if (Object.prototype.hasOwnProperty.call(r, 'errors')) { 37 | setErrorText(r.errors[0].message); 38 | } else { 39 | setSuccessText(r.data.id); 40 | setErrorText(''); 41 | } 42 | }); 43 | }; 44 | return !isLoading ? ( 45 |
    { 48 | setLoaded(true); 49 | register(); 50 | e.preventDefault(); 51 | }} 52 | > 53 |
    54 |
    55 |
    56 | 57 |
    58 | setName(e.target.value)} 64 | /> 65 |
    66 |
    67 |
    68 | 69 |
    70 | { 73 | setLoaded(false); 74 | setStartDate(e.format('YYYY-MM-DD')); 75 | }} 76 | format="DD-MM-YYYY" 77 | value={moment(startDate)} 78 | /> 79 |
    80 |
    81 |
    82 | 83 |
    84 | { 87 | setLoaded(false); 88 | setEndDate(e.format('YYYY-MM-DD')); 89 | }} 90 | format="DD-MM-YYYY" 91 | value={moment(endDate)} 92 | /> 93 |
    94 |
    95 |
    96 | 97 |
    98 |