@@ -44,8 +112,10 @@ export default function DataUpload() {
@@ -53,22 +123,38 @@ export default function DataUpload() {
diff --git a/public/locales/de/common.json b/public/locales/de/common.json
index 124dc601..502e899d 100644
--- a/public/locales/de/common.json
+++ b/public/locales/de/common.json
@@ -1,3 +1,4 @@
{
- "greeting": "Hallo"
+ "greeting": "Hallo",
+ "backToDashboardNavText": "Zurück zum Dashboard"
}
diff --git a/public/locales/de/csv-upload.json b/public/locales/de/csv-upload.json
new file mode 100644
index 00000000..118a6aaf
--- /dev/null
+++ b/public/locales/de/csv-upload.json
@@ -0,0 +1,11 @@
+{
+ "dataUploadHeading": "Manueller Datenupload",
+ "dataUploadExplanation": "Hier können Sie Messwerte für diese senseBox hochladen. Dies kann für senseBoxen nützlich sein, die ihre Messwerte auf einer SD-Karte protokollieren, wenn keine direkte Kommunikation mit openSenseMap möglich ist. Wählen Sie entweder eine Datei aus oder kopieren Sie die Daten in das Textfeld. Die akzeptierten Datenformate sind <2>hier<2> beschrieben.",
+ "uploadFileLabel": "Datei hochladen",
+ "selectFormatLabel": "Format auswählen",
+ "inputTextAreaPlaceholder": "Messdaten hier einfügen...",
+ "textAreaCutoffHint": "Nur die ersten 3000 Zeichen von {{length}} werden angezeigt",
+ "uploadButtonLabel": "Hochladen",
+ "successMessage": "Messdaten erfolgreich hochgeladen",
+ "errorMessage": "Messdaten konnten nicht erfolgreich hochgeladen werden: {{message}}"
+}
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index f750bd11..75d33155 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -1,3 +1,4 @@
{
- "greeting": "Hello"
+ "greeting": "Hello",
+ "backToDashboardNavText": "Back to Dashboard"
}
diff --git a/public/locales/en/csv-upload.json b/public/locales/en/csv-upload.json
new file mode 100644
index 00000000..a610af5a
--- /dev/null
+++ b/public/locales/en/csv-upload.json
@@ -0,0 +1,11 @@
+{
+ "dataUploadHeading": "Manual Data Upload",
+ "dataUploadExplanation": "Here you can upload measurements for this senseBox. This can be of use for senseBoxes that log their measurements to an SD card when no means of direct communication to openSenseMap are available. Either select a file, or copy the data into the text field. Accepted data formats are described <2>here<2>.",
+ "uploadFileLabel": "Upload File",
+ "selectFormatLabel": "Select Format",
+ "inputTextAreaPlaceholder": "Paste measurement data here...",
+ "textAreaCutoffHint": "Showing first 3000 characters of {{length}}",
+ "uploadButtonLabel": "Upload",
+ "successMessage": "Successfully uploaded measurement data",
+ "errorMessage": "Unable to successfully upload measurement data: {{message}}"
+}
diff --git a/tests/routes/api.stats.spec.ts b/tests/routes/api.stats.spec.ts
index 75bc0c46..9685f24f 100644
--- a/tests/routes/api.stats.spec.ts
+++ b/tests/routes/api.stats.spec.ts
@@ -1,155 +1,118 @@
-import { sql } from "drizzle-orm";
-import { type LoaderFunctionArgs } from "react-router";
-import { BASE_URL } from "vitest.setup";
-import { drizzleClient } from "~/db.server";
-import { loader } from "~/routes/api.stats";
-
-describe("openSenseMap API Routes: /stats", () => {
- let boxCount: number = 0;
- let measurementsCount: number = 0;
- beforeAll(async () => {
- const [count] = await drizzleClient.execute(
- sql`SELECT * FROM approximate_row_count('device');`,
- );
- boxCount = Number(count.approximate_row_count);
-
- const [count2] = await drizzleClient.execute(
- sql`SELECT * FROM approximate_row_count('measurement');`,
- );
- measurementsCount = Number(count2.approximate_row_count);
- });
-
- it("should return /stats correctly", async () => {
- // Arrange
- const request = new Request(`${BASE_URL}/stats`, {
- method: "GET",
- headers: { Accept: "application/json" },
- });
-
- // Act
- const dataFunctionValue = await loader({
- request: request,
- } as LoaderFunctionArgs);
- const response = dataFunctionValue as Response;
- const body = await response.json();
-
- // Assert
- expect(response.status).toBe(200);
- const [boxes, measurements] = body;
- expect(boxes).toBe(boxCount);
- expect(measurements).toBe(measurementsCount);
- });
-
- it("should return a json array with three numbers", async () => {
- // Arrange
- const request = new Request(`${BASE_URL}/stats`, {
- method: "GET",
- headers: { Accept: "application/json" },
- });
-
- // Act
- const dataFunctionValue = await loader({
- request: request,
- } as LoaderFunctionArgs);
- const response = dataFunctionValue as Response;
- const body = await response.json();
-
- // Assert
- expect(response.status).toBe(200);
- expect(response.headers.get("content-type")).toBe(
- "application/json; charset=utf-8",
- );
- expect(body).not.toBeNull();
- expect(body).toBeDefined();
- expect(Array.isArray(body)).toBe(true);
- expect(body.every((n: any) => typeof n === "number")).toBe(true);
- });
-
- it("should return a json array with three strings when called with parameter human=true", async () => {
- // Arrange
- const url = `${BASE_URL}/stats?human=true`;
- const request = new Request(url, {
- method: "GET",
- headers: { Accept: "application/json" },
- });
-
- // Act
- const dataFunctionValue = await loader({
- request: request,
- } as LoaderFunctionArgs);
- const response = dataFunctionValue as Response;
- const body = await response.json();
-
- // Assert
- expect(response.status).toBe(200);
- expect(response.headers.get("content-type")).toBe(
- "application/json; charset=utf-8",
- );
- expect(body).not.toBeNull();
- expect(body).toBeDefined();
- expect(Array.isArray(body)).toBe(true);
- expect(body.every((n: any) => typeof n === "string")).toBe(true);
- });
-
- it("should return a json array with three numbers when called with parameter human=false", async () => {
- // Arrange
- const url = `${BASE_URL}/stats?human=false`;
- const request = new Request(url, {
- method: "GET",
- headers: { Accept: "application/json" },
- });
-
- // Act
- const dataFunctionValue = await loader({
- request: request,
- } as LoaderFunctionArgs);
- const response = dataFunctionValue as Response;
- const body = await response.json();
-
- // Assert
- expect(response.status).toBe(200);
- expect(response.headers.get("content-type")).toBe(
- "application/json; charset=utf-8",
- );
- expect(body).not.toBeNull();
- expect(body).toBeDefined();
- expect(Array.isArray(body)).toBe(true);
- expect(body.every((n: any) => typeof n === "number")).toBe(true);
- });
-
- it("should return an error if parameter human is not true or false", async () => {
- // Arrange
- const urls = [
- `${BASE_URL}/stats?human=wrong1`,
- `${BASE_URL}/stats?human=wrong2`,
- ];
- const requests = urls.map(
- (url) =>
- new Request(url, {
- method: "GET",
- headers: { Accept: "application/json" },
- }),
- );
-
- // Act
- const responses = await Promise.all(
- requests.map((request) =>
- loader({
- request: request,
- } as LoaderFunctionArgs),
- ),
- );
- const bodies = await Promise.all(responses.map((res) => res.json()));
-
- // Assert
- for (let i = 0; i < responses.length; i++) {
- expect(responses[i].status).toBe(400);
- expect(responses[i].headers.get("content-type")).toBe(
- "application/json; charset=utf-8",
- );
- expect(bodies[i].message).toBe(
- "Illegal value for parameter human. allowed values: true, false",
- );
- }
- });
-});
+import { type LoaderFunctionArgs } from 'react-router'
+import { BASE_URL } from 'vitest.setup'
+import { loader } from '~/routes/api.stats'
+
+describe('openSenseMap API Routes: /stats', () => {
+ it('should return a json array with three numbers', async () => {
+ // Arrange
+ const request = new Request(`${BASE_URL}/stats`, {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ })
+
+ // Act
+ const dataFunctionValue = await loader({
+ request: request,
+ } as LoaderFunctionArgs)
+ const response = dataFunctionValue as Response
+ const body = await response.json()
+
+ // Assert
+ expect(response.status).toBe(200)
+ expect(response.headers.get('content-type')).toBe(
+ 'application/json; charset=utf-8',
+ )
+ expect(body).not.toBeNull()
+ expect(body).toBeDefined()
+ expect(Array.isArray(body)).toBe(true)
+ expect(body.every((n: any) => typeof n === 'number')).toBe(true)
+ })
+
+ it('should return a json array with three strings when called with parameter human=true', async () => {
+ // Arrange
+ const url = `${BASE_URL}/stats?human=true`
+ const request = new Request(url, {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ })
+
+ // Act
+ const dataFunctionValue = await loader({
+ request: request,
+ } as LoaderFunctionArgs)
+ const response = dataFunctionValue as Response
+ const body = await response.json()
+
+ // Assert
+ expect(response.status).toBe(200)
+ expect(response.headers.get('content-type')).toBe(
+ 'application/json; charset=utf-8',
+ )
+ expect(body).not.toBeNull()
+ expect(body).toBeDefined()
+ expect(Array.isArray(body)).toBe(true)
+ expect(body.every((n: any) => typeof n === 'string')).toBe(true)
+ })
+
+ it('should return a json array with three numbers when called with parameter human=false', async () => {
+ // Arrange
+ const url = `${BASE_URL}/stats?human=false`
+ const request = new Request(url, {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ })
+
+ // Act
+ const dataFunctionValue = await loader({
+ request: request,
+ } as LoaderFunctionArgs)
+ const response = dataFunctionValue as Response
+ const body = await response.json()
+
+ // Assert
+ expect(response.status).toBe(200)
+ expect(response.headers.get('content-type')).toBe(
+ 'application/json; charset=utf-8',
+ )
+ expect(body).not.toBeNull()
+ expect(body).toBeDefined()
+ expect(Array.isArray(body)).toBe(true)
+ expect(body.every((n: any) => typeof n === 'number')).toBe(true)
+ })
+
+ it('should return an error if parameter human is not true or false', async () => {
+ // Arrange
+ const urls = [
+ `${BASE_URL}/stats?human=wrong1`,
+ `${BASE_URL}/stats?human=wrong2`,
+ ]
+ const requests = urls.map(
+ (url) =>
+ new Request(url, {
+ method: 'GET',
+ headers: { Accept: 'application/json' },
+ }),
+ )
+
+ // Act
+ const responses = await Promise.all(
+ requests.map((request) =>
+ loader({
+ request: request,
+ } as LoaderFunctionArgs),
+ ),
+ )
+ const bodies = await Promise.all(responses.map((res) => res.json()))
+
+ // Assert
+ for (let i = 0; i < responses.length; i++) {
+ expect(responses[i].status).toBe(400)
+ expect(responses[i].headers.get('content-type')).toBe(
+ 'application/json; charset=utf-8',
+ )
+ expect(bodies[i].message).toBe(
+ 'Illegal value for parameter human. allowed values: true, false',
+ )
+ }
+ })
+})