From 7f42ecd120ea1dae941f494bb0151a20e0f8ec64 Mon Sep 17 00:00:00 2001 From: Blagoslav Mihaylov Date: Sat, 3 Apr 2021 03:28:54 +0300 Subject: [PATCH] Improve comment section --- src/components/Util.js | 103 ++++++++++++++ src/components/modules/Modules.js | 5 +- .../modules/violations/AllComments.js | 129 ++++++++++++++++++ src/components/modules/violations/Comment.js | 62 +++++++++ .../modules/violations/CommentForm.js | 92 ++++++++++++- .../modules/violations/CommentSection.js | 69 ++++------ .../modules/violations/ViolationDetails.js | 112 +++++++++++---- 7 files changed, 489 insertions(+), 83 deletions(-) create mode 100644 src/components/Util.js create mode 100644 src/components/modules/violations/AllComments.js create mode 100644 src/components/modules/violations/Comment.js diff --git a/src/components/Util.js b/src/components/Util.js new file mode 100644 index 0000000..eb96b35 --- /dev/null +++ b/src/components/Util.js @@ -0,0 +1,103 @@ +const format = function(number, n, x, s, c) { + var re = '\\d(?=(\\d{' + (x || 3) + '})+' + (n > 0 ? '\\D' : '$') + ')', + num = parseFloat(number).toFixed(Math.max(0, ~~n)); + + return (c ? num.replace('.', c) : num).replace(new RegExp(re, 'g'), '$&' + (s || ',')); +}; + +const formatLv = function(number) { + return format(number, 2, 3, ' ', ','); +}; + +const formatPop = function(number) { + return format(Math.floor(number), 0, 3, ' ', ','); +} + +const pad = num => { + var s = String(num); + while (s.length < 2) {s = "0" + s;} + return s; +}; + +const month = monthStr => { + switch(monthStr) { + case 0: return 'януари'; break; + case 1: return 'февруари'; break; + case 2: return 'март'; break; + case 3: return 'април'; break; + case 4: return 'май'; break; + case 5: return 'юни'; break; + case 6: return 'юли'; break; + case 7: return 'август'; break; + case 8: return 'септември'; break; + case 9: return 'октомври'; break; + case 10: return 'ноември'; break; + case 11: return 'декември'; break; + } +}; + +const formatDay = d => { + if(d >= 10 && d <= 20) + return d + '-ти'; + + switch(d%10) { + case 1: return d + '-ви'; break; + case 2: return d + '-ри'; break; + case 7: case 8: return d + '-ми'; break; + default: return d + '-ти'; break; + } +}; + +const formatDate = dateTime => { + const date = new Date(dateTime); + + return formatDay(date.getDate()) + ' ' + + month(date.getMonth()) + ' '+ + date.getFullYear(); +}; + +const formatTime = dateTime => { + const date = new Date(dateTime); + return pad(date.getHours()) + ':' + + pad(date.getMinutes()); +}; + +const formatDateTime = dateTime => { + return formatTime(dateTime) + ' ' + formatDateShort(dateTime); + +}; + +const formatSecs = secs => { + return pad(Math.floor(secs / 60)) + ':' + pad(secs % 60); +}; + +const formatDateShort = dateTime => { + const date = new Date(dateTime); + + return pad(date.getDate()) + '.' + + pad(date.getMonth()+1) + '.' + + pad(date.getFullYear()); +} + +const checkPaths = (path1, path2) => { + if(!path1 || !path2) return false; + + if(path1[path1.length - 1] !== '/') + path1 = path1 + '/'; + if(path2[path2.length - 1] !== '/') + path2 = path2 + '/'; + + return path1 === path2; +} + +module.exports = { + formatLv, + formatPop, + formatDate, + formatDateShort, + formatDateTime, + formatTime, + formatSecs, + format, + checkPaths, +}; \ No newline at end of file diff --git a/src/components/modules/Modules.js b/src/components/modules/Modules.js index efadb30..ed32505 100644 --- a/src/components/modules/Modules.js +++ b/src/components/modules/Modules.js @@ -12,6 +12,7 @@ import ProtocolDetails from './protocols/ProtocolDetails'; import ViolationList from './violations/ViolationList'; import ViolationDetails from './violations/ViolationDetails'; +import AllComments from './violations/AllComments'; import styled from 'styled-components'; import Sections from './Sections'; @@ -36,7 +37,8 @@ const ContentHalf = styled.div` export const ContentPanel = styled.div` background-color: white; - margin: 30px 15%; + margin: 30px auto; + max-width: 800px; border-radius: 15px; //box-shadow: 0px 0px 5px #aaa; border: 1px solid #eee; @@ -60,6 +62,7 @@ export default props => { + diff --git a/src/components/modules/violations/AllComments.js b/src/components/modules/violations/AllComments.js new file mode 100644 index 0000000..e13f611 --- /dev/null +++ b/src/components/modules/violations/AllComments.js @@ -0,0 +1,129 @@ +import React, { useState, useEffect, useContext } from 'react'; + +import { Link, useLocation, useParams, useHistory } from 'react-router-dom'; + +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faChevronLeft, faChevronRight, faFastForward, faFastBackward } from '@fortawesome/free-solid-svg-icons'; + +import Loading from '../../layout/Loading'; +import { ContentPanel } from '../Modules'; + +import { AuthContext } from '../../App'; + +import CommentForm from './CommentForm'; +import { PaginationLinks } from './CommentSection'; +import Comment from './Comment'; + +const useQuery = () => { + return new URLSearchParams(useLocation().search); +} + +import styled from 'styled-components'; + +const BackButton = styled(Link)` + cursor: pointer; + border: none; + border-radius: 6px; + background: none; + margin-right: 5px; + font-size: 48px; + color: black; + padding: 5px; + + &:hover { + background-color: #eee; + } +`; + +export default props => { + const [data, setData] = useState(null); + const query = useQuery(); + const { violation } = useParams(); + const history = useHistory(); + + const { authGet } = useContext(AuthContext); + + useEffect(() => { + let url = `/violations/${violation}/comments`; + const page = query.get("page"); + const limit = query.get("limit"); + + if(page || limit) url += '?'; + + if(page) url += `page=${page}`; + if(limit) url += `limit=${limit}`; + + authGet(url).then(res => { + setData(res.data); + }); + }, [query.get("page")]); + + const renderLinks = () => { + const firstAvail = data.meta.currentPage !== 1; + const lastAvail = data.meta.currentPage !== data.meta.totalPages; + const nextAvail = data.links.next; + const prevAvail = data.links.previous; + + const nextPage = data.meta.currentPage + 1; + const prevPage = data.meta.currentPage - 1; + + return ( + + + Първа + + + Предишна + +
+ {data.meta.currentPage} / {data.meta.totalPages} +
+ + Следваща + + + Последна + +
+ ); + }; + + const newComment = comment => { + if(!query.get("page") || query.get("page").toString() === '1') { + if(data.items.length < data.meta.itemsPerPage) { + setData({...data, meta: {...data.meta, totalItems: data.meta.totalItems + 1}, items: [comment, ...data.items]}); + } else if(data.meta.totalItems === data.meta.itemsPerPage) { + history.push(`/violation/${violation}/comments?page=1&limit=20`); + } else { + let newItems = [comment, ...data.items].slice(0, data.meta.itemsPerPage); + setData({...data, meta: {...data.meta, totalItems: data.meta.totalItems + 1}, items: newItems}); + } + } else { + history.push(`/violation/${violation}/comments?page=1&limit=20`); + } + }; + + return ( + +

+ + + Всички коментари + {!data? null : ` (${data.meta.totalItems})`} + +

+
+ + { + !data? : [ + data.meta.totalPages > 1? renderLinks() : null, + data.items.map(comment => [ + , +
+ ]), + data.meta.totalPages > 1? renderLinks() : null, + ] + } +
+ ); +}; \ No newline at end of file diff --git a/src/components/modules/violations/Comment.js b/src/components/modules/violations/Comment.js new file mode 100644 index 0000000..51a0939 --- /dev/null +++ b/src/components/modules/violations/Comment.js @@ -0,0 +1,62 @@ +import React from 'react'; + +import styled from 'styled-components'; + +import { formatTime, formatDateShort } from '../../Util'; + +export const CommentStyle = styled.div` + width: 100%; + + h1 { + font-weight: normal; + font-size: 22px; + margin: 5px 0; + color: #555; + } + + h2 { + font-weight: normal; + font-size: 16px; + margin: 5px 0; + color: #bbb; + } + + p { + color: #333; + } + + .comment-type { + color: #888; + border: 3px solid #888; + border-radius: 10px; + font-size: 12px; + padding: 5px; + font-weight: bold; + vertical-align: top; + display: inline-block; + margin-left: 10px; + margin-top: -2px; + } +`; + +export default props => { + console.log(props.comment); + + const formatCommentType = commentType => { + switch(commentType) { + case 'internal': return 'Вътрешен'; break; + default: return 'Вътрешен'; break; + } + }; + + return ( + +

+ {props.comment.author.firstName} {props.comment.author.lastName} + {formatCommentType(props.comment.type)} +

+

{formatTime(props.comment.createdAt)} – {formatDateShort(props.comment.createdAt)}

+

{props.comment.text}

+
+ ); +}; \ No newline at end of file diff --git a/src/components/modules/violations/CommentForm.js b/src/components/modules/violations/CommentForm.js index 2ede06f..a82f220 100644 --- a/src/components/modules/violations/CommentForm.js +++ b/src/components/modules/violations/CommentForm.js @@ -1,3 +1,4 @@ +import { faGraduationCap } from '@fortawesome/free-solid-svg-icons'; import React, { useState, useContext } from 'react'; import { useParams } from 'react-router-dom'; @@ -6,10 +7,70 @@ import { AuthContext } from '../../App'; const CommentFormStyle = styled.form` width: 100%; + + input[type=radio] { + margin-left: 15px; + margin-right: 5px; + + &:first-of-type { + margin-left: 0; + } + } + + textarea { + width: 100%; + font-size: 18px; + padding: 20px; + border: 1px solid #eee; + margin: 20px 0; + box-sizing: border-box; + } +`; + +const FancyButton = styled.input` + border: none; + padding: 5px 10px; + font-size: 15px; + cursor: pointer; + border-radius: 5px; + box-sizing: border-box; + display: inline-block; + font-weight: bold; + margin: 0 2px; + position: relative; + color: white; + + &:active:enabled { + top: 5px; + border-bottom: 0; + } + + &:disabled { + cursor: auto; + color: white; + } + + &:first-of-type { + margin-left: 0; + } +`; + +export const FancyButtonBlue = styled(FancyButton)` + background-color: #36a2e3; + border-bottom: 5px solid #1e70b9; + + &:hover { + background-color: #48b4f4; + } + + &:disabled { + background-color: #b6e4ff; + border-bottom-color: #b9d4ec; + } `; export default props => { - const [comment, setComment] = useState(''); + const [formData, setFormData] = useState({type: 'internal'}); const [loading, setLoading] = useState(false); const { violation } = useParams(); @@ -18,21 +79,40 @@ export default props => { const handleSubmit = e => { e.preventDefault(); + console.log(formData); + setLoading(true); - authPost(`/violations/${violation}/comments`, {text: comment}).then(res => { + authPost(`/violations/${violation}/comments`, formData).then(res => { setLoading(false); props.newComment(res.data); - }); + setFormData({text: '', type: 'internal'}) + }).catch(err => setLoading(false)); }; const handleChange = e => { - setComment(e.target.value); + setFormData({...formData, [e.target.name]: e.target.value}); }; return ( - - + + + + + + + + + + + + + +
+