mirror of
https://github.com/koreanbots/core.git
synced 2025-12-15 14:10:22 +00:00
feat: implement basic analytics ui
This commit is contained in:
parent
95879bb7b4
commit
0fc2cf0c5b
23
components/Form/DateSelect.tsx
Normal file
23
components/Form/DateSelect.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import DatePicker, { registerLocale } from 'react-datepicker'
|
||||
import { ko } from 'date-fns/locale'
|
||||
|
||||
import 'react-datepicker/dist/react-datepicker.css'
|
||||
|
||||
registerLocale('ko', ko)
|
||||
|
||||
const DateSelect: React.FC<DateSelectProps> = ({ ...props }) => {
|
||||
return <DatePicker
|
||||
locale='ko'
|
||||
className='border-grey-light relative px-3 w-full py-1.5 text-black dark:text-white dark:bg-very-black border dark:border-transparent rounded outline-none'
|
||||
dateFormat='yyyy.MM.dd'
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
interface DateSelectProps {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
|
||||
export default DateSelect
|
||||
30
components/StatisticsCard.tsx
Normal file
30
components/StatisticsCard.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
const StatisticsCard: React.FC<StatisticsCardProps> = ({name, currentValue, diff, icon, range}) => {
|
||||
return <div className='text-black dark:text-white dark:bg-discord-black bg-little-white rounded-2xl shadow-xl p-5 hover:-translate-y-1 transition duration-100 ease-in cursor-pointer'>
|
||||
<p className='text-xl text-gray-400 font-semibold'>
|
||||
{name}
|
||||
</p>
|
||||
<div className='flex flex-row'>
|
||||
{icon}
|
||||
<p className='text-2xl text-center font-bold'>
|
||||
{currentValue}
|
||||
</p>
|
||||
<div className='w-1.5' />
|
||||
<p className={'text-base self-end text-semibold ' + (diff > 0 ? 'text-green-600' : diff == 0 ? 'text-gray-500' : 'text-red-600')}>
|
||||
{diff > 0 ? '+' : diff == 0 ? '-' : ''}{diff}
|
||||
</p>
|
||||
</div>
|
||||
<p className='text-lg text-gray-500 font-light'>
|
||||
{range === 'day' ? '어제 대비' : range === 'week' ? '저번주 대비' : range === 'month' ? '저번 달 대비' : range}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
interface StatisticsCardProps {
|
||||
name: string
|
||||
currentValue: number | string
|
||||
diff: number
|
||||
icon: JSX.Element
|
||||
range: 'day' | 'week' | 'month' | string
|
||||
}
|
||||
|
||||
export default StatisticsCard
|
||||
@ -24,6 +24,7 @@
|
||||
"cookie": "0.5.0",
|
||||
"csrf": "3.1.0",
|
||||
"dataloader": "2.2.2",
|
||||
"date-fns": "^2.30.0",
|
||||
"dayjs": "^1.10.6",
|
||||
"dd-trace": "^4.3.0",
|
||||
"difflib": "0.2.4",
|
||||
@ -49,6 +50,7 @@
|
||||
"rc-tooltip": "5.1.1",
|
||||
"react": "17.0.2",
|
||||
"react-adsense": "0.1.0",
|
||||
"react-datepicker": "^4.16.0",
|
||||
"react-dom": "17.0.2",
|
||||
"react-ga": "^3.3.1",
|
||||
"react-hotkeys": "2.0.0",
|
||||
|
||||
@ -26,15 +26,35 @@ const DeveloperLayout = dynamic(() => import('@components/DeveloperLayout'))
|
||||
const DiscordAvatar = dynamic(() => import('@components/DiscordAvatar'))
|
||||
const Message = dynamic(() => import('@components/Message'))
|
||||
const Modal = dynamic(() => import('@components/Modal'))
|
||||
const StatisticsCard = dynamic(() => import('@components/StatisticsCard'))
|
||||
const Select = dynamic(() => import('@components/Form/Select'))
|
||||
const DateSelect = dynamic(() => import('@components/Form/DateSelect'))
|
||||
|
||||
const BotApplication: NextPage<BotApplicationProps> = ({ user, spec, bot, theme, csrfToken }) => {
|
||||
const router = useRouter()
|
||||
const [ data, setData ] = useState<ResponseProps<unknown>>(null)
|
||||
const [ modalOpened, setModalOpen ] = useState(false)
|
||||
const [ showToken, setShowToken ] = useState(false)
|
||||
const [ range, setRange ] = useState('day')
|
||||
const [ startDate, setStartDate ] = useState(new Date())
|
||||
const [ endDate, setEndDate ] = useState(new Date())
|
||||
const [ tokenCopied, setTokenCopied ] = useClipboard(spec?.token, {
|
||||
successDuration: 1000
|
||||
})
|
||||
const availableRanges = [
|
||||
{
|
||||
label: '일간',
|
||||
value: 'day'
|
||||
},
|
||||
{
|
||||
label: '주간',
|
||||
value: 'week'
|
||||
},
|
||||
{
|
||||
label: '월간',
|
||||
value: 'month'
|
||||
},
|
||||
]
|
||||
async function updateApplication(d: DeveloperBot) {
|
||||
const res = await Fetch(`/applications/bots/${bot.id}`, {
|
||||
method: 'PATCH',
|
||||
@ -140,6 +160,25 @@ const BotApplication: NextPage<BotApplicationProps> = ({ user, spec, bot, theme,
|
||||
</div>
|
||||
<Divider />
|
||||
<h1 className='text-3xl font-bold pt-6'>봇 통계</h1>
|
||||
<div className='flex flex-row pt-2 items-center'>
|
||||
<div className='w-20 mr-2'>
|
||||
<Select options={availableRanges} handleChange={(option) => setRange(option.value)} value={availableRanges[0]} handleTouch={() => null} />
|
||||
</div>
|
||||
<DateSelect selected={startDate} onChange={(date) => setStartDate(date)} startDate={startDate} endDate={endDate} />
|
||||
<p className='px-2 text-gray-400 text-xl font-semibold'>
|
||||
부터
|
||||
</p>
|
||||
<DateSelect selected={endDate} onChange={(date) => setEndDate(date)} startDate={startDate} endDate={endDate} minDate={startDate} />
|
||||
<p className='px-2 text-gray-400 text-xl font-semibold'>
|
||||
까지
|
||||
</p>
|
||||
</div>
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 pt-3 gap-5'>
|
||||
<StatisticsCard name='하트 수' currentValue={50} diff={10} icon={<i className='fas fa-heart text-red-600 self-center text-xl mr-1' />} range={range} />
|
||||
<StatisticsCard name='서버 수' currentValue={1000} diff={-10} icon={<i className='fas fa-users text-discord-blurple self-center text-xl mr-2' />} range={range} />
|
||||
<StatisticsCard name='조회수' currentValue={102} diff={0} icon={<i className='fas fa-mouse-pointer text-neutral-200 self-center text-lg -mr-1' />} range={range} />
|
||||
<StatisticsCard name='초대 클릭 수' currentValue={5} diff={3} icon={<i className='fas fa-user-plus text-discord-old-blurple self-center text-xl mr-2.5' />} range={range} />
|
||||
</div>
|
||||
</DeveloperLayout>
|
||||
|
||||
}
|
||||
|
||||
58
yarn.lock
58
yarn.lock
@ -1705,6 +1705,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/runtime@^7.21.0":
|
||||
version "7.22.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438"
|
||||
integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/template@^7.22.5", "@babel/template@^7.3.3":
|
||||
version "7.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec"
|
||||
@ -2427,6 +2434,11 @@
|
||||
picocolors "^1.0.0"
|
||||
tslib "^2.5.0"
|
||||
|
||||
"@popperjs/core@^2.11.8":
|
||||
version "2.11.8"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||
|
||||
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
|
||||
@ -4206,6 +4218,13 @@ dataloader@2.2.2:
|
||||
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0"
|
||||
integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==
|
||||
|
||||
date-fns@^2.30.0:
|
||||
version "2.30.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
|
||||
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.21.0"
|
||||
|
||||
dayjs@^1.10.6:
|
||||
version "1.11.8"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.8.tgz#4282f139c8c19dd6d0c7bd571e30c2d0ba7698ea"
|
||||
@ -8159,7 +8178,7 @@ prompts@^2.0.1:
|
||||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.8.1:
|
||||
prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@ -8284,6 +8303,18 @@ react-adsense@0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/react-adsense/-/react-adsense-0.1.0.tgz#f170007a9ab05ee2cd1c915cdc9788624658e3c1"
|
||||
integrity sha512-VzGMH9qA7bBKAveK1joFWphhB7e2Y1ToSGzUa8b7eit3+VSkg/2MSvKxe5d/r4mcmkOHu6CHNJW0nn6RQC3IZA==
|
||||
|
||||
react-datepicker@^4.16.0:
|
||||
version "4.16.0"
|
||||
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.16.0.tgz#b9dd389bb5611a1acc514bba1dd944be21dd877f"
|
||||
integrity sha512-hNQ0PAg/LQoVbDUO/RWAdm/RYmPhN3cz7LuQ3hqbs24OSp69QCiKOJRrQ4jk1gv1jNR5oYu8SjjgfDh8q6Q1yw==
|
||||
dependencies:
|
||||
"@popperjs/core" "^2.11.8"
|
||||
classnames "^2.2.6"
|
||||
date-fns "^2.30.0"
|
||||
prop-types "^15.7.2"
|
||||
react-onclickoutside "^6.12.2"
|
||||
react-popper "^2.3.0"
|
||||
|
||||
react-dom@17.0.2:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
|
||||
@ -8298,6 +8329,11 @@ react-fast-compare@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
||||
|
||||
react-fast-compare@^3.0.1:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
|
||||
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
|
||||
|
||||
react-ga@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.3.1.tgz#d8e1f4e05ec55ed6ff944dcb14b99011dfaf9504"
|
||||
@ -8327,6 +8363,19 @@ react-is@^18.0.0:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
||||
react-onclickoutside@^6.12.2:
|
||||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz#e165ea4e5157f3da94f4376a3ab3e22a565f4ffc"
|
||||
integrity sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==
|
||||
|
||||
react-popper@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
|
||||
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
|
||||
dependencies:
|
||||
react-fast-compare "^3.0.1"
|
||||
warning "^4.0.2"
|
||||
|
||||
react-responsive-modal@6.4.2:
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/react-responsive-modal/-/react-responsive-modal-6.4.2.tgz#666b5c35b232cec617981006c9fe59414531a5a0"
|
||||
@ -9769,6 +9818,13 @@ walker@^1.0.8:
|
||||
dependencies:
|
||||
makeerror "1.0.12"
|
||||
|
||||
warning@^4.0.2:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
wcwidth@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user