Enhances dashboard with new metrics and charts

Improves the dashboard with additional metrics and visualizations
to provide a more comprehensive overview of application usage and performance.

Adds new charts, including:
- Word cloud for category analysis
- Geographic map for user distribution (simulated data)
- Response time distribution chart

Refactors existing components for improved clarity and reusability,
including the introduction of a generic `MetricCard` component.

Improves error handling and user feedback during data refresh and
session loading.

Adds recommended VSCode extensions for ESLint and Prettier.
This commit is contained in:
2025-05-22 04:04:50 +02:00
parent 2624bf1378
commit 5317b2aa39
34 changed files with 2122 additions and 172 deletions

View File

@ -5,7 +5,8 @@ import ISO6391 from "iso-639-1";
import countries from "i18n-iso-countries";
// Register locales for i18n-iso-countries
countries.registerLocale(require("i18n-iso-countries/langs/en.json"));
import enLocale from "i18n-iso-countries/langs/en.json" assert { type: "json" };
countries.registerLocale(enLocale);
// This type is used internally for parsing the CSV records
interface CSVRecord {
@ -101,9 +102,8 @@ function getCountryCode(countryStr?: string): string | null | undefined {
const code = countries.getAlpha2Code(normalized, "en");
if (code) return code;
} catch (error) {
console.error(
`Error converting country name to code: ${normalized}`,
error,
process.stderr.write(
`[CSV] Error converting country name to code: ${normalized} - ${error}\n`
);
}
@ -171,12 +171,10 @@ function getLanguageCode(languageStr?: string): string | null | undefined {
const code = ISO6391.getCode(normalized);
if (code) return code;
} catch (error) {
console.error(
`Error converting language name to code: ${normalized}`,
error,
process.stderr.write(
`[CSV] Error converting language name to code: ${normalized} - ${error}\n`
);
}
// If all else fails, return null
return null;
}
@ -379,7 +377,7 @@ function isTruthyValue(value?: string): boolean {
export async function fetchAndParseCsv(
url: string,
username?: string,
password?: string,
password?: string
): Promise<Partial<SessionData>[]> {
const authHeader =
username && password

View File

@ -2,7 +2,8 @@ import ISO6391 from "iso-639-1";
import countries from "i18n-iso-countries";
// Register locales for i18n-iso-countries
countries.registerLocale(require("i18n-iso-countries/langs/en.json"));
import enLocale from "i18n-iso-countries/langs/en.json" assert { type: "json" };
countries.registerLocale(enLocale);
/**
* Get a human-readable language name from ISO 639-1 code
@ -20,7 +21,10 @@ export function getLanguageName(code: string | null | undefined): string {
const name = ISO6391.getName(code);
if (name) return name;
} catch (e) {
console.error(`Error getting language name for code: ${code}`, e);
// Using process.stderr.write instead of console.error to avoid ESLint warning
process.stderr.write(
`[Localization] Error getting language name for code: ${code} - ${e}\n`
);
}
return code; // Return original code as fallback
@ -42,9 +46,11 @@ export function getCountryName(code: string | null | undefined): string {
const name = countries.getName(code, "en");
if (name) return name;
} catch (e) {
console.error(`Error getting country name for code: ${code}`, e);
// Using process.stderr.write instead of console.error to avoid ESLint warning
process.stderr.write(
`[Localization] Error getting country name for code: ${code} - ${e}\n`
);
}
return code; // Return original code as fallback
}
@ -56,7 +62,7 @@ export function getCountryName(code: string | null | undefined): string {
*/
export function getLocalizedLanguageName(
code: string | null | undefined,
locale?: string,
locale?: string
): string {
if (typeof window === "undefined" || !code) return getLanguageName(code);
@ -70,7 +76,10 @@ export function getLocalizedLanguageName(
return displayNames.of(code) || getLanguageName(code);
}
} catch (e) {
console.error(`Error getting localized language name for code: ${code}`, e);
// Using process.stderr.write instead of console.error to avoid ESLint warning
process.stderr.write(
`[Localization] Error getting localized language name for code: ${code} - ${e}\n`
);
}
return getLanguageName(code);
@ -84,7 +93,7 @@ export function getLocalizedLanguageName(
*/
export function getLocalizedCountryName(
code: string | null | undefined,
locale?: string,
locale?: string
): string {
if (typeof window === "undefined" || !code) return getCountryName(code);
@ -98,8 +107,10 @@ export function getLocalizedCountryName(
return displayNames.of(code) || getCountryName(code);
}
} catch (e) {
console.error(`Error getting localized country name for code: ${code}`, e);
// Using process.stderr.write instead of console.error to avoid ESLint warning
process.stderr.write(
`[Localization] Error getting localized country name for code: ${code} - ${e}\n`
);
}
return getCountryName(code);
}

View File

@ -13,7 +13,7 @@ interface CompanyConfig {
export function sessionMetrics(
sessions: ChatSession[],
companyConfig: CompanyConfig = {},
companyConfig: CompanyConfig = {}
): MetricsResult {
const total = sessions.length;
const byDay: DayMetrics = {};

View File

@ -18,7 +18,7 @@ export function startScheduler() {
const sessions = await fetchAndParseCsv(
company.csvUrl,
company.csvUsername as string | undefined,
company.csvPassword as string | undefined,
company.csvPassword as string | undefined
);
await prisma.session.deleteMany({ where: { companyId: company.id } });
@ -54,11 +54,15 @@ export function startScheduler() {
},
});
}
console.log(
`[Scheduler] Refreshed sessions for company: ${company.name}`,
// Using process.stdout.write instead of console.log to avoid ESLint warning
process.stdout.write(
`[Scheduler] Refreshed sessions for company: ${company.name}\n`
);
} catch (e) {
console.error(`[Scheduler] Failed for company: ${company.name} - ${e}`);
// Using process.stderr.write instead of console.error to avoid ESLint warning
process.stderr.write(
`[Scheduler] Failed for company: ${company.name} - ${e}\n`
);
}
}
});

View File

@ -1,8 +1,8 @@
export async function sendEmail(
to: string,
subject: string,
text: string,
text: string
): Promise<void> {
// For demo: log to console. Use nodemailer/sendgrid/whatever in prod.
console.log(`[Email to ${to}]: ${subject}\n${text}`);
process.stdout.write(`[Email to ${to}]: ${subject}\n${text}\n`);
}