feat: heatmap in dashboard
This commit is contained in:
parent
a69a22e52a
commit
82611050bc
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -432,5 +432,6 @@
|
|||
"zh": "中文",
|
||||
"zh-TW": "繁体中文",
|
||||
"Footer": "页脚",
|
||||
"Support Markdown": "支持 Markdown"
|
||||
"Support Markdown": "支持 Markdown",
|
||||
"on-chain posting on": "条链上发布于"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue