diff --git a/src/assets/Feedback/Modal/bauhaus-feedback-1.png b/src/assets/Feedback/Modal/bauhaus-feedback-1.png new file mode 100644 index 0000000..4b94f65 Binary files /dev/null and b/src/assets/Feedback/Modal/bauhaus-feedback-1.png differ diff --git a/src/assets/Feedback/Modal/bauhaus-feedback-2.svg b/src/assets/Feedback/Modal/bauhaus-feedback-2.svg new file mode 100644 index 0000000..5a5855d --- /dev/null +++ b/src/assets/Feedback/Modal/bauhaus-feedback-2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/Feedback/Page/bauhaus-feedback-1.png b/src/assets/Feedback/Page/bauhaus-feedback-1.png new file mode 100644 index 0000000..3c0cce1 Binary files /dev/null and b/src/assets/Feedback/Page/bauhaus-feedback-1.png differ diff --git a/src/assets/Feedback/Page/bauhaus-feedback-2.png b/src/assets/Feedback/Page/bauhaus-feedback-2.png new file mode 100644 index 0000000..ae8e31d Binary files /dev/null and b/src/assets/Feedback/Page/bauhaus-feedback-2.png differ diff --git a/src/assets/Feedback/Page/bauhaus-feedback-3.png b/src/assets/Feedback/Page/bauhaus-feedback-3.png new file mode 100644 index 0000000..bbbf4a2 Binary files /dev/null and b/src/assets/Feedback/Page/bauhaus-feedback-3.png differ diff --git a/src/assets/Feedback/Page/bauhaus-feedback-4.png b/src/assets/Feedback/Page/bauhaus-feedback-4.png new file mode 100644 index 0000000..40b0ec6 Binary files /dev/null and b/src/assets/Feedback/Page/bauhaus-feedback-4.png differ diff --git a/src/assets/GoogleCalendar.png b/src/assets/GoogleCalendar.png new file mode 100644 index 0000000..90a38a6 Binary files /dev/null and b/src/assets/GoogleCalendar.png differ diff --git a/src/assets/GoogleCalendarModal/mulai.png b/src/assets/GoogleCalendarModal/mulai.png new file mode 100644 index 0000000..a3b40ca Binary files /dev/null and b/src/assets/GoogleCalendarModal/mulai.png differ diff --git a/src/assets/GoogleCalendarModal/step1.png b/src/assets/GoogleCalendarModal/step1.png new file mode 100644 index 0000000..2c3d55f Binary files /dev/null and b/src/assets/GoogleCalendarModal/step1.png differ diff --git a/src/assets/GoogleCalendarModal/step2.png b/src/assets/GoogleCalendarModal/step2.png new file mode 100644 index 0000000..ddbbcfe Binary files /dev/null and b/src/assets/GoogleCalendarModal/step2.png differ diff --git a/src/assets/GoogleCalendarModal/step3.png b/src/assets/GoogleCalendarModal/step3.png new file mode 100644 index 0000000..23dceb7 Binary files /dev/null and b/src/assets/GoogleCalendarModal/step3.png differ diff --git a/src/assets/GoogleCalendarModal/step4.png b/src/assets/GoogleCalendarModal/step4.png new file mode 100644 index 0000000..ae01141 Binary files /dev/null and b/src/assets/GoogleCalendarModal/step4.png differ diff --git a/src/assets/GoogleCalendarModal/step5.png b/src/assets/GoogleCalendarModal/step5.png new file mode 100644 index 0000000..8d13fab Binary files /dev/null and b/src/assets/GoogleCalendarModal/step5.png differ diff --git a/src/assets/GoogleCalendarModal/step6.png b/src/assets/GoogleCalendarModal/step6.png new file mode 100644 index 0000000..b903458 Binary files /dev/null and b/src/assets/GoogleCalendarModal/step6.png differ diff --git a/src/assets/GoogleCalendarModal/step7.png b/src/assets/GoogleCalendarModal/step7.png new file mode 100644 index 0000000..9a2c279 Binary files /dev/null and b/src/assets/GoogleCalendarModal/step7.png differ diff --git a/src/assets/GoogleCalendarModal/step8.png b/src/assets/GoogleCalendarModal/step8.png new file mode 100644 index 0000000..56119a9 Binary files /dev/null and b/src/assets/GoogleCalendarModal/step8.png differ diff --git a/src/assets/Logo/RistekLogo-dark.svg b/src/assets/Logo/RistekLogo-dark.svg new file mode 100644 index 0000000..0bfabcc --- /dev/null +++ b/src/assets/Logo/RistekLogo-dark.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/Logo/RistekLogo-light.svg b/src/assets/Logo/RistekLogo-light.svg new file mode 100644 index 0000000..e310f7e --- /dev/null +++ b/src/assets/Logo/RistekLogo-light.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/config.js b/src/config.js index 20af88e..572c783 100644 --- a/src/config.js +++ b/src/config.js @@ -12,7 +12,8 @@ const config = { BASE_URL: "/", }, development: { - API_BASE_URL: "http://localhost:5000/susunjadwal/api", + // API_BASE_URL: "http://localhost:5000/susunjadwal/api", + API_BASE_URL: "https://stg.api.susunjadwal.cs.ui.ac.id/susunjadwal/api", BASE_URL: "/", }, }; diff --git a/src/containers/Admin/Feedbacks/feedback.js b/src/containers/Admin/Feedbacks/feedback.js new file mode 100644 index 0000000..22e1140 --- /dev/null +++ b/src/containers/Admin/Feedbacks/feedback.js @@ -0,0 +1,37 @@ +export const dummyFeedbacks = [ + { id: 1, username: 'farrel.altaf', rating: 5, feedback: 'Bagus banget inimah susun jadwal nggak membingungkan, makasih!', time: '17/04/2024 20:08', status: 'diproses' }, + { id: 2, username: 'johndoe', rating: 5, feedback: 'Sangat membantu, terima kasih!', time: '18/04/2024 10:15', status: 'diproses' }, + { id: 3, username: 'janedoe', rating: 5, feedback: 'Cukup membantu, tetapi bisa lebih baik.', time: '19/04/2024 14:23', status: 'belum_diproses' }, + { id: 4, username: 'alice', rating: 5, feedback: 'Luar biasa! Sangat merekomendasikan.', time: '20/04/2024 08:45', status: 'diproses' }, + { id: 5, username: 'bob', rating: 5, feedback: 'Tidak puas, banyak bug.', time: '21/04/2024 19:05', status: 'belum_diproses' }, + { id: 6, username: 'charlie', rating: 5, feedback: 'Bagus, tapi bisa lebih baik.', time: '22/04/2024 11:30', status: 'diproses' }, + { id: 7, username: 'dave', rating: 5, feedback: 'Kurang memuaskan.', time: '23/04/2024 17:50', status: 'belum_diproses' }, + { id: 8, username: 'eve', rating: 5, feedback: 'Biasa saja.', time: '24/04/2024 13:10', status: 'diproses' }, + { id: 9, username: 'frank', rating: 5, feedback: 'Luar biasa!', time: '25/04/2024 09:20', status: 'diproses' }, + { id: 10, username: 'grace', rating: 5, feedback: 'Sangat membantu, terima kasih!', time: '26/04/2024 16:40', status: 'diproses' }, + { id: 11, username: 'aldi.putra', rating: 5, feedback: 'Mantap, semoga kedepannya lebih bagus lagi.', time: '27/04/2024 10:30', status: 'diproses' }, + { id: 12, username: 'bella.rizky', rating: 5, feedback: 'Kurang suka, banyak yang kurang jelas.', time: '28/04/2024 14:50', status: 'belum_diproses' }, + { id: 13, username: 'cindy.sari', rating: 5, feedback: 'Biasa aja sih, tapi lumayan.', time: '29/04/2024 12:20', status: 'diproses' }, + { id: 14, username: 'doni.setiawan', rating: 5, feedback: 'Perfect! Gak ada duanya.', time: '30/04/2024 09:10', status: 'diproses' }, + { id: 15, username: 'erwin.pratama', rating: 5, feedback: 'Cukup oke, meskipun ada sedikit bug.', time: '01/05/2024 16:45', status: 'diproses' }, + { id: 16, username: 'fanny.febri', rating: 5, feedback: 'Gak sesuai ekspektasi, kecewa.', time: '02/05/2024 11:00', status: 'belum_diproses' }, + { id: 17, username: 'galih.eka', rating: 3, feedback: 'Lumayan, bisa diterima lah.', time: '03/05/2024 13:35', status: 'diproses' }, + { id: 18, username: 'hendra.satria', rating: 5, feedback: 'Bagus banget, sangat memudahkan.', time: '04/05/2024 15:20', status: 'diproses' }, + { id: 19, username: 'ika.putri', rating: 4, feedback: 'Bagus, tapi masih bisa ditingkatkan.', time: '05/05/2024 17:50', status: 'diproses' }, + { id: 20, username: 'jaka.saputra', rating: 2, feedback: 'Kurang memuaskan, banyak bug.', time: '06/05/2024 19:30', status: 'belum_diproses' }, + { id: 21, username: 'kenny.tan', rating: 3, feedback: 'Not bad, bisa lah.', time: '07/05/2024 21:10', status: 'diproses' }, + { id: 22, username: 'lina.agustina', rating: 4, feedback: 'Suka banget, sangat membantu.', time: '08/05/2024 08:50', status: 'diproses' }, + { id: 23, username: 'mario.gilang', rating: 5, feedback: 'Perfect, sangat memudahkan.', time: '09/05/2024 10:00', status: 'diproses' }, + { id: 24, username: 'nina.wulandari', rating: 2, feedback: 'Kurang jelas, banyak yang harus diperbaiki.', time: '10/05/2024 12:30', status: 'belum_diproses' }, + { id: 25, username: 'okky.ferdi', rating: 3, feedback: 'Lumayan, tapi banyak kekurangan.', time: '11/05/2024 14:10', status: 'diproses' }, + { id: 26, username: 'poppy.anggi', rating: 4, feedback: 'Bagus, saya suka.', time: '12/05/2024 16:00', status: 'diproses' }, + { id: 27, username: 'rizal.budi', rating: 1, feedback: 'Gak bagus, kecewa banget.', time: '13/05/2024 18:30', status: 'belum_diproses' }, + { id: 28, username: 'sandy.prayoga', rating: 5, feedback: 'Sangat memuaskan!', time: '14/05/2024 20:10', status: 'diproses' }, + { id: 29, username: 'tina.putri', rating: 4, feedback: 'Cukup membantu, terima kasih.', time: '15/05/2024 22:00', status: 'diproses' }, + { id: 30, username: 'utami.dewi', rating: 3, feedback: 'Biasa saja, masih bisa ditingkatkan.', time: '16/05/2024 08:10', status: 'diproses' }, +]; + +export const feedbackStats = [5, 4, 3, 2, 1].map(rating => ({ + rating, + count: dummyFeedbacks.filter(fb => fb.rating === rating).length, +})); diff --git a/src/containers/Admin/Feedbacks/index.js b/src/containers/Admin/Feedbacks/index.js new file mode 100644 index 0000000..adfed95 --- /dev/null +++ b/src/containers/Admin/Feedbacks/index.js @@ -0,0 +1,270 @@ +import React, { useState } from 'react'; +import { + Box, + Text, + Flex, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + Select, + HStack, + useColorModeValue, +} from '@chakra-ui/react'; +import { Helmet } from 'react-helmet'; +import { ChevronLeftIcon, ChevronRightIcon, StarIcon, ArrowUpIcon, ArrowDownIcon } from '@chakra-ui/icons'; +import styled from 'styled-components'; +import { dummyFeedbacks, feedbackStats } from './feedback'; + +const AdminFeedbacks = () => { + const [feedbacksPerPage, setFeedbacksPerPage] = useState(5); + const [currentPage, setCurrentPage] = useState(1); + const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); + + const totalPages = Math.ceil(dummyFeedbacks.length / feedbacksPerPage); + const sortedFeedbacks = [...dummyFeedbacks]; + + const sortedData = () => { + if (sortConfig.key !== null) { + sortedFeedbacks.sort((a, b) => { + if (sortConfig.key === 'Waktu') { + const dateA = new Date(a[sortConfig.key]); + const dateB = new Date(b[sortConfig.key]); + return sortConfig.direction === 'asc' ? dateA - dateB : dateB - dateA; + } else { + if (a[sortConfig.key] < b[sortConfig.key]) { + return sortConfig.direction === 'asc' ? -1 : 1; + } + if (a[sortConfig.key] > b[sortConfig.key]) { + return sortConfig.direction === 'asc' ? 1 : -1; + } + return 0; + } + }); + } + return sortedFeedbacks; + }; + + const currentFeedbacks = sortedData().slice( + (currentPage - 1) * feedbacksPerPage, + currentPage * feedbacksPerPage + ); + + const handlePageChange = page => { + if (page < 1 || page > totalPages) return; + setCurrentPage(page); + }; + + const handleSort = key => { + let direction = 'asc'; + if (sortConfig.key === key && sortConfig.direction === 'asc') { + direction = 'desc'; + } + setSortConfig({ key, direction }); + }; + + const averageRating = ( + dummyFeedbacks.reduce((acc, feedback) => acc + feedback.rating, 0) / dummyFeedbacks.length + ).toFixed(1); + + const getPaginationItems = () => { + const items = []; + const startPage = Math.max(1, currentPage - 2); + const endPage = Math.min(totalPages, currentPage + 2); + + if (startPage > 1) { + items.push( + handlePageChange(1)} isActive={currentPage === 1}> + 1 + + ); + if (startPage > 2) { + items.push(...); + } + } + + for (let page = startPage; page <= endPage; page++) { + items.push( + handlePageChange(page)} isActive={currentPage === page}> + {page} + + ); + } + + if (endPage < totalPages) { + if (endPage < totalPages - 1) { + items.push(...); + } + items.push( + handlePageChange(totalPages)} isActive={currentPage === totalPages}> + {totalPages} + + ); + } + + return items; + }; + + const renderSortIcon = key => { + if (sortConfig.key !== key) return ; + return sortConfig.direction === 'asc' ? : ; + }; + + const tableBg = useColorModeValue('primary.White', 'dark.LightBlack'); + const tableTextColor = useColorModeValue('secondary.MineShaft', 'dark.White'); + const selectBg = useColorModeValue('primary.White', 'dark.LightBlack'); + const borderColor = useColorModeValue('gray.200', 'gray.700'); + + return ( + + + + + Rating & Ulasan User + + + + + + {averageRating} + + + {Array(5) + .fill('') + .map((_, i) => ( + + ))} + + + {dummyFeedbacks.length} ulasan + + + + {feedbackStats.map((stat, i) => ( + + + {stat.rating} + + + + + + ))} + + + + + + + + + + + + + + + + + {currentFeedbacks.map((feedback, index) => ( + + + + + + + + + ))} + +
+ handleSort('id')}> + No {renderSortIcon('id')} + + + handleSort('username')}> + Nama User {renderSortIcon('username')} + + + handleSort('rating')}> + Rating {renderSortIcon('rating')} + + + Feedback (opsional) + + handleSort('time')}> + Waktu {renderSortIcon('time')} + + + handleSort('status')}> + Aksi {renderSortIcon('status')} + +
{(currentPage - 1) * feedbacksPerPage + index + 1}{feedback.username}{feedback.rating}{feedback.feedback}{feedback.time} + +
+
+ + + handlePageChange(currentPage - 1)} disabled={currentPage === 1}> + + Previous + + {getPaginationItems()} + handlePageChange(currentPage + 1)} disabled={currentPage === totalPages}> + Next + + + + + + Ulasan per halaman: + + +
+
+ ); +}; + +const PaginationButton = styled.button` + background: transparent; + color: var(--chakra-colors-primary-Purple); + border: ${props => (props.isActive ? '1px solid var(--chakra-colors-primary-Purple)' : 'none')}; + border-radius: 0.25rem; + padding: 0.5rem 1rem; + margin: 0 0.25rem; + cursor: pointer; + transition: background 0.3s, color 0.3s; + + &:hover { + background: var(--chakra-colors-primary-Purple); + color: white; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +`; + +export default AdminFeedbacks; diff --git a/src/containers/Admin/Login/index.js b/src/containers/Admin/Login/index.js new file mode 100644 index 0000000..bca2669 --- /dev/null +++ b/src/containers/Admin/Login/index.js @@ -0,0 +1,114 @@ +import React, { useState } from 'react'; +import { + Box, + Button, + Input, + FormControl, + FormLabel, + FormErrorMessage, + Text, + Stack, + InputGroup, + InputRightElement, + IconButton, + Image, +} from '@chakra-ui/react'; +import { Helmet } from 'react-helmet'; +import { useHistory } from 'react-router-dom'; +import { ViewIcon, ViewOffIcon } from '@chakra-ui/icons'; +import RistekLogo from "assets/Beta/LogoSunjad-light.svg"; + +const AdminLogin = () => { + const [showPassword, setShowPassword] = useState(false); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [errors, setErrors] = useState({ username: '', password: '' }); + + const handleShowClick = () => setShowPassword(!showPassword); + + const history = useHistory(); + + const handleSubmit = (e) => { + e.preventDefault(); + const validationErrors = {}; + + if (!username) { + validationErrors.username = 'Masukan username'; + } + if (!password) { + validationErrors.password = 'Masukan password'; + } + + setErrors(validationErrors); + + if (Object.keys(validationErrors).length === 0) { + if (username === 'admin' && password === '12345678') { + history.push('/feedback-recap'); + } else { + setErrors({ username: 'Invalid username or password' }); + } + } + }; + + return ( + + + + Logo + + Welcome Back! Admin + +
+ + Username * + setUsername(e.target.value)} + /> + {errors.username} + + + + Password * + + setPassword(e.target.value)} + /> + + : } + onClick={handleShowClick} + variant="ghost" + /> + + + {errors.password} + + + +
+
+
+ ); +}; + +export default AdminLogin; diff --git a/src/containers/BetaLanding/index.js b/src/containers/BetaLanding/index.js index ebe2ab0..44c7d9c 100644 --- a/src/containers/BetaLanding/index.js +++ b/src/containers/BetaLanding/index.js @@ -23,7 +23,7 @@ import { import { FAQS } from "./data"; import BauhausMobile from "assets/Beta/bauhaus-sm.svg"; -import RistekBetaLogo from "assets/Beta/Beta_Logo_Light.svg"; +import RistekBetaLogo from "assets/Logo/RistekLogo-light.svg"; import SunjadBetaLogo from "assets/Beta/Sunjad_Beta.svg"; import BetaAssetA from "assets/Beta/beta-landing-asset-1.svg"; import BetaAssetB from "assets/Beta/beta-landing-asset-2.svg"; diff --git a/src/containers/BuildSchedule/CourseClass.js b/src/containers/BuildSchedule/CourseClass.js index cb5b472..bdf8118 100644 --- a/src/containers/BuildSchedule/CourseClass.js +++ b/src/containers/BuildSchedule/CourseClass.js @@ -1,6 +1,6 @@ import React from "react"; import styled from "styled-components"; -import { useMixpanel } from "hooks/useMixpanel"; +// import { useMixpanel } from "hooks/useMixpanel"; import { useSelector, useDispatch } from "react-redux"; import { addSchedule, removeSchedule } from "redux/modules/schedules"; import { useColorModeValue } from "@chakra-ui/react"; @@ -100,7 +100,8 @@ function CourseClass({ course, courseClass }) { dispatch(removeSchedule(item)); } else { dispatch(addSchedule(item)); - useMixpanel.track("select_course"); + // TODO: Re-enable mixpanel or change to other analytics + // useMixpanel.track("select_course"); } }; diff --git a/src/containers/BuildSchedule/SelectMajor.js b/src/containers/BuildSchedule/SelectMajor.js index d8cd2ad..da612a8 100644 --- a/src/containers/BuildSchedule/SelectMajor.js +++ b/src/containers/BuildSchedule/SelectMajor.js @@ -1,5 +1,5 @@ import React from "react"; -import { useMixpanel } from "hooks/useMixpanel"; +// import { useMixpanel } from "hooks/useMixpanel"; import { Flex } from "@chakra-ui/react"; import FACULTIES from "utils/faculty-base-additional-info.json"; import { useForm } from "react-hook-form"; @@ -29,11 +29,13 @@ function SelectMajor({ theme, isMobile, setMajorSelected, show }) { } useEffect(() => { - if (selectedFaculty) useMixpanel.track("select_faculty"); + // TODO: Re-enable mixpanel or change to other analytics + // if (selectedFaculty) useMixpanel.track("select_faculty"); }, [selectedFaculty]); useEffect(() => { - if (selectedMajorName) useMixpanel.track("select_prodi"); + // TODO: Re-enable mixpanel or change to other analytics + // if (selectedMajorName) useMixpanel.track("select_prodi");/ }, [selectedMajorName]); return ( diff --git a/src/containers/BuildSchedule/index.js b/src/containers/BuildSchedule/index.js index b3e363b..c2a1356 100644 --- a/src/containers/BuildSchedule/index.js +++ b/src/containers/BuildSchedule/index.js @@ -1,6 +1,6 @@ import React, { useEffect, useState, useCallback, useRef } from "react"; import { useSelector, useDispatch } from "react-redux"; -import { useMixpanel } from "hooks/useMixpanel"; +// import { useMixpanel } from "hooks/useMixpanel"; import { Button, useColorModeValue, @@ -97,13 +97,15 @@ function BuildSchedule() { } }); - useEffect(() => { - useMixpanel.track("open_buat_jadwal"); - }, []); + // TODO: Re-enable mixpanel or change to other analytics + // useEffect(() => { + // useMixpanel.track("open_buat_jadwal"); + // }, []); useEffect(() => { if (isInitialMount.current) isInitialMount.current = false; - else useMixpanel.track("search_course"); + // TODO: Re-enable mixpanel or change to other analytics + // else useMixpanel.track("search_course"); }, [value]); return ( diff --git a/src/containers/Contributors/index.js b/src/containers/Contributors/index.js index 4d9e89e..f509699 100644 --- a/src/containers/Contributors/index.js +++ b/src/containers/Contributors/index.js @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useState } from "react"; -import { useMixpanel } from "hooks/useMixpanel"; +// import { useMixpanel } from "hooks/useMixpanel"; import { Text, Box, Button, Flex, useColorModeValue } from "@chakra-ui/react"; import { ChevronLeftIcon } from "@chakra-ui/icons"; import { Link } from "react-router-dom"; @@ -36,7 +36,8 @@ const Contributors = () => { }, [fetchContributors]); useEffect(() => { - useMixpanel.track("open_contributors"); + // TODO: Re-enable mixpanel or change to other analytics + // useMixpanel.track("open_contributors"); }, []); const MergeContributors = (contributors, otherContributors) => { @@ -90,7 +91,8 @@ const Contributors = () => { avatar={user.avatar_url} github={user.html_url} contributions={user.contributions} - onClick={() => useMixpanel.track("see_contributor_detail")} + // TODO: Re-enable mixpanel or change to other analytics + // onClick={() => useMixpanel.track("see_contributor_detail")} /> )); @@ -137,7 +139,8 @@ const Contributors = () => { useMixpanel.track("gabung_discord")} + // TODO: Re-enable mixpanel or change to other analytics + // onClick={() => useMixpanel.track("gabung_discord")} target="_blank" >