From 198c5ccd5c9d5894e24e0ae8abc582f6d669f4ba Mon Sep 17 00:00:00 2001 From: Benjamin Date: Fri, 22 Nov 2024 01:38:28 +0100 Subject: [PATCH] Adding vue and stats --- pointMaster/Controllers/StatsApiController.cs | 108 +++++++++++++++++ pointMaster/Controllers/StatsController.cs | 22 ++++ pointMaster/Dockerfile | 7 ++ ...cs => 20241121135108_DateTime.Designer.cs} | 17 ++- ..._initial.cs => 20241121135108_DateTime.cs} | 17 ++- .../Migrations/DataContextModelSnapshot.cs | 13 +++ pointMaster/Views/Shared/_Layout.cshtml | 3 + pointMaster/Views/Stats/Index.cshtml | 7 ++ pointMaster/js/package.json | 24 ++++ pointMaster/js/src/components/HelloWorld.vue | 104 +++++++++++++++++ pointMaster/js/src/components/Stats.vue | 110 ++++++++++++++++++ pointMaster/js/src/main.ts | 11 ++ pointMaster/js/src/shims-vue.d.ts | 6 + pointMaster/js/src/typings.d.ts | 13 +++ pointMaster/js/tsconfig.json | 42 +++++++ pointMaster/js/vue.config.js | 19 +++ pointMaster/pointMaster.csproj | 2 + 17 files changed, 517 insertions(+), 8 deletions(-) create mode 100644 pointMaster/Controllers/StatsApiController.cs create mode 100644 pointMaster/Controllers/StatsController.cs rename pointMaster/Migrations/{20241116003844_initial.Designer.cs => 20241121135108_DateTime.Designer.cs} (90%) rename pointMaster/Migrations/{20241116003844_initial.cs => 20241121135108_DateTime.cs} (88%) create mode 100644 pointMaster/Views/Stats/Index.cshtml create mode 100644 pointMaster/js/package.json create mode 100644 pointMaster/js/src/components/HelloWorld.vue create mode 100644 pointMaster/js/src/components/Stats.vue create mode 100644 pointMaster/js/src/main.ts create mode 100644 pointMaster/js/src/shims-vue.d.ts create mode 100644 pointMaster/js/src/typings.d.ts create mode 100644 pointMaster/js/tsconfig.json create mode 100644 pointMaster/js/vue.config.js diff --git a/pointMaster/Controllers/StatsApiController.cs b/pointMaster/Controllers/StatsApiController.cs new file mode 100644 index 0000000..87b5919 --- /dev/null +++ b/pointMaster/Controllers/StatsApiController.cs @@ -0,0 +1,108 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using pointMaster.Data; +using pointMaster.Models; + +namespace pointMaster.Controllers +{ + [ApiController] + [Route("api")] + public class StatsApiController : ControllerBase + { + readonly DataContext dataContext; + + public StatsApiController(DataContext dataContext) + { + this.dataContext = dataContext; + } + + [Authorize(Policy = Roles.Editor)] + [HttpGet("GetPointsOverTime")] + public async Task GetList() + { + var pointData = await dataContext.Points.Include(x => x.Patrulje).Include(x => x.Poster).ToListAsync(); + + var pointGrouped = pointData.OrderBy(x => x.DateCreated).GroupBy(x => x.Patrulje.Name).ToList(); + + var retval = new List(); + + var lastChange = DateTime.Now.ToString("MM/d/yyyy H:m:s").Replace('.', ':'); + + foreach (var group in pointGrouped) + { + var data = new List(); + + var total = 0; + foreach (var point in group) + { + var date = DateTime.Parse(point.DateCreated.ToString()); + + total += point.Points + point.Turnout; + + data.Add(new PointChartDataModel + { + x = date.ToString("MM/d/yyyy H:m:s").Replace('.', ':'), + y = total + }); + } + + data.Add(new PointChartDataModel + { + x = lastChange, + y = total + }); + + retval.Add(new PointChartModel + { + Name = group.Key, + Data = data + }); + } + + var rval = JsonConvert.SerializeObject(retval, new JsonSerializerSettings() + { + ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore, + + }); + + return Ok(rval); + } + + [Route("getstats")] + public async Task GetStats() + { + var vm = new List(); + + var pointData = await dataContext.Points.ToListAsync(); + + vm.Add(new StatModel + { + Title = "Points givet", + Value = pointData.Sum(x => x.Points + x.Turnout).ToString() + }); + + return Ok(JsonConvert.SerializeObject(vm)); + } + + public class PointChartModel + { + [JsonProperty("name")] + public string Name { get; set; } = null!; + [JsonProperty("data")] + public List Data { get; set; } = null!; + } + public class PointChartDataModel + { + public string x { get; set; } = null!; + public int y { get; set; } + } + + public class StatModel + { + public string Title { get; set; } = null!; + public string Value { get; set; } = null!; + } + } +} \ No newline at end of file diff --git a/pointMaster/Controllers/StatsController.cs b/pointMaster/Controllers/StatsController.cs new file mode 100644 index 0000000..ef106ef --- /dev/null +++ b/pointMaster/Controllers/StatsController.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using pointMaster.Data; +using pointMaster.Models; + +namespace pointMaster.Controllers +{ + public class StatsController : Controller + { + private readonly DataContext dataContext; + + public StatsController(DataContext dataContext) + { + this.dataContext = dataContext; + } + public async Task Index() + { + return View(); + } + } +} diff --git a/pointMaster/Dockerfile b/pointMaster/Dockerfile index e4b80b2..c6ce8b9 100644 --- a/pointMaster/Dockerfile +++ b/pointMaster/Dockerfile @@ -18,6 +18,13 @@ COPY . . WORKDIR "/src/pointMaster" RUN dotnet build "./pointMaster.csproj" -c $BUILD_CONFIGURATION -o /app/build +FROM node:20 AS node-build +WORKDIR /js-app +COPY ["js/package.json", "js/package-lock.json*", "./"] +RUN npm install +COPY js/ . +RUN npm run build + # This stage is used to publish the service project to be copied to the final stage FROM build AS publish ARG BUILD_CONFIGURATION=Release diff --git a/pointMaster/Migrations/20241116003844_initial.Designer.cs b/pointMaster/Migrations/20241121135108_DateTime.Designer.cs similarity index 90% rename from pointMaster/Migrations/20241116003844_initial.Designer.cs rename to pointMaster/Migrations/20241121135108_DateTime.Designer.cs index 0d0b1d7..6cdfa42 100644 --- a/pointMaster/Migrations/20241116003844_initial.Designer.cs +++ b/pointMaster/Migrations/20241121135108_DateTime.Designer.cs @@ -1,4 +1,5 @@ // +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -11,8 +12,8 @@ using pointMaster.Data; namespace pointMaster.Migrations { [DbContext(typeof(DataContext))] - [Migration("20241116003844_initial")] - partial class initial + [Migration("20241121135108_DateTime")] + partial class DateTime { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -32,6 +33,9 @@ namespace pointMaster.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + b.Property("Name") .IsRequired() .HasColumnType("text"); @@ -52,6 +56,9 @@ namespace pointMaster.Migrations b.Property("Age") .HasColumnType("integer"); + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + b.Property("Name") .IsRequired() .HasColumnType("text"); @@ -74,6 +81,9 @@ namespace pointMaster.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + b.Property("PatruljeId") .HasColumnType("integer"); @@ -103,6 +113,9 @@ namespace pointMaster.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + b.Property("Description") .IsRequired() .HasColumnType("text"); diff --git a/pointMaster/Migrations/20241116003844_initial.cs b/pointMaster/Migrations/20241121135108_DateTime.cs similarity index 88% rename from pointMaster/Migrations/20241116003844_initial.cs rename to pointMaster/Migrations/20241121135108_DateTime.cs index a8dbccf..e113569 100644 --- a/pointMaster/Migrations/20241116003844_initial.cs +++ b/pointMaster/Migrations/20241121135108_DateTime.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using System; +using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable @@ -6,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace pointMaster.Migrations { /// - public partial class initial : Migration + public partial class DateTime : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) @@ -17,7 +18,8 @@ namespace pointMaster.Migrations { Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Name = table.Column(type: "text", nullable: false) + Name = table.Column(type: "text", nullable: false), + DateCreated = table.Column(type: "timestamp with time zone", nullable: true) }, constraints: table => { @@ -32,7 +34,8 @@ namespace pointMaster.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), Name = table.Column(type: "text", nullable: false), Location = table.Column(type: "text", nullable: false), - Description = table.Column(type: "text", nullable: false) + Description = table.Column(type: "text", nullable: false), + DateCreated = table.Column(type: "timestamp with time zone", nullable: true) }, constraints: table => { @@ -47,7 +50,8 @@ namespace pointMaster.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), Name = table.Column(type: "text", nullable: false), Age = table.Column(type: "integer", nullable: false), - PatruljeId = table.Column(type: "integer", nullable: false) + PatruljeId = table.Column(type: "integer", nullable: false), + DateCreated = table.Column(type: "timestamp with time zone", nullable: true) }, constraints: table => { @@ -69,7 +73,8 @@ namespace pointMaster.Migrations Points = table.Column(type: "integer", nullable: false), Turnout = table.Column(type: "integer", nullable: false), PatruljeId = table.Column(type: "integer", nullable: false), - PosterId = table.Column(type: "integer", nullable: false) + PosterId = table.Column(type: "integer", nullable: false), + DateCreated = table.Column(type: "timestamp with time zone", nullable: true) }, constraints: table => { diff --git a/pointMaster/Migrations/DataContextModelSnapshot.cs b/pointMaster/Migrations/DataContextModelSnapshot.cs index 5909272..d400d6a 100644 --- a/pointMaster/Migrations/DataContextModelSnapshot.cs +++ b/pointMaster/Migrations/DataContextModelSnapshot.cs @@ -1,4 +1,5 @@ // +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -29,6 +30,9 @@ namespace pointMaster.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + b.Property("Name") .IsRequired() .HasColumnType("text"); @@ -49,6 +53,9 @@ namespace pointMaster.Migrations b.Property("Age") .HasColumnType("integer"); + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + b.Property("Name") .IsRequired() .HasColumnType("text"); @@ -71,6 +78,9 @@ namespace pointMaster.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + b.Property("PatruljeId") .HasColumnType("integer"); @@ -100,6 +110,9 @@ namespace pointMaster.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + b.Property("Description") .IsRequired() .HasColumnType("text"); diff --git a/pointMaster/Views/Shared/_Layout.cshtml b/pointMaster/Views/Shared/_Layout.cshtml index 9e38656..73a55c8 100644 --- a/pointMaster/Views/Shared/_Layout.cshtml +++ b/pointMaster/Views/Shared/_Layout.cshtml @@ -10,6 +10,7 @@ +
+ @await RenderSectionAsync("Scripts", required: false) diff --git a/pointMaster/Views/Stats/Index.cshtml b/pointMaster/Views/Stats/Index.cshtml new file mode 100644 index 0000000..8d737cc --- /dev/null +++ b/pointMaster/Views/Stats/Index.cshtml @@ -0,0 +1,7 @@ +@* + For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 +*@ +@{ + ViewData["title"] = "Stats"; +} + \ No newline at end of file diff --git a/pointMaster/js/package.json b/pointMaster/js/package.json new file mode 100644 index 0000000..5581598 --- /dev/null +++ b/pointMaster/js/package.json @@ -0,0 +1,24 @@ +{ + "name": "website", + "version": "0.1.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "watch": "vue-cli-service build --mode development --watch", + "build": "vue-cli-service build" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@vue/cli-plugin-typescript": "^5.0.8", + "@vue/cli-service": "^5.0.8", + "apexcharts": "^4.0.0", + "axios": "^1.7.7", + "vue": "^3.5.13", + "vue-class-component": "^7.2.6" + }, + "devDependencies": { + "vue-cli-service": "^5.0.10" + } +} diff --git a/pointMaster/js/src/components/HelloWorld.vue b/pointMaster/js/src/components/HelloWorld.vue new file mode 100644 index 0000000..634057f --- /dev/null +++ b/pointMaster/js/src/components/HelloWorld.vue @@ -0,0 +1,104 @@ + + + + + \ No newline at end of file diff --git a/pointMaster/js/src/components/Stats.vue b/pointMaster/js/src/components/Stats.vue new file mode 100644 index 0000000..8c59d78 --- /dev/null +++ b/pointMaster/js/src/components/Stats.vue @@ -0,0 +1,110 @@ + + + + + \ No newline at end of file diff --git a/pointMaster/js/src/main.ts b/pointMaster/js/src/main.ts new file mode 100644 index 0000000..34254e4 --- /dev/null +++ b/pointMaster/js/src/main.ts @@ -0,0 +1,11 @@ +import { createApp } from "vue"; +import HelloWorld from "./components/HelloWorld.vue"; +import Stats from "./components/Stats.vue"; + +const app = createApp({}); + +app.component("hello-world", Stats); + +(window as any).app = app; + +app.mount("#app"); \ No newline at end of file diff --git a/pointMaster/js/src/shims-vue.d.ts b/pointMaster/js/src/shims-vue.d.ts new file mode 100644 index 0000000..07ac950 --- /dev/null +++ b/pointMaster/js/src/shims-vue.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable */ +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/pointMaster/js/src/typings.d.ts b/pointMaster/js/src/typings.d.ts new file mode 100644 index 0000000..b4188bb --- /dev/null +++ b/pointMaster/js/src/typings.d.ts @@ -0,0 +1,13 @@ +type Point = { + Id: number; + Points: number; + Turnout: number; + postName: string; + patruljeName: string; + DateCreated: Date; +} + +type Stat = { + Title: string; + Value: string; +} \ No newline at end of file diff --git a/pointMaster/js/tsconfig.json b/pointMaster/js/tsconfig.json new file mode 100644 index 0000000..e5b8cea --- /dev/null +++ b/pointMaster/js/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "strict": true, + "jsx": "preserve", + "importHelpers": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "experimentalDecorators": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strictNullChecks": false, + "sourceMap": true, + "baseUrl": ".", + "types": [ + "webpack-env" + ], + "paths": { + "@/*": [ + "src/*" + ] + }, + "lib": [ + "esnext", + "dom", + "dom.iterable", + "scripthost" + ], + "skipLibCheck": true, + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "tests/**/*.ts", + "tests/**/*.tsx", + ], + "exclude": [ + "node_modules" + ] +} diff --git a/pointMaster/js/vue.config.js b/pointMaster/js/vue.config.js new file mode 100644 index 0000000..c8ed6c0 --- /dev/null +++ b/pointMaster/js/vue.config.js @@ -0,0 +1,19 @@ +module.exports = { + outputDir: "../wwwroot/js/dist", // Output everything into the `dist` folder + assetsDir: "", // Prevent nested `assets` folder + publicPath: "./", // Use relative paths for assets + filenameHashing: false, // Disable hash in filenames + runtimeCompiler: true, // Enable runtime compilation + configureWebpack: { + resolve: { + extensions: ['.vue', '.js', '.json'], + }, + output: { + filename: '[name].js', // Keep the main app filename dynamic + chunkFilename: '[name].js', // Ensure chunk filenames are unique + }, + }, + chainWebpack: config => { + config.optimization.splitChunks(false); // Disable chunk splitting + }, +}; \ No newline at end of file diff --git a/pointMaster/pointMaster.csproj b/pointMaster/pointMaster.csproj index 3555719..3f38408 100644 --- a/pointMaster/pointMaster.csproj +++ b/pointMaster/pointMaster.csproj @@ -18,11 +18,13 @@ + +