Skip to content

Commit

Permalink
Merge pull request #58 from sai80082/ui-enhance
Browse files Browse the repository at this point in the history
New Workflow for PR's and Heatmap for past one year assets
  • Loading branch information
varun-raj authored Nov 23, 2024
2 parents 6985afc + 53b267f commit 2085174
Show file tree
Hide file tree
Showing 11 changed files with 581 additions and 18 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/pr-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Build and Tag Docker Image on PR

on:
pull_request:
types: [opened, synchronize]

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- uses: actions/checkout@v3

- uses: docker/setup-buildx-action@v3

- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract PR number
id: pr-number
run: echo "PR_NUMBER=${{ github.event.number }}" >> $GITHUB_ENV

- name: Build and push Docker image
run: |
docker build --build-arg VERSION=pr-${{ env.PR_NUMBER }} \
-t ghcr.io/${{ github.repository }}:pr-${{ env.PR_NUMBER }} .
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.1",
Expand All @@ -30,6 +31,7 @@
"@tanstack/react-table": "^8.20.1",
"@types/cookie": "^0.6.0",
"@types/qs": "^6.9.15",
"@types/react-calendar-heatmap": "^1.6.7",
"axios": "^1.7.4",
"chrono-node": "^1.4.9",
"class-variance-authority": "^0.7.0",
Expand All @@ -44,6 +46,7 @@
"pg": "^8.12.0",
"qs": "^6.13.0",
"react": "^18",
"react-calendar-heatmap": "^1.9.0",
"react-day-picker": "9.0.8",
"react-dom": "^18",
"react-grid-gallery": "^1.0.1",
Expand Down
143 changes: 143 additions & 0 deletions src/components/analytics/exif/AssetHeatMap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, { useEffect, useState } from "react";
import { getHeatMapData } from "@/handlers/api/analytics.handler";
import { useConfig } from "@/contexts/ConfigContext";

type HeatMapEntry = {
date: string;
count: number;
};

export default function AssetHeatMap() {
const { exImmichUrl } = useConfig();

const [heatMapData, setHeatMapData] = useState<HeatMapEntry[][]>([]);
const [loading, setLoading] = useState(false);
const [weeksPerMonth, setWeeksPerMonth] = useState<number[]>([]); // Store weeks per month

const fetchHeatMapData = async () => {
setLoading(true);
try {
const data = await getHeatMapData();
setHeatMapData(formatHeatMapData(data));
} finally {
setLoading(false);
}
};

useEffect(() => {
fetchHeatMapData();
}, []);

const currentDate = new Date();
const months: string[] = [];

// Build an array of the last 12 months
for (let i = 0; i < 12; i++) {
months.push(currentDate.toLocaleString("default", { month: "short" }));
currentDate.setMonth(currentDate.getMonth() - 1);
}
months.reverse();

// Flatten heatMapData to calculate min and max counts
const flattenedData = heatMapData.flat();
const minCount = Math.min(...flattenedData.map((entry) => entry.count));
const maxCount = Math.max(...flattenedData.map((entry) => entry.count));

// Check if a week contains a date with the first day of the month
const hasFirstDayOfMonth = (arr: HeatMapEntry[]) => {
return arr.some((entry) => new Date(entry.date).getDate() === 1);
};

const getColor = (count: number) => {
if (count === -1) return "";

// Calculate thresholds based on the data range
const range = maxCount - minCount;
const threshold1 = minCount + range * 0.2;
const threshold2 = minCount + range * 0.4;
const threshold3 = minCount + range * 0.6;
const threshold4 = minCount + range * 0.8;
if (count === 0) return "bg-zinc-800";
if (count <= threshold1) return "bg-green-200";
if (count <= threshold2) return "bg-green-400";
if (count <= threshold3) return "bg-green-500";
if (count <= threshold4) return "bg-green-600";
return "bg-green-800";
};

const formatHeatMapData = (data: HeatMapEntry[]) => {
const chunks = [];
const weeksPerMonthTemp: number[] = [];
let previousMonthIndex = 0;

for (let i = 0; i < data.length; i += 7) {
const thisWeek = data.slice(i, i + 7);
chunks.push(thisWeek);

if (hasFirstDayOfMonth(thisWeek)) {
const changeWeek = (i - previousMonthIndex) / 7;
previousMonthIndex = i;
weeksPerMonthTemp.push(changeWeek);
}
}
weeksPerMonthTemp.reverse();
setWeeksPerMonth(weeksPerMonthTemp);
return chunks;
};

return (
<div className="p-4 w-full">
<h2 className="text-xl font-bold mb-4">Past Year</h2>
{loading ? (
<p>Loading...</p>
) : (
<div className="overflow-x-auto">
<table className="table-auto border-separate border-spacing-1 w-full">
<thead>
<tr>
{weeksPerMonth.map((weeks, index) => (
<th
key={index}
className="text-center"
colSpan={weeks}
>
{months[index]}
</th>
))}
</tr>
</thead>
<tbody>
{heatMapData[0]?.map((_, rowIndex) => (
<tr key={rowIndex}>
{heatMapData.map((column, colIndex) => (
<td
key={colIndex}
className={`h-6 ${getColor(column[rowIndex]?.count ?? -1)} rounded-lg`}
style={{ width: `calc(${100 / 60}%)` }}
>
{
column[rowIndex]?.date ? (
<a
href={`${exImmichUrl}/search?query=%7B%22takenAfter%22%3A%22${column[rowIndex]?.date ?? "N/A"}T00%3A00%3A00.000Z%22%2C%22takenBefore%22%3A%22${column[rowIndex]?.date ?? "N/A"}T23%3A59%3A59.999Z%22%7D`}
className="block h-full w-full"
target="_blank"
>
<div
className="h-full w-full"
title={`Date: ${column[rowIndex]?.date ?? "N/A"}, Count: ${column[rowIndex]?.count ?? 0}`}
/>
</a>) : <div
className="h-full w-full"
/>
}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}
27 changes: 27 additions & 0 deletions src/components/ui/hover-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from "react"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"

import { cn } from "@/lib/utils"

const HoverCard = HoverCardPrimitive.Root

const HoverCardTrigger = HoverCardPrimitive.Trigger

const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName

export { HoverCard, HoverCardTrigger, HoverCardContent }
100 changes: 100 additions & 0 deletions src/components/ui/linechardots.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { CartesianGrid, LabelList, Line, LineChart, XAxis, YAxis } from "recharts";

import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { useMemo } from "react";
import { CHART_COLORS } from "@/config/constants/chart.constant";

export interface ILineChartData {
label: string;
value: number;
}

interface ChartProps {
data: ILineChartData[];
topLabel?: string;
loading?: boolean;
errorMessage?: string | null;
}

export function LineChartDots({
data: _data,
topLabel,
loading,
errorMessage,
}: ChartProps) {
const data = useMemo(
() =>
_data.map((item, index) => ({
...item,
fill: CHART_COLORS[index] || "#000000",
})),
[_data]
);

const chartConfig = useMemo(() => {
let config: ChartConfig = {};
data.map((data) => {
config[data.label as string] = {
label: data.label,
color: "hsl(var(--chart-1))",
};
});
return config;
}, [data]);

if (loading) {
return <p>Loading...</p>;
}

if (errorMessage) {
return <p>{errorMessage}</p>;
}

return (
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
<LineChart
accessibilityLayer
data={data}

>
<CartesianGrid vertical={false} />
<YAxis /> {/* Added YAxis for the count */}
<XAxis padding={{ left: 20 }} />
<ChartTooltip
cursor={false}
content={
<ChartTooltipContent indicator="line" nameKey="label" hideLabel />
}
/>
<Line
dataKey="value"
type="natural"
stroke="#FF6B6B"
strokeWidth={2}
dot={{
fill: "var(--color-visitors)",
}}
activeDot={{
r: 6,
}}
>
<LabelList
position="top"
offset={12}
className="fill-foreground"
fontSize={12}
dataKey="label"
formatter={(value: keyof typeof chartConfig) =>
chartConfig[value]?.label
}
/>
</Line>
</LineChart>
</ChartContainer>
);
}
Loading

0 comments on commit 2085174

Please sign in to comment.