diff --git a/src/config.js b/src/config.js index 572c783..4a0e1ac 100644 --- a/src/config.js +++ b/src/config.js @@ -8,11 +8,11 @@ const config = { MIXPANEL_PROJECT_TOKEN: process.env.REACT_APP_MIXPANEL_PROJECT_TOKEN, }, production: { - API_BASE_URL: process.env.REACT_APP_AWS_BACKEND_URL, + // API_BASE_URL: process.env.REACT_APP_AWS_BACKEND_URL, + API_BASE_URL: "https://stg.api.susunjadwal.cs.ui.ac.id/susunjadwal/api", BASE_URL: "/", }, development: { - // 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 deleted file mode 100644 index 22e1140..0000000 --- a/src/containers/Admin/Feedbacks/feedback.js +++ /dev/null @@ -1,37 +0,0 @@ -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 index adfed95..82f9a8b 100644 --- a/src/containers/Admin/Feedbacks/index.js +++ b/src/containers/Admin/Feedbacks/index.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from "react"; import { Box, Text, @@ -12,62 +12,83 @@ import { 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'; + Image, + Link, +} from "@chakra-ui/react"; +import { Helmet } from "react-helmet"; +import { useHistory } from "react-router-dom"; +import { ChevronLeftIcon, ChevronRightIcon, StarIcon } from "@chakra-ui/icons"; +import styled from "styled-components"; +import { useDispatch } from "react-redux"; +import { setLoading } from "redux/modules/appState"; +import { + getReviews, + getReviewOverview, + updateReviewStatus, +} from "services/api"; +import LogoSunjad from "assets/Beta/LogoSunjad-light.svg"; const AdminFeedbacks = () => { + const [feedbacks, setFeedbacks] = useState([]); const [feedbacksPerPage, setFeedbacksPerPage] = useState(5); const [currentPage, setCurrentPage] = useState(1); - const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); + const [totalPages, setTotalPages] = useState(1); + const [averageRating, setAverageRating] = useState(0); + const [ratingCounts, setRatingCounts] = useState({}); + const dispatch = useDispatch(); + const history = useHistory(); - const totalPages = Math.ceil(dummyFeedbacks.length / feedbacksPerPage); - const sortedFeedbacks = [...dummyFeedbacks]; + useEffect(() => { + const fetchData = async () => { + const token = localStorage.getItem("admin_token"); + if (!token) { + history.push("/admin"); + return; + } - 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; - }; + dispatch(setLoading(true)); + try { + const reviewResponse = await getReviews( + token, + currentPage, + feedbacksPerPage, + ); + setFeedbacks(reviewResponse.data.reviews); + setTotalPages(reviewResponse.data.total_page); - const currentFeedbacks = sortedData().slice( - (currentPage - 1) * feedbacksPerPage, - currentPage * feedbacksPerPage - ); + const overviewResponse = await getReviewOverview(token); + setAverageRating(overviewResponse.data.average_rating); + setRatingCounts(overviewResponse.data.rating_counts); + } catch (error) { + console.error("Error fetching data:", error); + } finally { + setTimeout(() => dispatch(setLoading(false)), 500); + } + }; - const handlePageChange = page => { - if (page < 1 || page > totalPages) return; - setCurrentPage(page); - }; + fetchData(); + }, [currentPage, feedbacksPerPage, dispatch, history]); - const handleSort = key => { - let direction = 'asc'; - if (sortConfig.key === key && sortConfig.direction === 'asc') { - direction = 'desc'; + const handleStatusChange = async (reviewId, status) => { + const token = localStorage.getItem("admin_token"); + try { + await updateReviewStatus(token, reviewId, status); + setFeedbacks((prevFeedbacks) => + prevFeedbacks.map((feedback) => + feedback.id === reviewId + ? { ...feedback, reviewed: status } + : feedback, + ), + ); + } catch (error) { + console.error("Error updating review status:", error); } - setSortConfig({ key, direction }); }; - const averageRating = ( - dummyFeedbacks.reduce((acc, feedback) => acc + feedback.rating, 0) / dummyFeedbacks.length - ).toFixed(1); + const handlePageChange = (page) => { + if (page < 1 || page > totalPages) return; + setCurrentPage(page); + }; const getPaginationItems = () => { const items = []; @@ -76,135 +97,246 @@ const AdminFeedbacks = () => { if (startPage > 1) { items.push( - handlePageChange(1)} isActive={currentPage === 1}> + handlePageChange(1)} + isActive={currentPage === 1} + > 1 - + , ); if (startPage > 2) { - items.push(...); + items.push( + + ... + , + ); } } for (let page = startPage; page <= endPage; page++) { items.push( - handlePageChange(page)} isActive={currentPage === page}> + handlePageChange(page)} + isActive={currentPage === page} + > {page} - + , ); } if (endPage < totalPages) { if (endPage < totalPages - 1) { - items.push(...); + items.push( + + ... + , + ); } items.push( - handlePageChange(totalPages)} isActive={currentPage === totalPages}> + 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"); - 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'); + const handleSignOut = () => { + localStorage.removeItem("admin_token"); + history.push("/admin"); + }; return ( - + + + + + logo + + + Sign Out + + + + - + Rating & Ulasan User - - {averageRating} + + {averageRating.toFixed(1)} {Array(5) - .fill('') + .fill("") .map((_, i) => ( ))} - - {dummyFeedbacks.length} ulasan + + {Object.values(ratingCounts).reduce((a, b) => a + b, 0)} ulasan - {feedbackStats.map((stat, i) => ( - + {[5, 4, 3, 2, 1].map((rating) => ( + - {stat.rating} + {rating} - + a + b, + 0, + )) * + 100 + }%`} + borderRadius="full" + > ))} - + - - - - - - - {currentFeedbacks.map((feedback, index) => ( + {feedbacks.map((feedback, index) => ( - + - - + +
- handleSort('id')}> - No {renderSortIcon('id')} - + + No - handleSort('username')}> - Nama User {renderSortIcon('username')} - + + Nama User - handleSort('rating')}> - Rating {renderSortIcon('rating')} - + + Rating - Feedback (opsional) + + Feedback (opsional) - handleSort('time')}> - Waktu {renderSortIcon('time')} - + + Waktu - handleSort('status')}> - Aksi {renderSortIcon('status')} - + + Aksi
{(currentPage - 1) * feedbacksPerPage + index + 1}{feedback.username}{feedback.user_name} {feedback.rating}{feedback.feedback}{feedback.time}{feedback.comment} + {new Date(feedback.created_at).toLocaleString()} + - + handleStatusChange( + feedback.id, + e.target.value === "telah_diproses", + ) + } + width="175px" + bg={selectBg} + > @@ -216,23 +348,33 @@ const AdminFeedbacks = () => { - handlePageChange(currentPage - 1)} disabled={currentPage === 1}> - - Previous + handlePageChange(currentPage - 1)} + disabled={currentPage === 1} + > + + Previous {getPaginationItems()} - handlePageChange(currentPage + 1)} disabled={currentPage === totalPages}> - Next - + handlePageChange(currentPage + 1)} + disabled={currentPage === totalPages} + > + Next + - + Ulasan per halaman: { - Password * + + Password{" "} + + * + + setPassword(e.target.value)} @@ -96,13 +131,7 @@ const AdminLogin = () => { {errors.password} - diff --git a/src/containers/Feedback/index.js b/src/containers/Feedback/index.js index 3a993f2..77e9911 100644 --- a/src/containers/Feedback/index.js +++ b/src/containers/Feedback/index.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; import { Text, @@ -10,19 +10,18 @@ import { useColorModeValue, Image, useToast, -} from '@chakra-ui/react'; -import Helmet from 'react-helmet'; -import { StarIcon } from '@chakra-ui/icons'; -import styled from 'styled-components'; -import { useState } from 'react'; -import { useSelector } from 'react-redux'; -import bauhaus from 'assets/Feedback/Page/bauhaus-feedback-1.png'; -import bauhaus2 from 'assets/Feedback/Page/bauhaus-feedback-2.png'; -import bauhaus3 from 'assets/Feedback/Page/bauhaus-feedback-3.png'; -import bauhaus4 from 'assets/Feedback/Page/bauhaus-feedback-4.png'; -import { createReview } from 'services/api'; -import { makeAtLeastMs } from 'utils/promise'; - +} from "@chakra-ui/react"; +import Helmet from "react-helmet"; +import { StarIcon } from "@chakra-ui/icons"; +import styled from "styled-components"; +import { useState } from "react"; +import { useSelector } from "react-redux"; +import bauhaus from "assets/Feedback/Page/bauhaus-feedback-1.png"; +import bauhaus2 from "assets/Feedback/Page/bauhaus-feedback-2.png"; +import bauhaus3 from "assets/Feedback/Page/bauhaus-feedback-3.png"; +import bauhaus4 from "assets/Feedback/Page/bauhaus-feedback-4.png"; +import { createReview } from "services/api"; +import { makeAtLeastMs } from "utils/promise"; export default function Feedback() { const [rating, setRating] = useState(0); @@ -40,8 +39,7 @@ export default function Feedback() { const message = comment; const userId = auth.userId; try { - const response = await makeAtLeastMs(createReview(userId, ratingValue, message), 1000); - alert(response); + await makeAtLeastMs(createReview(userId, ratingValue, message), 1000); setRating(0); setComment(""); toast({ @@ -49,7 +47,7 @@ export default function Feedback() { status: "success", duration: 2500, position: "bottom", - }) + }); } catch (error) { toast({ title: "Failed to submit feedback", @@ -58,28 +56,62 @@ export default function Feedback() { position: "bottom", }); } - } + }; return ( - - + + Bauhaus - + Bauhaus - + Bauhaus - + Bauhaus - + Berikan kami Ulasan! - + Bagaimana pengalaman Anda dalam menggunakan SusunJadwal? @@ -97,8 +129,8 @@ export default function Feedback() { .map((_, i) => ( - + {comment.length}/300 @@ -144,7 +182,7 @@ export default function Feedback() { - ) + ); } const MainContainer = styled.div` @@ -162,8 +200,8 @@ const MainContainer = styled.div` } `; -const FeedbackForm = styled.div` - padding-top: 56px !important; +const FeedbackForm = styled.div` + padding-top: 56px !important; padding-bottom: 100px !important; width: 100%; max-width: 700px; @@ -171,4 +209,4 @@ const FeedbackForm = styled.div` @media (max-width: 768px) { padding: 0 !important; } -`; \ No newline at end of file +`; diff --git a/src/containers/Header/index.js b/src/containers/Header/index.js index bd70589..e6dd554 100644 --- a/src/containers/Header/index.js +++ b/src/containers/Header/index.js @@ -49,7 +49,7 @@ function Header() { return isOpen ? onClose() : onOpen(); } - if (["/admin"].includes(pathname)) return null; + if (["/admin", "/feedback-recap"].includes(pathname)) return null; return ( { const theme = useColorModeValue("light", "dark"); return ( - {pathname !== "/feedback-recap" && + {pathname !== "/feedback-recap" && LINKS.map(({ to, label }) => ( {label} - )) - } + ))} Sign Out ); diff --git a/src/containers/ScheduleList/index.js b/src/containers/ScheduleList/index.js index 2e735c0..3f26b5d 100644 --- a/src/containers/ScheduleList/index.js +++ b/src/containers/ScheduleList/index.js @@ -36,6 +36,7 @@ import { convertPeriodToLiteral, groupScheduleByPeriod } from "utils/schedule"; import { makeAtLeastMs } from "utils/promise"; import { SuccessToast, ErrorToast } from "components/Toast"; import { BauhausSide } from "components/Bauhaus"; +import GoogleCalendarModal from "../ViewSchedule/GoogleCalendarModal"; import BauhausMobile from "assets/Beta/bauhaus-sm.svg"; import BauhausDesktop from "assets/Beta/bauhaus-lg.svg"; @@ -427,6 +428,8 @@ const ScheduleList = () => { )} + + ); }; diff --git a/src/routes.js b/src/routes.js index 149fabe..80e198a 100644 --- a/src/routes.js +++ b/src/routes.js @@ -45,45 +45,45 @@ function Routes() { return ( - <> - - - - - - - - - - -