feat: heatmap in dashboard

This commit is contained in:
DIYgod 2024-01-16 00:06:59 +08:00
parent a69a22e52a
commit 82611050bc
No known key found for this signature in database
5 changed files with 195 additions and 5 deletions

View File

@ -4,6 +4,7 @@ import { useTranslations } from "next-intl"
import { useParams } from "next/navigation"
import { CharacterFloatCard } from "~/components/common/CharacterFloatCard"
import Heatmap from "~/components/common/Heatmap"
import { DashboardMain } from "~/components/dashboard/DashboardMain"
import { Image } from "~/components/ui/Image"
import { UniLink } from "~/components/ui/UniLink"
@ -132,6 +133,7 @@ export default function SubdomainIndex() {
</UniLink>
))}
</div>
<Heatmap characterId={characterId} />
<div className="prose p-6 bg-slate-50 rounded-lg relative">
{t.rich("hello.welcome", {
p: (chunks) => <p>{chunks}</p>,
@ -205,8 +207,9 @@ export default function SubdomainIndex() {
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2">
{pages.data?.pages[0]?.list.map((item) => (
<UniLink
href={`${getSiteLink({ subdomain: "xlog" })}/${item.metadata
?.content?.slug}`}
href={`${getSiteLink({ subdomain: "xlog" })}/${
item.metadata?.content?.slug
}`}
key={item.transactionHash}
className="bg-slate-100 rounded-lg flex flex-col py-4 px-6"
>

View File

@ -0,0 +1,62 @@
"use client"
import dayjs from "dayjs"
import advancedFormat from "dayjs/plugin/advancedFormat"
import { useTranslations } from "next-intl"
import { Tooltip } from "~/components/ui/Tooltip"
import { useCalendar } from "~/queries/page"
dayjs.extend(advancedFormat)
export default function CalHeatmap({ characterId }: { characterId?: number }) {
const calendar = useCalendar(characterId)
const t = useTranslations()
return (
<div>
<div className="flex sm:gap-1 gap-[2px]">
{calendar.data?.calendar.map((week: any, index: number) => (
<div className="flex sm:gap-1 gap-[2px] flex-col" key={index}>
{week.map((day: any) => (
<Tooltip
key={day.day}
label={
<>
<p>
{day.count} {t("on-chain posting on")}{" "}
{dayjs(day.day).format("MMM DD, YYYY")}
</p>
{(() =>
day.titles.map((title: string) => (
<p key={`${day.day}${index}`} className="text-xs">
{title}
</p>
)))()}
</>
}
placement="top"
className="z-10"
>
<div
className={
"sm:w-[12px] sm:h-[12px] w-1 h-1 cursor-pointer sm:rounded-sm rounded-[1px] " +
(day.count === 0
? "bg-gray-100"
: day.count > 0 && day.count < 2
? "bg-green-300"
: day.count >= 2 && day.count < 5
? "bg-green-400"
: day.count >= 5 && day.count < 10
? "bg-green-500"
: "bg-green-600")
}
></div>
</Tooltip>
))}
</div>
))}
</div>
</div>
)
}

View File

@ -432,5 +432,6 @@
"zh": "中文",
"zh-TW": "繁体中文",
"Footer": "页脚",
"Support Markdown": "支持 Markdown"
"Support Markdown": "支持 Markdown",
"on-chain posting on": "条链上发布于"
}

View File

@ -6,6 +6,9 @@ import type {
NoteEntity,
NoteMetadata,
} from "crossbell"
import dayjs from "dayjs"
import advancedFormat from "dayjs/plugin/advancedFormat"
import weekOfYear from "dayjs/plugin/weekOfYear"
import { type Address } from "viem"
import { GeneralAccount } from "@crossbell/connect-kit"
@ -29,6 +32,9 @@ import {
} from "~/lib/types"
import { client } from "~/queries/graphql"
dayjs.extend(weekOfYear)
dayjs.extend(advancedFormat)
export const PINNED_PAGE_KEY = "xlog_pinned_page"
export async function checkPageSlug(input: {
@ -296,8 +302,9 @@ export async function getPagesBySite(input: {
cursor:
data?.notes?.length < limit
? null
: `${input.characterId}_${data?.notes?.[data?.notes?.length - 1]
?.noteId}`,
: `${input.characterId}_${
data?.notes?.[data?.notes?.length - 1]?.noteId
}`,
}
const expandedNotes = {
@ -384,6 +391,114 @@ export async function getPagesBySite(input: {
return expandedNotes
}
export async function getCalendar(characterId?: number) {
if (!characterId) {
return {
calendar: [],
}
}
const format = (day: dayjs.Dayjs) => {
const ww = day.format("ww")
const mm = day.format("MM")
if (ww === "01" && mm !== "01") {
return `${day.year() + 1}-${ww}`
} else {
return `${day.year()}-${ww}`
}
}
const calendarLength = 370
const getCalendarTemp = () => {
const calendar: {
[key: string]: {
day: dayjs.Dayjs
count: number
titles: string[]
}[]
} = {}
for (let i = calendarLength - 1; i >= 0; i--) {
const day = dayjs().subtract(i, "day")
let week = format(day)
if (!calendar[week]) {
calendar[week] = []
}
calendar[week].push({
day: day,
count: 0,
titles: [],
})
}
return calendar
}
const currentDate = new Date()
currentDate.setUTCHours(0, 0, 0, 0)
currentDate.setUTCDate(currentDate.getUTCDate() - 370)
const utcString = currentDate.toISOString()
const { data } = await client
.query(
gql`
query getNotes($characterId: Int, $limit: Int, $utcString: DateTime) {
notes(
where: {
characterId: { equals: $characterId }
createdAt: { gte: $utcString }
metadata: { content: { path: "sources", array_contains: "xlog" } }
}
take: $limit
) {
createdAt
metadata {
content
}
}
}
`,
{
characterId,
limit: 1000,
utcString,
},
)
.toPromise()
let response = {
calendar: getCalendarTemp(),
}
for (let i = 0; i < data.notes.length; i++) {
const day = dayjs(data.notes[i].createdAt)
let week = format(day)
const today = response.calendar[week].find((item: any) =>
item.day.isSame(day, "day"),
)
if (today) {
today.count++
today.titles.push(
(
data.notes[i].metadata.content.title ||
data.notes[i].metadata.content.content
).slice(0, 20),
)
} else {
console.warn("not found", day)
}
}
return {
calendar: Object.keys(response.calendar)
.sort()
.map((key) =>
response.calendar[key].map((item: any) => {
item.day = item.day.valueOf()
return item
}),
),
}
}
export async function getSearchPagesBySite(input: {
characterId?: number
keyword?: string

View File

@ -708,3 +708,12 @@ export const usePinnedPage = ({
return { isLoading, noteId: data as number }
}
export const useCalendar = (characterId?: number) => {
return useQuery(["getCalendar", characterId], async () => {
if (!characterId) {
return
}
return pageModel.getCalendar(characterId)
})
}