mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 18:32:10 +01:00
Compare commits
2 Commits
cbbdc8a1dc
...
13d0f8ee8d
| Author | SHA1 | Date | |
|---|---|---|---|
|
13d0f8ee8d
|
|||
|
303226e3a9
|
8
TODO.md
8
TODO.md
@ -20,7 +20,7 @@ This file lists general areas for improvement and tasks that are broader in scop
|
|||||||
|
|
||||||
## Security Enhancements
|
## Security Enhancements
|
||||||
|
|
||||||
- [ ] **Password Reset Functionality:** Implement a secure password reset mechanism. (Related: `app/forgot-password/page.tsx`, `app/reset-password/page.tsx`, `pages/api/forgot-password.ts`, `pages/api/reset-password.ts` - ensure these are robust and secure if already implemented).
|
- [x] **Password Reset Functionality:** Implement a secure password reset mechanism. (Related: `app/forgot-password/page.tsx`, `app/reset-password/page.tsx`, `pages/api/forgot-password.ts`, `pages/api/reset-password.ts` - ensure these are robust and secure if already implemented).
|
||||||
- [ ] **Two-Factor Authentication (2FA):** Consider adding 2FA, especially for admin accounts.
|
- [ ] **Two-Factor Authentication (2FA):** Consider adding 2FA, especially for admin accounts.
|
||||||
- [ ] **Input Validation and Sanitization:** Rigorously review and ensure all user inputs (API request bodies, query parameters) are validated and sanitized.
|
- [ ] **Input Validation and Sanitization:** Rigorously review and ensure all user inputs (API request bodies, query parameters) are validated and sanitized.
|
||||||
|
|
||||||
@ -35,11 +35,9 @@ This file lists general areas for improvement and tasks that are broader in scop
|
|||||||
|
|
||||||
## Component Specific
|
## Component Specific
|
||||||
|
|
||||||
- [ ] **`components/SessionDetails.tsx.new`:** Review, complete TODOs within the file, and integrate as the primary `SessionDetails.tsx` component, removing/archiving older versions (`SessionDetails.tsx`, `SessionDetails.tsx.bak`).
|
|
||||||
- [ ] **`components/GeographicMap.tsx`:** Check if `GeographicMap.tsx.bak` is still needed or can be removed.
|
|
||||||
- [ ] **`app/dashboard/sessions/page.tsx`:** Implement pagination, advanced filtering, and sorting.
|
|
||||||
- [ ] **`pages/api/dashboard/users.ts`:** Implement robust emailing of temporary passwords.
|
- [ ] **`pages/api/dashboard/users.ts`:** Implement robust emailing of temporary passwords.
|
||||||
|
- [x] **`app/dashboard/sessions/page.tsx`:** Implement pagination, advanced filtering, and sorting.
|
||||||
|
|
||||||
## File Cleanup
|
## File Cleanup
|
||||||
|
|
||||||
- [ ] Review and remove `.bak` and `.new` files once changes are integrated (e.g., `GeographicMap.tsx.bak`, `SessionDetails.tsx.bak`, `SessionDetails.tsx.new`).
|
- [x] Review and remove `.bak` and `.new` files once changes are integrated (e.g., `GeographicMap.tsx.bak`, `SessionDetails.tsx.bak`, `SessionDetails.tsx.new`).
|
||||||
|
|||||||
@ -253,10 +253,6 @@ function DashboardContent() {
|
|||||||
}
|
}
|
||||||
trend={{
|
trend={{
|
||||||
value: metrics.sessionTrend ?? 0,
|
value: metrics.sessionTrend ?? 0,
|
||||||
label:
|
|
||||||
(metrics.sessionTrend ?? 0) > 0
|
|
||||||
? `${metrics.sessionTrend ?? 0}% increase`
|
|
||||||
: `${Math.abs(metrics.sessionTrend ?? 0)}% decrease`,
|
|
||||||
isPositive: (metrics.sessionTrend ?? 0) >= 0,
|
isPositive: (metrics.sessionTrend ?? 0) >= 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -281,10 +277,6 @@ function DashboardContent() {
|
|||||||
}
|
}
|
||||||
trend={{
|
trend={{
|
||||||
value: metrics.usersTrend ?? 0,
|
value: metrics.usersTrend ?? 0,
|
||||||
label:
|
|
||||||
(metrics.usersTrend ?? 0) > 0
|
|
||||||
? `${metrics.usersTrend}% increase`
|
|
||||||
: `${Math.abs(metrics.usersTrend ?? 0)}% decrease`,
|
|
||||||
isPositive: (metrics.usersTrend ?? 0) >= 0,
|
isPositive: (metrics.usersTrend ?? 0) >= 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -309,10 +301,6 @@ function DashboardContent() {
|
|||||||
}
|
}
|
||||||
trend={{
|
trend={{
|
||||||
value: metrics.avgSessionTimeTrend ?? 0,
|
value: metrics.avgSessionTimeTrend ?? 0,
|
||||||
label:
|
|
||||||
(metrics.avgSessionTimeTrend ?? 0) > 0
|
|
||||||
? `${metrics.avgSessionTimeTrend}% increase`
|
|
||||||
: `${Math.abs(metrics.avgSessionTimeTrend ?? 0)}% decrease`,
|
|
||||||
isPositive: (metrics.avgSessionTimeTrend ?? 0) >= 0,
|
isPositive: (metrics.avgSessionTimeTrend ?? 0) >= 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -337,10 +325,6 @@ function DashboardContent() {
|
|||||||
}
|
}
|
||||||
trend={{
|
trend={{
|
||||||
value: metrics.avgResponseTimeTrend ?? 0,
|
value: metrics.avgResponseTimeTrend ?? 0,
|
||||||
label:
|
|
||||||
(metrics.avgResponseTimeTrend ?? 0) > 0
|
|
||||||
? `${metrics.avgResponseTimeTrend ?? 0}% increase`
|
|
||||||
: `${Math.abs(metrics.avgResponseTimeTrend ?? 0)}% decrease`,
|
|
||||||
isPositive: (metrics.avgResponseTimeTrend ?? 0) <= 0, // Lower response time is better
|
isPositive: (metrics.avgResponseTimeTrend ?? 0) <= 0, // Lower response time is better
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -402,7 +386,9 @@ function DashboardContent() {
|
|||||||
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
||||||
Common Topics
|
Common Topics
|
||||||
</h3>
|
</h3>
|
||||||
<WordCloud words={getWordCloudData()} />
|
<div className="h-[300px]">
|
||||||
|
<WordCloud words={getWordCloudData()} width={500} height={400} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -67,9 +67,6 @@ export default function MetricCard({
|
|||||||
>
|
>
|
||||||
{trend.isPositive !== false ? "↑" : "↓"}{" "}
|
{trend.isPositive !== false ? "↑" : "↓"}{" "}
|
||||||
{Math.abs(trend.value).toFixed(1)}%
|
{Math.abs(trend.value).toFixed(1)}%
|
||||||
{trend.label && (
|
|
||||||
<span className="text-gray-500 ml-1">{trend.label}</span>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,20 +11,55 @@ interface WordCloudProps {
|
|||||||
}[];
|
}[];
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
minWidth?: number;
|
||||||
|
minHeight?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WordCloud({
|
export default function WordCloud({
|
||||||
words,
|
words,
|
||||||
width = 500,
|
width: initialWidth = 500,
|
||||||
height = 300,
|
height: initialHeight = 300,
|
||||||
|
minWidth = 200,
|
||||||
|
minHeight = 200,
|
||||||
}: WordCloudProps) {
|
}: WordCloudProps) {
|
||||||
const svgRef = useRef<SVGSVGElement | null>(null);
|
const svgRef = useRef<SVGSVGElement | null>(null);
|
||||||
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [isClient, setIsClient] = useState(false);
|
const [isClient, setIsClient] = useState(false);
|
||||||
|
const [dimensions, setDimensions] = useState({
|
||||||
|
width: initialWidth,
|
||||||
|
height: initialHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set isClient to true on initial render
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsClient(true);
|
setIsClient(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Add effect to detect container size changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current || !isClient) return;
|
||||||
|
|
||||||
|
// Create ResizeObserver to detect size changes
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
const { width, height } = entry.contentRect;
|
||||||
|
// Ensure minimum dimensions
|
||||||
|
const newWidth = Math.max(width, minWidth);
|
||||||
|
const newHeight = Math.max(height, minHeight);
|
||||||
|
setDimensions({ width: newWidth, height: newHeight });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start observing the container
|
||||||
|
resizeObserver.observe(containerRef.current);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, [isClient, minWidth, minHeight]);
|
||||||
|
|
||||||
|
// Effect to render the word cloud whenever dimensions or words change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!svgRef.current || !isClient || !words.length) return;
|
if (!svgRef.current || !isClient || !words.length) return;
|
||||||
|
|
||||||
@ -36,7 +71,7 @@ export default function WordCloud({
|
|||||||
|
|
||||||
// Configure the layout
|
// Configure the layout
|
||||||
const layout = cloud()
|
const layout = cloud()
|
||||||
.size([width, height])
|
.size([dimensions.width, dimensions.height])
|
||||||
.words(
|
.words(
|
||||||
words.map((d) => ({
|
words.map((d) => ({
|
||||||
text: d.text,
|
text: d.text,
|
||||||
@ -53,7 +88,10 @@ export default function WordCloud({
|
|||||||
function draw(words: Word[]) {
|
function draw(words: Word[]) {
|
||||||
svg
|
svg
|
||||||
.append("g")
|
.append("g")
|
||||||
.attr("transform", `translate(${width / 2},${height / 2})`)
|
.attr(
|
||||||
|
"transform",
|
||||||
|
`translate(${dimensions.width / 2},${dimensions.height / 2})`
|
||||||
|
)
|
||||||
.selectAll("text")
|
.selectAll("text")
|
||||||
.data(words)
|
.data(words)
|
||||||
.enter()
|
.enter()
|
||||||
@ -87,7 +125,7 @@ export default function WordCloud({
|
|||||||
return () => {
|
return () => {
|
||||||
svg.selectAll("*").remove();
|
svg.selectAll("*").remove();
|
||||||
};
|
};
|
||||||
}, [words, width, height, isClient]);
|
}, [words, dimensions, isClient]);
|
||||||
|
|
||||||
if (!isClient) {
|
if (!isClient) {
|
||||||
return (
|
return (
|
||||||
@ -98,12 +136,21 @@ export default function WordCloud({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center w-full h-full">
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className="flex justify-center w-full h-full"
|
||||||
|
style={{ minHeight: `${minHeight}px` }}
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
ref={svgRef}
|
ref={svgRef}
|
||||||
width={width}
|
width={dimensions.width}
|
||||||
height={height}
|
height={dimensions.height}
|
||||||
|
className="w-full h-full"
|
||||||
aria-label="Word cloud visualization of categories"
|
aria-label="Word cloud visualization of categories"
|
||||||
|
style={{
|
||||||
|
maxWidth: "100%",
|
||||||
|
maxHeight: "100%",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
523
lib/metrics.ts
523
lib/metrics.ts
@ -13,295 +13,309 @@ interface CompanyConfig {
|
|||||||
sentimentAlert?: number;
|
sentimentAlert?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to calculate trend percentages
|
||||||
|
function calculateTrendPercentage(current: number, previous: number): number {
|
||||||
|
if (previous === 0) return 0; // Avoid division by zero
|
||||||
|
return ((current - previous) / previous) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock data for previous period - in a real app, this would come from database
|
||||||
|
const mockPreviousPeriodData = {
|
||||||
|
totalSessions: 120,
|
||||||
|
uniqueUsers: 85,
|
||||||
|
avgSessionLength: 240, // in seconds
|
||||||
|
avgResponseTime: 1.7, // in seconds
|
||||||
|
};
|
||||||
|
|
||||||
// List of common stop words - this can be expanded
|
// List of common stop words - this can be expanded
|
||||||
const stopWords = new Set([
|
const stopWords = new Set([
|
||||||
"assistant",
|
"assistant",
|
||||||
"user",
|
"user",
|
||||||
// Web
|
// Web
|
||||||
|
"bmp",
|
||||||
|
"co",
|
||||||
"com",
|
"com",
|
||||||
"www",
|
"css",
|
||||||
"http",
|
"gif",
|
||||||
"https",
|
|
||||||
"www2",
|
|
||||||
"href",
|
"href",
|
||||||
"html",
|
"html",
|
||||||
"php",
|
"http",
|
||||||
"js",
|
"https",
|
||||||
"css",
|
|
||||||
"xml",
|
|
||||||
"json",
|
|
||||||
"txt",
|
|
||||||
"jpg",
|
|
||||||
"jpeg",
|
|
||||||
"png",
|
|
||||||
"gif",
|
|
||||||
"bmp",
|
|
||||||
"svg",
|
|
||||||
"org",
|
|
||||||
"net",
|
|
||||||
"co",
|
|
||||||
"io",
|
"io",
|
||||||
|
"jpeg",
|
||||||
|
"jpg",
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"net",
|
||||||
|
"org",
|
||||||
|
"php",
|
||||||
|
"png",
|
||||||
|
"svg",
|
||||||
|
"txt",
|
||||||
|
"www",
|
||||||
|
"www2",
|
||||||
|
"xml",
|
||||||
// English stop words
|
// English stop words
|
||||||
"a",
|
"a",
|
||||||
|
"about",
|
||||||
|
"above",
|
||||||
|
"after",
|
||||||
|
"again",
|
||||||
|
"against",
|
||||||
|
"ain",
|
||||||
|
"all",
|
||||||
|
"am",
|
||||||
"an",
|
"an",
|
||||||
"the",
|
"any",
|
||||||
"is",
|
|
||||||
"are",
|
"are",
|
||||||
"was",
|
"aren",
|
||||||
"were",
|
"at",
|
||||||
"be",
|
"be",
|
||||||
"been",
|
"been",
|
||||||
|
"before",
|
||||||
"being",
|
"being",
|
||||||
"have",
|
"below",
|
||||||
"has",
|
"between",
|
||||||
"had",
|
"both",
|
||||||
"do",
|
"by",
|
||||||
"does",
|
"bye",
|
||||||
"did",
|
|
||||||
"will",
|
|
||||||
"would",
|
|
||||||
"should",
|
|
||||||
"can",
|
"can",
|
||||||
"could",
|
"could",
|
||||||
"may",
|
"couldn",
|
||||||
"might",
|
"d",
|
||||||
"must",
|
"did",
|
||||||
"am",
|
"didn",
|
||||||
"i",
|
"do",
|
||||||
"you",
|
"does",
|
||||||
"he",
|
"doesn",
|
||||||
"she",
|
"don",
|
||||||
"it",
|
|
||||||
"we",
|
|
||||||
"they",
|
|
||||||
"me",
|
|
||||||
"him",
|
|
||||||
"her",
|
|
||||||
"us",
|
|
||||||
"them",
|
|
||||||
"my",
|
|
||||||
"your",
|
|
||||||
"his",
|
|
||||||
"its",
|
|
||||||
"our",
|
|
||||||
"their",
|
|
||||||
"mine",
|
|
||||||
"yours",
|
|
||||||
"hers",
|
|
||||||
"ours",
|
|
||||||
"theirs",
|
|
||||||
"to",
|
|
||||||
"of",
|
|
||||||
"in",
|
|
||||||
"on",
|
|
||||||
"at",
|
|
||||||
"by",
|
|
||||||
"for",
|
|
||||||
"with",
|
|
||||||
"about",
|
|
||||||
"against",
|
|
||||||
"between",
|
|
||||||
"into",
|
|
||||||
"through",
|
|
||||||
"during",
|
|
||||||
"before",
|
|
||||||
"after",
|
|
||||||
"above",
|
|
||||||
"below",
|
|
||||||
"from",
|
|
||||||
"up",
|
|
||||||
"down",
|
"down",
|
||||||
"out",
|
"during",
|
||||||
"off",
|
|
||||||
"over",
|
|
||||||
"under",
|
|
||||||
"again",
|
|
||||||
"further",
|
|
||||||
"then",
|
|
||||||
"once",
|
|
||||||
"here",
|
|
||||||
"there",
|
|
||||||
"when",
|
|
||||||
"where",
|
|
||||||
"why",
|
|
||||||
"how",
|
|
||||||
"all",
|
|
||||||
"any",
|
|
||||||
"both",
|
|
||||||
"each",
|
"each",
|
||||||
"few",
|
"few",
|
||||||
|
"for",
|
||||||
|
"from",
|
||||||
|
"further",
|
||||||
|
"goodbye",
|
||||||
|
"had",
|
||||||
|
"hadn",
|
||||||
|
"has",
|
||||||
|
"hasn",
|
||||||
|
"have",
|
||||||
|
"haven",
|
||||||
|
"he",
|
||||||
|
"hello",
|
||||||
|
"her",
|
||||||
|
"here",
|
||||||
|
"hers",
|
||||||
|
"hi",
|
||||||
|
"him",
|
||||||
|
"his",
|
||||||
|
"how",
|
||||||
|
"i",
|
||||||
|
"in",
|
||||||
|
"into",
|
||||||
|
"is",
|
||||||
|
"isn",
|
||||||
|
"it",
|
||||||
|
"its",
|
||||||
|
"just",
|
||||||
|
"ll",
|
||||||
|
"m",
|
||||||
|
"ma",
|
||||||
|
"may",
|
||||||
|
"me",
|
||||||
|
"might",
|
||||||
|
"mightn",
|
||||||
|
"mine",
|
||||||
"more",
|
"more",
|
||||||
"most",
|
"most",
|
||||||
"other",
|
"must",
|
||||||
"some",
|
"mustn",
|
||||||
"such",
|
"my",
|
||||||
|
"needn",
|
||||||
"no",
|
"no",
|
||||||
"nor",
|
"nor",
|
||||||
"not",
|
"not",
|
||||||
"only",
|
|
||||||
"own",
|
|
||||||
"same",
|
|
||||||
"so",
|
|
||||||
"than",
|
|
||||||
"too",
|
|
||||||
"very",
|
|
||||||
"s",
|
|
||||||
"t",
|
|
||||||
"just",
|
|
||||||
"don",
|
|
||||||
"shouldve",
|
|
||||||
"now",
|
"now",
|
||||||
"d",
|
|
||||||
"ll",
|
|
||||||
"m",
|
|
||||||
"o",
|
"o",
|
||||||
"re",
|
"of",
|
||||||
"ve",
|
"off",
|
||||||
"y",
|
|
||||||
"ain",
|
|
||||||
"aren",
|
|
||||||
"couldn",
|
|
||||||
"didn",
|
|
||||||
"doesn",
|
|
||||||
"hadn",
|
|
||||||
"hasn",
|
|
||||||
"haven",
|
|
||||||
"isn",
|
|
||||||
"ma",
|
|
||||||
"mightn",
|
|
||||||
"mustn",
|
|
||||||
"needn",
|
|
||||||
"shan",
|
|
||||||
"shouldn",
|
|
||||||
"wasn",
|
|
||||||
"weren",
|
|
||||||
"won",
|
|
||||||
"wouldn",
|
|
||||||
"hi",
|
|
||||||
"hello",
|
|
||||||
"thanks",
|
|
||||||
"thank",
|
|
||||||
"please",
|
|
||||||
"ok",
|
"ok",
|
||||||
"okay",
|
"okay",
|
||||||
"yes",
|
"on",
|
||||||
|
"once",
|
||||||
|
"only",
|
||||||
|
"other",
|
||||||
|
"our",
|
||||||
|
"ours",
|
||||||
|
"out",
|
||||||
|
"over",
|
||||||
|
"own",
|
||||||
|
"please",
|
||||||
|
"re",
|
||||||
|
"s",
|
||||||
|
"same",
|
||||||
|
"shan",
|
||||||
|
"she",
|
||||||
|
"should",
|
||||||
|
"shouldn",
|
||||||
|
"shouldve",
|
||||||
|
"so",
|
||||||
|
"some",
|
||||||
|
"such",
|
||||||
|
"t",
|
||||||
|
"than",
|
||||||
|
"thank",
|
||||||
|
"thanks",
|
||||||
|
"the",
|
||||||
|
"their",
|
||||||
|
"theirs",
|
||||||
|
"them",
|
||||||
|
"then",
|
||||||
|
"there",
|
||||||
|
"they",
|
||||||
|
"through",
|
||||||
|
"to",
|
||||||
|
"too",
|
||||||
|
"under",
|
||||||
|
"up",
|
||||||
|
"us",
|
||||||
|
"ve",
|
||||||
|
"very",
|
||||||
|
"was",
|
||||||
|
"wasn",
|
||||||
|
"we",
|
||||||
|
"were",
|
||||||
|
"weren",
|
||||||
|
"when",
|
||||||
|
"where",
|
||||||
|
"why",
|
||||||
|
"will",
|
||||||
|
"with",
|
||||||
|
"won",
|
||||||
|
"would",
|
||||||
|
"wouldn",
|
||||||
|
"y",
|
||||||
"yeah",
|
"yeah",
|
||||||
"bye",
|
"yes",
|
||||||
"goodbye",
|
"you",
|
||||||
|
"your",
|
||||||
|
"yours",
|
||||||
// French stop words
|
// French stop words
|
||||||
|
"des",
|
||||||
|
"donc",
|
||||||
|
"et",
|
||||||
"la",
|
"la",
|
||||||
"le",
|
"le",
|
||||||
"les",
|
"les",
|
||||||
|
"mais",
|
||||||
|
"ou",
|
||||||
"un",
|
"un",
|
||||||
"une",
|
"une",
|
||||||
"des",
|
|
||||||
"et",
|
|
||||||
"ou",
|
|
||||||
"mais",
|
|
||||||
"donc",
|
|
||||||
// Dutch stop words
|
// Dutch stop words
|
||||||
"dit",
|
|
||||||
"ben",
|
|
||||||
"de",
|
|
||||||
"het",
|
|
||||||
"ik",
|
|
||||||
"jij",
|
|
||||||
"hij",
|
|
||||||
"zij",
|
|
||||||
"wij",
|
|
||||||
"jullie",
|
|
||||||
"deze",
|
|
||||||
"dit",
|
|
||||||
"dat",
|
|
||||||
"die",
|
|
||||||
"een",
|
|
||||||
"en",
|
|
||||||
"of",
|
|
||||||
"maar",
|
|
||||||
"want",
|
|
||||||
"omdat",
|
|
||||||
"dus",
|
|
||||||
"als",
|
|
||||||
"ook",
|
|
||||||
"dan",
|
|
||||||
"nu",
|
|
||||||
"nog",
|
|
||||||
"al",
|
|
||||||
"naar",
|
|
||||||
"voor",
|
|
||||||
"van",
|
|
||||||
"door",
|
|
||||||
"met",
|
|
||||||
"bij",
|
|
||||||
"tot",
|
|
||||||
"om",
|
|
||||||
"over",
|
|
||||||
"tussen",
|
|
||||||
"onder",
|
|
||||||
"boven",
|
|
||||||
"tegen",
|
|
||||||
"aan",
|
"aan",
|
||||||
"uit",
|
"al",
|
||||||
"sinds",
|
|
||||||
"tijdens",
|
|
||||||
"binnen",
|
|
||||||
"buiten",
|
|
||||||
"zonder",
|
|
||||||
"volgens",
|
|
||||||
"dankzij",
|
|
||||||
"ondanks",
|
|
||||||
"behalve",
|
|
||||||
"mits",
|
|
||||||
"tenzij",
|
|
||||||
"hoewel",
|
|
||||||
"alhoewel",
|
"alhoewel",
|
||||||
"toch",
|
"als",
|
||||||
"anders",
|
"anders",
|
||||||
"echter",
|
"behalve",
|
||||||
"wel",
|
"ben",
|
||||||
"niet",
|
|
||||||
"geen",
|
|
||||||
"iets",
|
|
||||||
"niets",
|
|
||||||
"veel",
|
|
||||||
"weinig",
|
|
||||||
"meer",
|
|
||||||
"meest",
|
|
||||||
"elk",
|
|
||||||
"ieder",
|
|
||||||
"sommige",
|
|
||||||
"hoe",
|
|
||||||
"wat",
|
|
||||||
"waar",
|
|
||||||
"wie",
|
|
||||||
"wanneer",
|
|
||||||
"waarom",
|
|
||||||
"welke",
|
|
||||||
"wordt",
|
|
||||||
"worden",
|
|
||||||
"werd",
|
|
||||||
"werden",
|
|
||||||
"geworden",
|
|
||||||
"zijn",
|
|
||||||
"ben",
|
"ben",
|
||||||
"bent",
|
"bent",
|
||||||
"was",
|
"bij",
|
||||||
"waren",
|
"binnen",
|
||||||
|
"boven",
|
||||||
|
"buiten",
|
||||||
|
"dan",
|
||||||
|
"dankzij",
|
||||||
|
"dat",
|
||||||
|
"de",
|
||||||
|
"deze",
|
||||||
|
"die",
|
||||||
|
"dit",
|
||||||
|
"dit",
|
||||||
|
"door",
|
||||||
|
"dus",
|
||||||
|
"echter",
|
||||||
|
"een",
|
||||||
|
"elk",
|
||||||
|
"en",
|
||||||
|
"geen",
|
||||||
|
"gehad",
|
||||||
"geweest",
|
"geweest",
|
||||||
"hebben",
|
"geworden",
|
||||||
"heb",
|
|
||||||
"hebt",
|
|
||||||
"heeft",
|
|
||||||
"had",
|
"had",
|
||||||
"hadden",
|
"hadden",
|
||||||
"gehad",
|
"heb",
|
||||||
"kunnen",
|
"hebben",
|
||||||
|
"hebt",
|
||||||
|
"heeft",
|
||||||
|
"het",
|
||||||
|
"hij",
|
||||||
|
"hoe",
|
||||||
|
"hoewel",
|
||||||
|
"ieder",
|
||||||
|
"iets",
|
||||||
|
"ik",
|
||||||
|
"jij",
|
||||||
|
"jullie",
|
||||||
"kan",
|
"kan",
|
||||||
"kunt",
|
|
||||||
"kon",
|
"kon",
|
||||||
"konden",
|
"konden",
|
||||||
"zullen",
|
"kunnen",
|
||||||
|
"kunt",
|
||||||
|
"maar",
|
||||||
|
"meer",
|
||||||
|
"meest",
|
||||||
|
"met",
|
||||||
|
"mits",
|
||||||
|
"naar",
|
||||||
|
"niet",
|
||||||
|
"niets",
|
||||||
|
"nog",
|
||||||
|
"nu",
|
||||||
|
"of",
|
||||||
|
"om",
|
||||||
|
"omdat",
|
||||||
|
"ondanks",
|
||||||
|
"onder",
|
||||||
|
"ook",
|
||||||
|
"over",
|
||||||
|
"sinds",
|
||||||
|
"sommige",
|
||||||
|
"tegen",
|
||||||
|
"tenzij",
|
||||||
|
"tijdens",
|
||||||
|
"toch",
|
||||||
|
"tot",
|
||||||
|
"tussen",
|
||||||
|
"uit",
|
||||||
|
"van",
|
||||||
|
"veel",
|
||||||
|
"volgens",
|
||||||
|
"voor",
|
||||||
|
"waar",
|
||||||
|
"waarom",
|
||||||
|
"wanneer",
|
||||||
|
"want",
|
||||||
|
"waren",
|
||||||
|
"was",
|
||||||
|
"wat",
|
||||||
|
"weinig",
|
||||||
|
"wel",
|
||||||
|
"welke",
|
||||||
|
"werd",
|
||||||
|
"werden",
|
||||||
|
"wie",
|
||||||
|
"wij",
|
||||||
|
"worden",
|
||||||
|
"wordt",
|
||||||
"zal",
|
"zal",
|
||||||
|
"zij",
|
||||||
|
"zijn",
|
||||||
|
"zonder",
|
||||||
|
"zullen",
|
||||||
"zult",
|
"zult",
|
||||||
// Add more domain-specific stop words if necessary
|
// Add more domain-specific stop words if necessary
|
||||||
]);
|
]);
|
||||||
@ -515,6 +529,24 @@ export function sessionMetrics(
|
|||||||
const avgSessionsPerDay =
|
const avgSessionsPerDay =
|
||||||
numDaysWithSessions > 0 ? totalSessions / numDaysWithSessions : 0;
|
numDaysWithSessions > 0 ? totalSessions / numDaysWithSessions : 0;
|
||||||
|
|
||||||
|
// Calculate trends
|
||||||
|
const totalSessionsTrend = calculateTrendPercentage(
|
||||||
|
totalSessions,
|
||||||
|
mockPreviousPeriodData.totalSessions
|
||||||
|
);
|
||||||
|
const uniqueUsersTrend = calculateTrendPercentage(
|
||||||
|
uniqueUsers,
|
||||||
|
mockPreviousPeriodData.uniqueUsers
|
||||||
|
);
|
||||||
|
const avgSessionLengthTrend = calculateTrendPercentage(
|
||||||
|
avgSessionLength,
|
||||||
|
mockPreviousPeriodData.avgSessionLength
|
||||||
|
);
|
||||||
|
const avgResponseTimeTrend = calculateTrendPercentage(
|
||||||
|
avgResponseTime,
|
||||||
|
mockPreviousPeriodData.avgResponseTime
|
||||||
|
);
|
||||||
|
|
||||||
// console.log("Debug metrics calculation:", {
|
// console.log("Debug metrics calculation:", {
|
||||||
// totalSessionDuration,
|
// totalSessionDuration,
|
||||||
// validSessionsForDuration,
|
// validSessionsForDuration,
|
||||||
@ -542,7 +574,16 @@ export function sessionMetrics(
|
|||||||
wordCloudData,
|
wordCloudData,
|
||||||
belowThresholdCount: alerts, // Corrected to match MetricsResult interface (belowThresholdCount)
|
belowThresholdCount: alerts, // Corrected to match MetricsResult interface (belowThresholdCount)
|
||||||
avgSessionsPerDay, // Added to satisfy MetricsResult interface
|
avgSessionsPerDay, // Added to satisfy MetricsResult interface
|
||||||
// Optional fields from MetricsResult that are not yet calculated can be added here or handled by the consumer
|
// Map trend values to the expected property names in MetricsResult
|
||||||
// avgSentiment, sentimentThreshold, lastUpdated, sessionTrend, usersTrend, avgSessionTimeTrend, avgResponseTimeTrend
|
sessionTrend: totalSessionsTrend,
|
||||||
|
usersTrend: uniqueUsersTrend,
|
||||||
|
avgSessionTimeTrend: avgSessionLengthTrend,
|
||||||
|
// For response time, a negative trend is actually positive (faster responses are better)
|
||||||
|
avgResponseTimeTrend: -avgResponseTimeTrend, // Invert as lower response time is better
|
||||||
|
// Additional fields
|
||||||
|
sentimentThreshold: companyConfig.sentimentAlert,
|
||||||
|
lastUpdated: Date.now(),
|
||||||
|
totalSessionDuration,
|
||||||
|
validSessionsForDuration,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
358
package-lock.json
generated
358
package-lock.json
generated
@ -50,8 +50,10 @@
|
|||||||
"eslint": "^9.27.0",
|
"eslint": "^9.27.0",
|
||||||
"eslint-config-next": "^15.3.2",
|
"eslint-config-next": "^15.3.2",
|
||||||
"eslint-plugin-prettier": "^5.4.0",
|
"eslint-plugin-prettier": "^5.4.0",
|
||||||
|
"markdownlint-cli2": "^0.18.1",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
|
"prettier-plugin-jinja-template": "^2.1.0",
|
||||||
"prisma": "^6.8.2",
|
"prisma": "^6.8.2",
|
||||||
"tailwindcss": "^4.1.7",
|
"tailwindcss": "^4.1.7",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
@ -1205,6 +1207,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@sindresorhus/merge-streams": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@swc/counter": {
|
"node_modules/@swc/counter": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
@ -1863,6 +1878,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/katex": {
|
||||||
|
"version": "0.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz",
|
||||||
|
"integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/leaflet": {
|
"node_modules/@types/leaflet": {
|
||||||
"version": "1.9.18",
|
"version": "1.9.18",
|
||||||
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.18.tgz",
|
||||||
@ -4838,6 +4860,37 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/globby": {
|
||||||
|
"version": "14.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz",
|
||||||
|
"integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sindresorhus/merge-streams": "^2.1.0",
|
||||||
|
"fast-glob": "^3.3.3",
|
||||||
|
"ignore": "^7.0.3",
|
||||||
|
"path-type": "^6.0.0",
|
||||||
|
"slash": "^5.1.0",
|
||||||
|
"unicorn-magic": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/globby/node_modules/ignore": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
@ -5775,6 +5828,13 @@
|
|||||||
"json5": "lib/cli.js"
|
"json5": "lib/cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonc-parser": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/jsx-ast-utils": {
|
"node_modules/jsx-ast-utils": {
|
||||||
"version": "3.3.5",
|
"version": "3.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
||||||
@ -5791,6 +5851,33 @@
|
|||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/katex": {
|
||||||
|
"version": "0.16.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz",
|
||||||
|
"integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
"https://opencollective.com/katex",
|
||||||
|
"https://github.com/sponsors/katex"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^8.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"katex": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/katex/node_modules/commander": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
@ -6086,6 +6173,16 @@
|
|||||||
"integrity": "sha512-KlA/wRSjpKl7tS9iRUdlG72oQ7qZ1IlVbVgHwoO10TBR/4gQ86uhKow6nlzMAJJhjCWKto8OeoAzzIzKSmN25A==",
|
"integrity": "sha512-KlA/wRSjpKl7tS9iRUdlG72oQ7qZ1IlVbVgHwoO10TBR/4gQ86uhKow6nlzMAJJhjCWKto8OeoAzzIzKSmN25A==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/linkify-it": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"uc.micro": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
@ -6161,6 +6258,98 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/markdown-it": {
|
||||||
|
"version": "14.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||||
|
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"argparse": "^2.0.1",
|
||||||
|
"entities": "^4.4.0",
|
||||||
|
"linkify-it": "^5.0.0",
|
||||||
|
"mdurl": "^2.0.0",
|
||||||
|
"punycode.js": "^2.3.1",
|
||||||
|
"uc.micro": "^2.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"markdown-it": "bin/markdown-it.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/markdown-it/node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/markdownlint": {
|
||||||
|
"version": "0.38.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.38.0.tgz",
|
||||||
|
"integrity": "sha512-xaSxkaU7wY/0852zGApM8LdlIfGCW8ETZ0Rr62IQtAnUMlMuifsg09vWJcNYeL4f0anvr8Vo4ZQar8jGpV0btQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"micromark": "4.0.2",
|
||||||
|
"micromark-core-commonmark": "2.0.3",
|
||||||
|
"micromark-extension-directive": "4.0.0",
|
||||||
|
"micromark-extension-gfm-autolink-literal": "2.1.0",
|
||||||
|
"micromark-extension-gfm-footnote": "2.1.0",
|
||||||
|
"micromark-extension-gfm-table": "2.1.1",
|
||||||
|
"micromark-extension-math": "3.1.0",
|
||||||
|
"micromark-util-types": "2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/DavidAnson"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/markdownlint-cli2": {
|
||||||
|
"version": "0.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.18.1.tgz",
|
||||||
|
"integrity": "sha512-/4Osri9QFGCZOCTkfA8qJF+XGjKYERSHkXzxSyS1hd3ZERJGjvsUao2h4wdnvpHp6Tu2Jh/bPHM0FE9JJza6ng==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"globby": "14.1.0",
|
||||||
|
"js-yaml": "4.1.0",
|
||||||
|
"jsonc-parser": "3.3.1",
|
||||||
|
"markdown-it": "14.1.0",
|
||||||
|
"markdownlint": "0.38.0",
|
||||||
|
"markdownlint-cli2-formatter-default": "0.0.5",
|
||||||
|
"micromatch": "4.0.8"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"markdownlint-cli2": "markdownlint-cli2-bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/DavidAnson"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/markdownlint-cli2-formatter-default": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.5.tgz",
|
||||||
|
"integrity": "sha512-4XKTwQ5m1+Txo2kuQ3Jgpo/KmnG+X90dWt4acufg6HVGadTUG5hzHF/wssp9b5MBYOMCnZ9RMPaU//uHsszF8Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/DavidAnson"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"markdownlint-cli2": ">=0.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@ -6323,6 +6512,13 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mdurl": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/merge2": {
|
"node_modules/merge2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
@ -6402,6 +6598,102 @@
|
|||||||
"micromark-util-types": "^2.0.0"
|
"micromark-util-types": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/micromark-extension-directive": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"devlop": "^1.0.0",
|
||||||
|
"micromark-factory-space": "^2.0.0",
|
||||||
|
"micromark-factory-whitespace": "^2.0.0",
|
||||||
|
"micromark-util-character": "^2.0.0",
|
||||||
|
"micromark-util-symbol": "^2.0.0",
|
||||||
|
"micromark-util-types": "^2.0.0",
|
||||||
|
"parse-entities": "^4.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/micromark-extension-gfm-autolink-literal": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"micromark-util-character": "^2.0.0",
|
||||||
|
"micromark-util-sanitize-uri": "^2.0.0",
|
||||||
|
"micromark-util-symbol": "^2.0.0",
|
||||||
|
"micromark-util-types": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/micromark-extension-gfm-footnote": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"devlop": "^1.0.0",
|
||||||
|
"micromark-core-commonmark": "^2.0.0",
|
||||||
|
"micromark-factory-space": "^2.0.0",
|
||||||
|
"micromark-util-character": "^2.0.0",
|
||||||
|
"micromark-util-normalize-identifier": "^2.0.0",
|
||||||
|
"micromark-util-sanitize-uri": "^2.0.0",
|
||||||
|
"micromark-util-symbol": "^2.0.0",
|
||||||
|
"micromark-util-types": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/micromark-extension-gfm-table": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"devlop": "^1.0.0",
|
||||||
|
"micromark-factory-space": "^2.0.0",
|
||||||
|
"micromark-util-character": "^2.0.0",
|
||||||
|
"micromark-util-symbol": "^2.0.0",
|
||||||
|
"micromark-util-types": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/micromark-extension-math": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/katex": "^0.16.0",
|
||||||
|
"devlop": "^1.0.0",
|
||||||
|
"katex": "^0.16.0",
|
||||||
|
"micromark-factory-space": "^2.0.0",
|
||||||
|
"micromark-util-character": "^2.0.0",
|
||||||
|
"micromark-util-symbol": "^2.0.0",
|
||||||
|
"micromark-util-types": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/micromark-factory-destination": {
|
"node_modules/micromark-factory-destination": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
|
||||||
@ -7387,6 +7679,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/path-type": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@ -7538,6 +7843,16 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier-plugin-jinja-template": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier-plugin-jinja-template/-/prettier-plugin-jinja-template-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-mzoCp2Oy9BDSug80fw3B3J4n4KQj1hRvoQOL1akqcDKBb5nvYxrik9zUEDs4AEJ6nK7QDTGoH0y9rx7AlnQ78Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"prettier": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pretty-format": {
|
"node_modules/pretty-format": {
|
||||||
"version": "3.8.0",
|
"version": "3.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
|
||||||
@ -7602,6 +7917,16 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/punycode.js": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
@ -8177,6 +8502,19 @@
|
|||||||
"is-arrayish": "^0.3.1"
|
"is-arrayish": "^0.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/slash": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@ -8748,6 +9086,13 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uc.micro": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/unbox-primitive": {
|
"node_modules/unbox-primitive": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
||||||
@ -8773,6 +9118,19 @@
|
|||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/unicorn-magic": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unified": {
|
"node_modules/unified": {
|
||||||
"version": "11.0.5",
|
"version": "11.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
||||||
|
|||||||
47
package.json
47
package.json
@ -46,8 +46,10 @@
|
|||||||
"eslint": "^9.27.0",
|
"eslint": "^9.27.0",
|
||||||
"eslint-config-next": "^15.3.2",
|
"eslint-config-next": "^15.3.2",
|
||||||
"eslint-plugin-prettier": "^5.4.0",
|
"eslint-plugin-prettier": "^5.4.0",
|
||||||
|
"markdownlint-cli2": "^0.18.1",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
|
"prettier-plugin-jinja-template": "^2.1.0",
|
||||||
"prisma": "^6.8.2",
|
"prisma": "^6.8.2",
|
||||||
"tailwindcss": "^4.1.7",
|
"tailwindcss": "^4.1.7",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
@ -64,7 +66,9 @@
|
|||||||
"prisma:migrate": "prisma migrate dev",
|
"prisma:migrate": "prisma migrate dev",
|
||||||
"prisma:seed": "node prisma/seed.mjs",
|
"prisma:seed": "node prisma/seed.mjs",
|
||||||
"prisma:studio": "prisma studio",
|
"prisma:studio": "prisma studio",
|
||||||
"start": "next start"
|
"start": "next start",
|
||||||
|
"lint:md": "markdownlint-cli2 \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"",
|
||||||
|
"lint:md:fix": "markdownlint-cli2 --fix \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\""
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"bracketSpacing": true,
|
"bracketSpacing": true,
|
||||||
@ -74,6 +78,45 @@
|
|||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"useTabs": false
|
"useTabs": false,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.md",
|
||||||
|
"*.markdown"
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"proseWrap": "preserve",
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"prettier-plugin-jinja-template"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"markdownlint-cli2": {
|
||||||
|
"config": {
|
||||||
|
"MD007": {
|
||||||
|
"indent": 4,
|
||||||
|
"start_indented": false,
|
||||||
|
"start_indent": 4
|
||||||
|
},
|
||||||
|
"MD013": false,
|
||||||
|
"MD030": {
|
||||||
|
"ul_single": 3,
|
||||||
|
"ol_single": 2,
|
||||||
|
"ul_multi": 3,
|
||||||
|
"ol_multi": 2
|
||||||
|
},
|
||||||
|
"MD033": false
|
||||||
|
},
|
||||||
|
"ignores": [
|
||||||
|
"node_modules",
|
||||||
|
".git",
|
||||||
|
"*.json"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user