Upgrade frontend dependencies and tooling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
leanderandCopilot committed 2026-10-22 04:00:31 +02:00
1 parent 4a36cc140c
commit 18da8335a8
32 files changed
+5718 -16951

No files matched your search

+1
View File
@@ -19,6 +19,7 @@ module.exports = {
rules: { rules: {
"no-console": "off", "no-console": "off",
"prefer-destructuring": "off", "prefer-destructuring": "off",
"import/no-unresolved": ["error", { ignore: ["\\?react$"] }],
"import/no-extraneous-dependencies": [ "import/no-extraneous-dependencies": [
"error", "error",
{ {
+18 -2
View File
@@ -3,6 +3,22 @@ documents: ./src/**/*.graphql
generates: generates:
./src/types.ts: ./src/types.ts:
plugins: plugins:
- typescript - typescript:
- typescript-operations typesPrefix: Schema
scalars:
DateTime:
input: string
output: string
JSONScalar:
input: unknown
output: "Array<{ name: string; count: number }>"
- typescript-operations:
nonOptionalTypename: true
scalars:
DateTime:
input: string
output: string
JSONScalar:
input: unknown
output: "Array<{ name: string; count: number }>"
- typed-document-node - typed-document-node
+5241 -16627
View File
File diff suppressed because it is too large. Load diff
+30 -35
View File
@@ -7,59 +7,54 @@
"check-types": "tsc", "check-types": "tsc",
"dev": "vite", "dev": "vite",
"format:js": "eslint --fix src", "format:js": "eslint --fix src",
"format:styles": "stylelint --fix src", "format:styles": "prettier --write \"src/**/*.css\"",
"generate": "graphql-codegen", "generate": "node scripts/generate-types.mjs",
"lint:js": "eslint src", "lint:js": "eslint src",
"lint:styles": "stylelint src", "lint:styles": "prettier --check \"src/**/*.css\"",
"preview": "vite preview", "preview": "vite preview",
"start": "vite", "start": "vite",
"test": "jest" "test": "node --test"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.5.10", "@apollo/client": "^3.14.1",
"@nivo/bar": "^0.79.1", "@nivo/bar": "^0.99.0",
"@nivo/line": "^0.79.1", "@nivo/line": "^0.99.0",
"@nivo/pie": "^0.79.1", "@nivo/pie": "^0.99.0",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"graphql": "^16.3.0", "graphql": "^16.3.0",
"react": "^17.0.2", "react": "^18.3.1",
"react-dom": "^17.0.2", "react-dom": "^18.3.1",
"react-hook-form": "^7.30.0", "react-hook-form": "^7.30.0",
"react-router-dom": "^6.3.0", "react-router-dom": "^7.18.4",
"use-url-search-params": "^2.5.1" "use-url-search-params": "^2.5.1"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "^2.6.2", "@graphql-codegen/core": "^6.2.2",
"@graphql-codegen/typed-document-node": "^2.2.8", "@graphql-codegen/typescript": "^6.1.2",
"@graphql-codegen/typescript-operations": "^2.3.5", "@graphql-codegen/typed-document-node": "^7.2.0",
"@graphql-codegen/typescript-operations": "^6.1.10",
"@graphql-typed-document-node/core": "^3.1.1", "@graphql-typed-document-node/core": "^3.1.1",
"@types/react": "^17.0.44", "@types/react": "^18.3.28",
"@types/react-dom": "^17.0.15", "@types/react-dom": "^18.3.7",
"@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^8.72.0",
"@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/parser": "^8.72.0",
"@typescript-eslint/parser": "^5.19.0", "eslint": "^8.57.1",
"@vitejs/plugin-react": "^1.3.1", "eslint-config-prettier": "^10.1.8",
"@vitejs/plugin-react-refresh": "^1.3.6",
"eslint": "^8.13.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^5.5.7",
"eslint-plugin-react": "^7.29.4", "eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.4.0", "eslint-plugin-react-hooks": "^4.4.0",
"jest": "^27.5.1", "prettier": "^3.6.2",
"prettier": "^2.6.2", "typescript": "^5.9.3",
"stylelint": "^14.7.0", "vite": "^6.4.5",
"stylelint-config-idiomatic-order": "^8.1.0", "vite-plugin-svgr": "^5.2.0",
"stylelint-config-prettier": "^9.0.3", "yaml": "^2.8.1"
"stylelint-config-recommended": "^7.0.0",
"stylelint-order": "^5.0.0",
"stylelint-prettier": "^2.0.0",
"typescript": "^4.6.3",
"vite": "^2.9.5",
"vite-plugin-svgr": "^1.1.0"
}, },
"engines": { "engines": {
"node": "20" "node": "20"
},
"overrides": {
"@whatwg-node/promise-helpers": "^1.3.2"
} }
} }
+66
View File
@@ -0,0 +1,66 @@
import { readdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { codegen } from "@graphql-codegen/core";
import { plugin as typedDocumentNodePlugin } from "@graphql-codegen/typed-document-node";
import { plugin as typescriptOperationsPlugin } from "@graphql-codegen/typescript-operations";
import { plugin as typescriptPlugin } from "@graphql-codegen/typescript";
import { buildASTSchema, parse } from "graphql";
import { parse as parseYaml } from "yaml";
const frontendRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const configPath = path.join(frontendRoot, "codegen.yml");
const config = parseYaml(await readFile(configPath, "utf8"));
const schemaDocument = parse(await readFile(path.resolve(frontendRoot, config.schema), "utf8"));
const outputEntries = Object.entries(config.generates ?? {});
if (outputEntries.length !== 1) {
throw new Error(`Expected one GraphQL Codegen output in ${configPath}`);
}
const [outputName, outputConfig] = outputEntries[0];
const outputPath = path.resolve(frontendRoot, outputName);
const sourceRoot = path.join(frontendRoot, "src");
const plugins = outputConfig.plugins.map((plugin) =>
typeof plugin === "string" ? { [plugin]: {} } : plugin,
);
async function findGraphqlFiles(directory) {
const entries = await readdir(directory, { withFileTypes: true });
const nestedFiles = await Promise.all(
entries.map((entry) => {
const entryPath = path.join(directory, entry.name);
if (entry.isDirectory()) return findGraphqlFiles(entryPath);
return entry.isFile() && entry.name.endsWith(".graphql") ? [entryPath] : [];
}),
);
return nestedFiles.flat();
}
const documents = await Promise.all(
(await findGraphqlFiles(sourceRoot))
.sort()
.map(async (location) => ({
location,
document: parse(await readFile(location, "utf8")),
})),
);
const pluginMap = {
typescript: { plugin: typescriptPlugin },
"typescript-operations": { plugin: typescriptOperationsPlugin },
"typed-document-node": { plugin: typedDocumentNodePlugin },
};
const generated = await codegen({
filename: outputPath,
schema: schemaDocument,
schemaAst: buildASTSchema(schemaDocument),
documents,
config: {},
plugins,
pluginMap,
});
await writeFile(outputPath, generated);
+1 -1
View File
@@ -1,4 +1,4 @@
.app { .app {
margin: 1rem auto;
max-width: 1220px; max-width: 1220px;
margin: 1rem auto;
} }
+1 -1
View File
@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { ReactComponent as Brick } from "../assets/brick.svg"; import Brick from "../assets/brick.svg?react";
import styles from "./Footer.module.css"; import styles from "./Footer.module.css";
export const Footer = (): JSX.Element => { export const Footer = (): JSX.Element => {
+7 -5
View File
@@ -1,9 +1,11 @@
.errorMessage { .errorMessage {
font-family: Rocket Rinder, monospace;
font-size: 3rem;
font-style: italic;
color: var(--brand);
text-align: center;
width: 100%; width: 100%;
margin-top: 30vh; margin-top: 30vh;
color: var(--brand);
font-family:
Rocket Rinder,
monospace;
font-size: 3rem;
font-style: italic;
text-align: center;
} }
+24 -7
View File
@@ -1,6 +1,6 @@
.header { .header {
margin: 1rem auto;
max-width: 1220px; max-width: 1220px;
margin: 1rem auto;
} }
.nav { .nav {
@@ -17,14 +17,21 @@
display: block; display: block;
height: 1px; height: 1px;
background-color: rgba(243, 33, 61, 0.4); background-color: rgba(243, 33, 61, 0.4);
box-shadow: 0 0 4px rgba(243, 33, 61, 0.4), 0 0 11px rgba(243, 33, 61, 0.4), 0 0 19px rgba(243, 33, 61, 0.4); box-shadow:
0 0 4px rgba(243, 33, 61, 0.4),
0 0 11px rgba(243, 33, 61, 0.4),
0 0 19px rgba(243, 33, 61, 0.4);
content: ""; content: "";
transform: rotate(-2.5deg); transform: rotate(-2.5deg);
} }
.navLink:hover:after { .navLink:hover:after {
background-color: var(--brand); background-color: var(--brand);
box-shadow: 0 0 4px var(--brand), 0 0 11px var(--brand), 0 0 19px var(--brand), 0 0 30px var(--brand); box-shadow:
0 0 4px var(--brand),
0 0 11px var(--brand),
0 0 19px var(--brand),
0 0 30px var(--brand);
} }
.logoNav { .logoNav {
@@ -55,12 +62,17 @@
.brand { .brand {
padding-left: 1rem; padding-left: 1rem;
color: var(--brand); color: var(--brand);
font-family: Rocket Rinder, monospace; font-family:
Rocket Rinder,
monospace;
font-size: 3rem; font-size: 3rem;
font-style: italic; font-style: italic;
letter-spacing: 2px; letter-spacing: 2px;
line-height: 1; line-height: 1;
text-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8); text-shadow:
0 0 4px rgba(243, 33, 61, 0.8),
0 0 11px rgba(243, 33, 61, 0.8),
0 0 19px rgba(243, 33, 61, 0.8);
} }
.subtitle { .subtitle {
@@ -68,7 +80,9 @@
left: -8px; left: -8px;
animation: flicker 1.5s infinite alternate; animation: flicker 1.5s infinite alternate;
color: var(--interlace); color: var(--interlace);
font-family: Rocket Clouds DEMO, serif; font-family:
Rocket Clouds DEMO,
serif;
font-size: 10px; font-size: 10px;
font-weight: bold; font-weight: bold;
line-height: 1; line-height: 1;
@@ -83,7 +97,10 @@
53%, 53%,
57%, 57%,
100% { 100% {
text-shadow: 0 0 4px rgba(121, 152, 238, 0.8), 0 0 11px rgba(121, 152, 238, 0.8), 0 0 19px rgba(121, 152, 238, 0.8); text-shadow:
0 0 4px rgba(121, 152, 238, 0.8),
0 0 11px rgba(121, 152, 238, 0.8),
0 0 19px rgba(121, 152, 238, 0.8);
} }
20%, 20%,
+20 -6
View File
@@ -18,22 +18,31 @@
.sun { .sun {
width: clamp(100px, 65vw, 600px); width: clamp(100px, 65vw, 600px);
height: clamp(100px, 65vw, 600px); height: clamp(100px, 65vw, 600px);
border-radius: 50% 50%;
margin: 0 auto; margin: 0 auto;
background: linear-gradient(var(--sun) 50%, var(--sun-end) 90%); background: linear-gradient(var(--sun) 50%, var(--sun-end) 90%);
border-radius: 50% 50%; box-shadow:
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun), 0 0 30px var(--sun); 0 0 4px var(--sun),
0 0 11px var(--sun),
0 0 19px var(--sun),
0 0 30px var(--sun);
} }
.brand { .brand {
position: relative; position: relative;
left: -0.6rem; left: -0.6rem;
padding-top: 3rem; padding-top: 3rem;
color: var(--brand); color: var(--brand);
font-family: Rocket Rinder, monospace; font-family:
Rocket Rinder,
monospace;
font-size: clamp(20px, 20vw, 200px); font-size: clamp(20px, 20vw, 200px);
font-style: italic; font-style: italic;
letter-spacing: 1rem; letter-spacing: 1rem;
line-height: 1; line-height: 1;
text-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8); text-shadow:
0 0 4px rgba(243, 33, 61, 0.8),
0 0 11px rgba(243, 33, 61, 0.8),
0 0 19px rgba(243, 33, 61, 0.8);
} }
.subtitle { .subtitle {
@@ -42,7 +51,9 @@
left: 0rem; left: 0rem;
animation: flicker 1.5s infinite alternate; animation: flicker 1.5s infinite alternate;
color: var(--interlace); color: var(--interlace);
font-family: Rocket Clouds DEMO, serif; font-family:
Rocket Clouds DEMO,
serif;
font-size: clamp(20px, 5vw, 50px); font-size: clamp(20px, 5vw, 50px);
font-weight: bold; font-weight: bold;
line-height: 1; line-height: 1;
@@ -57,7 +68,10 @@
53%, 53%,
57%, 57%,
100% { 100% {
text-shadow: 0 0 4px rgba(121, 152, 238, 0.8), 0 0 11px rgba(121, 152, 238, 0.8), 0 0 19px rgba(121, 152, 238, 0.8); text-shadow:
0 0 4px rgba(121, 152, 238, 0.8),
0 0 11px rgba(121, 152, 238, 0.8),
0 0 19px rgba(121, 152, 238, 0.8);
} }
20%, 20%,
+8 -2
View File
@@ -16,11 +16,17 @@
.primary { .primary {
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%); background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun); box-shadow:
0 0 4px var(--sun),
0 0 11px var(--sun),
0 0 19px var(--sun);
} }
.danger { .danger {
background: var(--brand); background: var(--brand);
box-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8); box-shadow:
0 0 4px rgba(243, 33, 61, 0.8),
0 0 11px rgba(243, 33, 61, 0.8),
0 0 19px rgba(243, 33, 61, 0.8);
} }
.close { .close {
+6 -7
View File
@@ -1,30 +1,29 @@
.wireframe { .wireframe {
position: absolute; position: absolute;
z-index: -1;
bottom: calc(22% - 512px); bottom: calc(22% - 512px);
left: calc(50% - 512px); left: calc(50% - 512px);
display: grid;
width: 1024px; width: 1024px;
height: 1024px; height: 1024px;
display: grid; animation-duration: 10s;
grid-template-columns: repeat(8, 128px);
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-name: isometric-rotate; animation-name: isometric-rotate;
animation-duration: 10s;
animation-timing-function: linear; animation-timing-function: linear;
grid-template-columns: repeat(8, 128px);
pointer-events: none; pointer-events: none;
z-index: -1;
} }
.cell { .cell {
border-bottom: 2px solid var(--sun);
border-right: 2px solid var(--sun); border-right: 2px solid var(--sun);
border-bottom: 2px solid var(--sun);
} }
.cell:nth-child(8n) { .cell:nth-child(8n) {
border-right: none; border-right: none;
} }
.cell:nth-child(n+57) { .cell:nth-child(n + 57) {
border-bottom: none; border-bottom: none;
} }
@@ -2,9 +2,9 @@
display: grid; display: grid;
height: 800px; height: 800px;
padding-top: 2rem; padding-top: 2rem;
column-gap: 16px;
grid-template-columns: repeat(5, 220px); grid-template-columns: repeat(5, 220px);
overflow-y: auto; overflow-y: auto;
column-gap: 16px;
row-gap: 24px; row-gap: 24px;
} }
+3 -3
View File
@@ -11,7 +11,7 @@ export const useDeposit = () => {
const [amount, setAmount] = useState(""); const [amount, setAmount] = useState("");
const [userQuery, setUserQuery] = useState(""); const [userQuery, setUserQuery] = useState("");
const filteredUsers = (data?.allUsers || []).filter((user) => const filteredUsers = (data?.allUsers || []).filter((user) =>
`${user.firstName.toLowerCase()} ${user.lastName.toLowerCase()}`.includes(userQuery.toLowerCase()) `${user.firstName.toLowerCase()} ${user.lastName.toLowerCase()}`.includes(userQuery.toLowerCase()),
); );
const { showToast } = useToast(); const { showToast } = useToast();
@@ -30,7 +30,7 @@ export const useDeposit = () => {
if (res.data?.createDeposit?.__typename === "CreateDepositSuccess") { if (res.data?.createDeposit?.__typename === "CreateDepositSuccess") {
const { transaction } = res.data?.createDeposit; const { transaction } = res.data?.createDeposit;
showToast( showToast(
`${transaction.user.firstName} har satt inn${transaction.amount}. Ny saldo er ${transaction.user.profile?.balance}.` `${transaction.user.firstName} har satt inn${transaction.amount}. Ny saldo er ${transaction.user.profile?.balance}.`,
); );
reset(); reset();
return; return;
@@ -42,7 +42,7 @@ export const useDeposit = () => {
if (res.data?.createDeposit?.__typename === "AboveMaxSaldo") { if (res.data?.createDeposit?.__typename === "AboveMaxSaldo") {
showToast( showToast(
`${res.data.createDeposit.message}. Ny saldo ville vært ${res.data.createDeposit.above} mer enn maksimalt ${res.data.createDeposit.maxSaldo}.`, `${res.data.createDeposit.message}. Ny saldo ville vært ${res.data.createDeposit.above} mer enn maksimalt ${res.data.createDeposit.maxSaldo}.`,
"danger" "danger",
); );
} }
}; };
+1 -1
View File
@@ -20,8 +20,8 @@
.pagination a { .pagination a {
padding: 0.4rem 0.8rem; padding: 0.4rem 0.8rem;
border: 1px solid var(--border); border: 1px solid var(--border);
margin-top: 1rem;
border-radius: 4px; border-radius: 4px;
margin-top: 1rem;
} }
.pagination a:hover { .pagination a:hover {
color: white; color: white;
@@ -1,51 +1,56 @@
.product { .product {
display: flex;
border: none;
background: rgb(35, 15, 41);
border-radius: 4px;
transition: background-color 0.1s ease-out, border 0.1s ease-out;
position: relative; position: relative;
display: flex;
align-items: flex-start; align-items: flex-start;
padding: 1rem; padding: 1rem;
border: none;
border-radius: 4px;
background: rgb(35, 15, 41);
transition:
background-color 0.1s ease-out,
border 0.1s ease-out;
} }
.product:hover, .product:hover,
.active.product { .active.product {
background-color: var(--background); background-color: var(--background);
box-shadow: 0 0 4px var(--background), 0 0 11px var(--background), 0 0 19px var(--background); box-shadow:
0 0 4px var(--background),
0 0 11px var(--background),
0 0 19px var(--background);
} }
.productText { .productText {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
font-size: 1.2rem;
text-shadow: none;
color: white; color: white;
font-size: 1.2rem;
text-overflow: ellipsis;
text-shadow: none;
white-space: nowrap;
} }
.amount { .amount {
position: absolute; position: absolute;
z-index: 1; z-index: 1;
top: 0; top: 0;
bottom: 0;
right: 0; right: 0;
bottom: 0;
display: none;
padding: 12px 12px 8px 8px; padding: 12px 12px 8px 8px;
border-left: 1px solid transparent; border-left: 1px solid transparent;
background-color: transparent;
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
color: transparent; color: transparent;
font-size: 2rem; font-size: 2rem;
font-weight: bold; font-weight: bold;
line-height: 1; line-height: 1;
background-color: transparent;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
display: none;
} }
.active .amount { .active .amount {
display: block; display: block;
color: var(--background);
text-shadow: none;
border-left-color: rgb(35, 15, 41); border-left-color: rgb(35, 15, 41);
background-color: var(--sun-end); background-color: var(--sun-end);
color: var(--background);
text-shadow: none;
} }
@@ -1,7 +1,7 @@
import clsx from "clsx"; import clsx from "clsx";
import React, { MouseEventHandler } from "react"; import React, { MouseEventHandler } from "react";
import { Product } from "../../types"; import { SchemaProduct as Product } from "../../types";
import styles from "./ProductCard.module.css"; import styles from "./ProductCard.module.css";
type ProductCardProps = { type ProductCardProps = {
@@ -1,7 +1,7 @@
.userList { .userList {
display: grid; display: grid;
grid-template-columns: repeat(5, 220px);
column-gap: 16px; column-gap: 16px;
grid-template-columns: repeat(5, 220px);
row-gap: 24px; row-gap: 24px;
} }
@@ -11,8 +11,8 @@
.productList { .productList {
display: grid; display: grid;
grid-template-columns: repeat(5, 220px);
column-gap: 16px; column-gap: 16px;
grid-template-columns: repeat(5, 220px);
row-gap: 24px; row-gap: 24px;
} }
+1 -1
View File
@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { OrderLineInput, Product } from "../../types"; import { OrderLineInput, SchemaProduct as Product } from "../../types";
import { UserCard } from "../users/UserCard"; import { UserCard } from "../users/UserCard";
import { ProductCard } from "./ProductCard"; import { ProductCard } from "./ProductCard";
import styles from "./Register.module.css"; import styles from "./Register.module.css";
@@ -2,15 +2,15 @@ import { useMutation, useQuery } from "@apollo/client";
import { useState } from "react"; import { useState } from "react";
import { useToast } from "../../components/ToastProvider"; import { useToast } from "../../components/ToastProvider";
import { AllProductsDocument, AllUsersDocument, CreateOrderDocument, OrderLineInput, Product } from "../../types"; import { AllProductsDocument, AllUsersDocument, CreateOrderDocument, OrderLineInput, SchemaProduct } from "../../types";
import { formatCurrency } from "../../utils/currency";
import { activeUserFilter } from "../../utils/users"; import { activeUserFilter } from "../../utils/users";
import {formatCurrency} from "../../utils/currency";
const USER_EXTERNAL = "external"; const USER_EXTERNAL = "external";
const sumTotal = (products: Omit<Product, "userCounts">[], order: OrderLineInput[], isExternal: boolean) => { const sumTotal = (products: Omit<SchemaProduct, "userCounts">[], order: OrderLineInput[], isExternal: boolean) => {
const prices = order.map((orderLine: OrderLineInput) => { const prices = order.map((orderLine: OrderLineInput) => {
const product = products.find((product: Omit<Product, "userCounts">) => product.id === orderLine.productId); const product = products.find((product: Omit<SchemaProduct, "userCounts">) => product.id === orderLine.productId);
if (!product) return 0; if (!product) return 0;
return orderLine.amount * (isExternal ? product.salePriceExt : product.salePriceInt); return orderLine.amount * (isExternal ? product.salePriceExt : product.salePriceInt);
}); });
@@ -60,7 +60,7 @@ export const useRegister = () => {
let res; let res;
try { try {
res = await createOrderMutation(); res = await createOrderMutation();
} catch (e) { } catch {
showToast("Whoops"); showToast("Whoops");
console.log("woops"); console.log("woops");
return; return;
+5 -5
View File
@@ -2,7 +2,7 @@ import { useQuery } from "@apollo/client";
import { Bar, BarDatum } from "@nivo/bar"; import { Bar, BarDatum } from "@nivo/bar";
import React from "react"; import React from "react";
import { OrderStatsDocument, OrderStatsByTime } from "../../types"; import { OrderStatsDocument, SchemaOrderStatsByTime } from "../../types";
import { StatsView } from "./StatsView"; import { StatsView } from "./StatsView";
const days = { 1: "Mandag", 2: "Tirsdag", 3: "Onsdag", 4: "Torsdag", 5: "Fredag", 6: "Lørdag", 7: "Søndag" }; const days = { 1: "Mandag", 2: "Tirsdag", 3: "Onsdag", 4: "Torsdag", 5: "Fredag", 6: "Lørdag", 7: "Søndag" };
@@ -42,7 +42,7 @@ const OrderStats = () => {
<StatsView loading={loading}> <StatsView loading={loading}>
<h3>Ordre per time</h3> <h3>Ordre per time</h3>
<OrderBar <OrderBar
data={(data?.orderStats?.hourly || []).map((stat: OrderStatsByTime) => ({ data={(data?.orderStats?.hourly || []).map((stat: SchemaOrderStatsByTime) => ({
id: `${stat.period.toString().padStart(2, "0")}:00`, id: `${stat.period.toString().padStart(2, "0")}:00`,
label: stat.period, label: stat.period,
value: stat.count, value: stat.count,
@@ -50,7 +50,7 @@ const OrderStats = () => {
/> />
<h3>Ordre per ukedag</h3> <h3>Ordre per ukedag</h3>
<OrderBar <OrderBar
data={(data?.orderStats?.daily || []).map((stat: OrderStatsByTime) => ({ data={(data?.orderStats?.daily || []).map((stat: SchemaOrderStatsByTime) => ({
/* @ts-expect-error Here, stat.period can only be a key in days range. */ /* @ts-expect-error Here, stat.period can only be a key in days range. */
id: days[stat.period], id: days[stat.period],
label: stat.period, label: stat.period,
@@ -59,7 +59,7 @@ const OrderStats = () => {
/> />
<h3>Ordre per måned</h3> <h3>Ordre per måned</h3>
<OrderBar <OrderBar
data={(data?.orderStats?.monthly || []).map((stat: OrderStatsByTime) => ({ data={(data?.orderStats?.monthly || []).map((stat: SchemaOrderStatsByTime) => ({
/* @ts-expect-error Here, stat.period can only be a key in months range. */ /* @ts-expect-error Here, stat.period can only be a key in months range. */
id: months[stat.period], id: months[stat.period],
label: stat.period, label: stat.period,
@@ -68,7 +68,7 @@ const OrderStats = () => {
/> />
<h3>Ordre per år</h3> <h3>Ordre per år</h3>
<OrderBar <OrderBar
data={(data?.orderStats?.yearly || []).map((stat: OrderStatsByTime) => ({ data={(data?.orderStats?.yearly || []).map((stat: SchemaOrderStatsByTime) => ({
id: stat.period, id: stat.period,
label: stat.period, label: stat.period,
value: stat.count, value: stat.count,
+9 -10
View File
@@ -8,14 +8,13 @@ import { StatsView } from "./StatsView";
const Stats = () => { const Stats = () => {
const { data, loading } = useQuery(ProductStatsDocument); const { data, loading } = useQuery(ProductStatsDocument);
const series: any[] = []; const series = (data?.productStats || []).map(({ productName: id, data }) => ({
(data?.productStats || [])?.forEach(({ productName: id, data }) => { id,
const lineData: any[] = []; data: data.map((point, index, points) => ({
data.forEach((point, index) => { x: point.x,
lineData.push({ x: point.x, y: point.y + (lineData?.[index - 1]?.y || 0) }); y: point.y + (points[index - 1]?.y || 0),
}); })),
series.push({ id, data: lineData }); }));
});
return ( return (
<StatsView loading={loading}> <StatsView loading={loading}>
@@ -59,8 +58,8 @@ const Stats = () => {
<strong>{point.data.xFormatted}</strong> <strong>{point.data.xFormatted}</strong>
</div> </div>
<div> <div>
<span style={{ background: point.serieColor, width: 13, height: 13, display: "inline-block" }} />{" "} <span style={{ background: point.seriesColor, width: 13, height: 13, display: "inline-block" }} />{" "}
<strong>{point.data.yFormatted}</strong> {point.serieId} <strong>{String(point.data.yFormatted)}</strong> {point.seriesId}
</div> </div>
</div> </div>
)} )}
+17 -12
View File
@@ -1,14 +1,16 @@
.user { .user {
display: flex; display: flex;
max-height: 82px;
align-items: center; align-items: center;
padding: 0; padding: 0;
border: none; border: none;
border-radius: 4px;
border-left: 4px solid transparent; border-left: 4px solid transparent;
background: rgb(35, 15, 41); background: rgb(35, 15, 41);
border-radius: 4px;
color: var(--text); color: var(--text);
transition: background-color 0.1s ease-out, border 0.1s ease-out; transition:
max-height: 82px; background-color 0.1s ease-out,
border 0.1s ease-out;
} }
.user:hover { .user:hover {
@@ -17,16 +19,19 @@
.user:hover, .user:hover,
.active.user { .active.user {
background-color: var(--background); background-color: var(--background);
box-shadow: 0 0 4px var(--background), 0 0 11px var(--background), 0 0 19px var(--background); box-shadow:
0 0 4px var(--background),
0 0 11px var(--background),
0 0 19px var(--background);
} }
.active.user { .active.user {
border-left: 4px solid var(--sun) border-left: 4px solid var(--sun);
} }
.avatar { .avatar {
margin-left: 1rem;
width: 50px; width: 50px;
height: 50px; height: 50px;
margin-left: 1rem;
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
} }
.avatarWrap { .avatarWrap {
@@ -35,10 +40,10 @@
} }
.noAvatar { .noAvatar {
margin-left: 1rem;
display: block; display: block;
width: 50px; width: 50px;
height: 50px; height: 50px;
margin-left: 1rem;
background-color: black; background-color: black;
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
content: ""; content: "";
@@ -46,16 +51,16 @@
.meta { .meta {
display: flex; display: flex;
padding: 1rem; overflow: hidden;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
white-space: nowrap; padding: 1rem;
text-overflow: ellipsis;
overflow: hidden;
font-size: 1.2rem; font-size: 1.2rem;
text-overflow: ellipsis;
text-shadow: none; text-shadow: none;
white-space: nowrap;
} }
.balance { .balance {
color: var(--sun) color: var(--sun);
} }
+1 -1
View File
@@ -2,8 +2,8 @@ import clsx from "clsx";
import React, { MouseEventHandler } from "react"; import React, { MouseEventHandler } from "react";
import { AllUsersQuery } from "../../types"; import { AllUsersQuery } from "../../types";
import { formatCurrency } from "../../utils/currency";
import styles from "./UserCard.module.css"; import styles from "./UserCard.module.css";
import {formatCurrency} from "../../utils/currency";
type UserCardProps = { type UserCardProps = {
user: AllUsersQuery["allUsers"][0] | null; user: AllUsersQuery["allUsers"][0] | null;
+10 -12
View File
@@ -4,7 +4,7 @@ import React from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { GQLError } from "../../components/GQLError"; import { GQLError } from "../../components/GQLError";
import { User, UserDetailDocument, UserProfile } from "../../types"; import { SchemaUser as User, UserDetailDocument, SchemaUserProfile as UserProfile } from "../../types";
import { formatMonth, formatTime } from "../../utils/time"; import { formatMonth, formatTime } from "../../utils/time";
import styles from "./UserDetail.module.css"; import styles from "./UserDetail.module.css";
@@ -18,15 +18,13 @@ const UserDetail = () => {
const user = data?.user as User; const user = data?.user as User;
const profile = user?.profile as UserProfile; const profile = user?.profile as UserProfile;
// FIXME: types const series = (profile?.productOrderStats || []).map(({ productName: id, data }) => ({
const series: any[] = []; id,
(profile?.productOrderStats || [])?.forEach(({ productName: id, data }) => { data: data.map((point, index, points) => ({
const lineData: any[] = []; x: point.x,
data.forEach((point, index) => { y: point.y + (points[index - 1]?.y || 0),
lineData.push({ x: point.x, y: point.y + (lineData?.[index - 1]?.y || 0) }); })),
}); }));
series.push({ id, data: lineData });
});
return ( return (
<div className={styles.userDetail}> <div className={styles.userDetail}>
<div> <div>
@@ -101,8 +99,8 @@ const UserDetail = () => {
<strong>{point.data.xFormatted}</strong> <strong>{point.data.xFormatted}</strong>
</div> </div>
<div> <div>
<span style={{ background: point.serieColor, width: 13, height: 13, display: "inline-block" }} />{" "} <span style={{ background: point.seriesColor, width: 13, height: 13, display: "inline-block" }} />{" "}
<strong>{point.data.yFormatted}</strong> {point.serieId} <strong>{String(point.data.yFormatted)}</strong> {point.seriesId}
</div> </div>
</div> </div>
)} )}
+28 -10
View File
@@ -33,7 +33,10 @@ body {
background-color: var(--background); background-color: var(--background);
color: var(--text); color: var(--text);
font-family: var(--font-family); font-family: var(--font-family);
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor; text-shadow:
0 0 4px currentColor,
0 0 11px currentColor,
0 0 19px currentColor;
} }
/* Links */ /* Links */
@@ -63,9 +66,12 @@ h5 {
a.btn-primary { a.btn-primary {
padding: 0.4rem 1rem; padding: 0.4rem 1rem;
border: 1px solid transparent; border: 1px solid transparent;
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
border-radius: 4px; border-radius: 4px;
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun); background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
box-shadow:
0 0 4px var(--sun),
0 0 11px var(--sun),
0 0 19px var(--sun);
color: var(--text); color: var(--text);
} }
@@ -73,11 +79,14 @@ a.btn-primary {
button { button {
padding: 0.4rem 1rem; padding: 0.4rem 1rem;
border: 1px solid var(--border); border: 1px solid var(--border);
background: transparent;
border-radius: 4px; border-radius: 4px;
background: transparent;
color: var(--border); color: var(--border);
font-family: var(--font-family); font-family: var(--font-family);
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor; text-shadow:
0 0 4px currentColor,
0 0 11px currentColor,
0 0 19px currentColor;
} }
button:hover { button:hover {
color: var(--text); color: var(--text);
@@ -86,7 +95,10 @@ button:hover {
button.primary { button.primary {
border: 1px solid transparent; border: 1px solid transparent;
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%); background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun); box-shadow:
0 0 4px var(--sun),
0 0 11px var(--sun),
0 0 19px var(--sun);
color: var(--text); color: var(--text);
} }
@@ -94,12 +106,15 @@ button.primary {
input { input {
padding: 0.4rem 0.4rem; padding: 0.4rem 0.4rem;
border: 1px solid var(--border); border: 1px solid var(--border);
background: transparent;
border-radius: 4px; border-radius: 4px;
background: transparent;
color: var(--text); color: var(--text);
font-family: var(--font-family); font-family: var(--font-family);
font-size: 1.5rem; font-size: 1.5rem;
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor; text-shadow:
0 0 4px currentColor,
0 0 11px currentColor,
0 0 19px currentColor;
} }
input:focus { input:focus {
background: rgba(85, 109, 200, 0.1); background: rgba(85, 109, 200, 0.1);
@@ -108,9 +123,12 @@ input:focus {
input.btn-primary { input.btn-primary {
padding: 0.4rem 1rem; padding: 0.4rem 1rem;
border: 1px solid transparent; border: 1px solid transparent;
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
border-radius: 4px; border-radius: 4px;
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun); background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
box-shadow:
0 0 4px var(--sun),
0 0 11px var(--sun),
0 0 19px var(--sun);
color: var(--text); color: var(--text);
} }
::placeholder { ::placeholder {
+2 -2
View File
@@ -1,9 +1,9 @@
import { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
export function usePrevious(value: any) { export function usePrevious<T>(value: T) {
// The ref object is a generic container whose current property is mutable ... // The ref object is a generic container whose current property is mutable ...
// ... and can hold any value, similar to an instance property on a class // ... and can hold any value, similar to an instance property on a class
const ref = useRef(); const ref = useRef<T | undefined>(undefined);
// Store current value in ref // Store current value in ref
useEffect(() => { useEffect(() => {
+1 -1
View File
@@ -9,5 +9,5 @@ ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById("root"),
); );
+181 -167
View File
@@ -1,329 +1,343 @@
/** Internal type. DO NOT USE DIRECTLY. */
type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
/** Internal type. DO NOT USE DIRECTLY. */
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type Maybe<T> = T | null; export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>; export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
/** All built-in and custom scalars, mapped to their actual values */ /** All built-in and custom scalars, mapped to their actual values */
export type Scalars = { export type Scalars = {
ID: string; ID: { input: string; output: string; }
String: string; String: { input: string; output: string; }
Boolean: boolean; Boolean: { input: boolean; output: boolean; }
Int: number; Int: { input: number; output: number; }
Float: number; Float: { input: number; output: number; }
/** Date with time (isoformat) */ /** Date with time (isoformat) */
DateTime: any; DateTime: { input: string; output: string; }
/** The GenericScalar scalar type represents a generic GraphQL scalar value that could be: List or Object. */ /** The GenericScalar scalar type represents a generic GraphQL scalar value that could be: List or Object. */
JSONScalar: any; JSONScalar: { input: unknown; output: Array<{ name: string; count: number }>; }
}; };
export type AboveMaxSaldo = Error & { export type SchemaAboveMaxSaldo = SchemaError & {
__typename?: 'AboveMaxSaldo'; __typename?: 'AboveMaxSaldo';
above: Scalars['Float']; message: Scalars['String']['output'];
error: Scalars['Boolean']; error: Scalars['Boolean']['output'];
maxSaldo: Scalars['Float']; maxSaldo: Scalars['Float']['output'];
message: Scalars['String']; above: Scalars['Float']['output'];
}; };
export type CreateDepositResponse = AboveMaxSaldo | CreateDepositSuccess | FormErrors; export type SchemaCreateDepositResponse = SchemaCreateDepositSuccess | SchemaAboveMaxSaldo | SchemaFormErrors;
export type CreateDepositSuccess = { export type SchemaCreateDepositSuccess = {
__typename?: 'CreateDepositSuccess'; __typename?: 'CreateDepositSuccess';
transaction: Transaction; transaction: SchemaTransaction;
}; };
export type CreateOrderResponse = CreateOrderSuccess | FormErrors | InsufficientFunds; export type SchemaCreateOrderResponse = SchemaCreateOrderSuccess | SchemaInsufficientFunds | SchemaFormErrors;
export type CreateOrderSuccess = { export type SchemaCreateOrderSuccess = {
__typename?: 'CreateOrderSuccess'; __typename?: 'CreateOrderSuccess';
order: Order; order: SchemaOrder;
}; };
export type Error = { export type SchemaError = {
error: Scalars['Boolean']; message: Scalars['String']['output'];
message: Scalars['String']; error: Scalars['Boolean']['output'];
}; };
export type FieldError = Error & { export type SchemaFieldError = SchemaError & {
__typename?: 'FieldError'; __typename?: 'FieldError';
error: Scalars['Boolean']; message: Scalars['String']['output'];
field: Scalars['String']; error: Scalars['Boolean']['output'];
message: Scalars['String']; field: Scalars['String']['output'];
}; };
export type FormErrors = { export type SchemaFormErrors = {
__typename?: 'FormErrors'; __typename?: 'FormErrors';
error: Scalars['Boolean']; fields: Array<SchemaFieldError>;
fields: Array<FieldError>; message: Scalars['String']['output'];
message: Scalars['String']; error: Scalars['Boolean']['output'];
}; };
export type InsufficientFunds = Error & { export type SchemaInsufficientFunds = SchemaError & {
__typename?: 'InsufficientFunds'; __typename?: 'InsufficientFunds';
amountLacking: Scalars['Float']; message: Scalars['String']['output'];
error: Scalars['Boolean']; error: Scalars['Boolean']['output'];
message: Scalars['String']; amountLacking: Scalars['Float']['output'];
}; };
export type Mutation = { export type SchemaMutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
createDeposit: CreateDepositResponse; createOrder: SchemaCreateOrderResponse;
createOrder: CreateOrderResponse; createDeposit: SchemaCreateDepositResponse;
createUser: User; createUser: SchemaUser;
}; };
export type MutationCreateDepositArgs = { export type SchemaMutationCreateOrderArgs = {
amount: Scalars['Int']; customerId: Scalars['ID']['input'];
userId: Scalars['ID']; orderLines: Array<SchemaOrderLineInput>;
isExternal?: Scalars['Boolean']['input'];
}; };
export type MutationCreateOrderArgs = { export type SchemaMutationCreateDepositArgs = {
customerId: Scalars['ID']; userId: Scalars['ID']['input'];
isExternal?: Scalars['Boolean']; amount: Scalars['Int']['input'];
orderLines: Array<OrderLineInput>;
}; };
export type MutationCreateUserArgs = { export type SchemaMutationCreateUserArgs = {
input: UserInput; input: SchemaUserInput;
}; };
export type OffsetPaginationInput = { export type SchemaOffsetPaginationInput = {
limit?: Scalars['Int']; offset?: Scalars['Int']['input'];
offset?: Scalars['Int']; limit?: Scalars['Int']['input'];
}; };
export type Order = { export type SchemaOrder = {
__typename?: 'Order'; __typename?: 'Order';
created: Scalars['DateTime']; id: Scalars['ID']['output'];
customer?: Maybe<User>; customer?: Maybe<SchemaUser>;
id: Scalars['ID']; orderSum: Scalars['Float']['output'];
isExternal: Scalars['Boolean']; created: Scalars['DateTime']['output'];
orderSum: Scalars['Float']; orderlines?: Maybe<Array<SchemaOrderLine>>;
orderlines?: Maybe<Array<OrderLine>>; isExternal: Scalars['Boolean']['output'];
}; };
export type OrderLine = { export type SchemaOrderLine = {
__typename?: 'OrderLine'; __typename?: 'OrderLine';
amount: Scalars['Int']; id: Scalars['ID']['output'];
id: Scalars['ID']; order: SchemaOrder;
order: Order; product: SchemaProduct;
product: Product; amount: Scalars['Int']['output'];
unitPrice: Scalars['Float']; unitPrice: Scalars['Float']['output'];
}; };
export type OrderLineInput = { export type SchemaOrderLineInput = {
amount: Scalars['Int']; productId: Scalars['ID']['input'];
productId: Scalars['ID']; amount: Scalars['Int']['input'];
}; };
export type OrderOrdering = { export type SchemaOrderOrdering = {
created?: InputMaybe<Ordering>; created?: InputMaybe<SchemaOrdering>;
}; };
export type OrderStats = { export type SchemaOrderStats = {
__typename?: 'OrderStats'; __typename?: 'OrderStats';
daily: Array<OrderStatsByTime>; yearly: Array<SchemaOrderStatsByTime>;
hourly: Array<OrderStatsByTime>; monthly: Array<SchemaOrderStatsByTime>;
monthly: Array<OrderStatsByTime>; daily: Array<SchemaOrderStatsByTime>;
yearly: Array<OrderStatsByTime>; hourly: Array<SchemaOrderStatsByTime>;
}; };
export type OrderStatsByTime = { export type SchemaOrderStatsByTime = {
__typename?: 'OrderStatsByTime'; __typename?: 'OrderStatsByTime';
count: Scalars['String']; period: Scalars['Int']['output'];
period: Scalars['Int']; count: Scalars['String']['output'];
}; };
export enum Ordering { export enum SchemaOrdering {
Asc = 'ASC', Asc = 'ASC',
Desc = 'DESC' Desc = 'DESC'
} }
export type Point = { export type SchemaPoint = {
__typename?: 'Point'; __typename?: 'Point';
x: Scalars['String']; x: Scalars['String']['output'];
y: Scalars['Int']; y: Scalars['Int']['output'];
}; };
export type Product = { export type SchemaProduct = {
__typename?: 'Product'; __typename?: 'Product';
id: Scalars['ID']; id: Scalars['ID']['output'];
imageUrl: Scalars['String']; name: Scalars['String']['output'];
name: Scalars['String']; imageUrl: Scalars['String']['output'];
salePriceExt: Scalars['Float']; salePriceInt: Scalars['Float']['output'];
salePriceInt: Scalars['Float']; salePriceExt: Scalars['Float']['output'];
userCounts: Scalars['JSONScalar']; userCounts: Scalars['JSONScalar']['output'];
}; };
export type ProductFilter = { export type SchemaProductFilter = {
active?: InputMaybe<Scalars['Boolean']>; id?: InputMaybe<Scalars['ID']['input']>;
id?: InputMaybe<Scalars['ID']>; active?: InputMaybe<Scalars['Boolean']['input']>;
}; };
export type ProductOrderStat = { export type SchemaProductOrderStat = {
__typename?: 'ProductOrderStat'; __typename?: 'ProductOrderStat';
data: Array<Point>; productName: Scalars['String']['output'];
productName: Scalars['String']; data: Array<SchemaPoint>;
}; };
export type Query = { export type SchemaQuery = {
__typename?: 'Query'; __typename?: 'Query';
allOrders: Array<Order>; transactionList: Array<SchemaTransaction>;
allProducts: Array<Product>; allProducts: Array<SchemaProduct>;
allUsers: Array<User>; allOrders: Array<SchemaOrder>;
orderList: Array<Order>; orderList: Array<SchemaOrder>;
orderStats: OrderStats; orderStats: SchemaOrderStats;
productStats: Array<ProductOrderStat>; productStats: Array<SchemaProductOrderStat>;
transactionList: Array<Transaction>; allUsers: Array<SchemaUser>;
user: User; user: SchemaUser;
}; };
export type QueryAllProductsArgs = { export type SchemaQueryTransactionListArgs = {
filters?: InputMaybe<ProductFilter>; pagination?: InputMaybe<SchemaOffsetPaginationInput>;
order?: InputMaybe<SchemaTransactionOrdering>;
}; };
export type QueryOrderListArgs = { export type SchemaQueryAllProductsArgs = {
order?: InputMaybe<OrderOrdering>; filters?: InputMaybe<SchemaProductFilter>;
pagination?: InputMaybe<OffsetPaginationInput>;
}; };
export type QueryTransactionListArgs = { export type SchemaQueryOrderListArgs = {
order?: InputMaybe<TransactionOrdering>; pagination?: InputMaybe<SchemaOffsetPaginationInput>;
pagination?: InputMaybe<OffsetPaginationInput>; order?: InputMaybe<SchemaOrderOrdering>;
}; };
export type QueryUserArgs = { export type SchemaQueryUserArgs = {
userId: Scalars['ID']; userId: Scalars['ID']['input'];
}; };
export type TopMonth = { export type SchemaTopMonth = {
__typename?: 'TopMonth'; __typename?: 'TopMonth';
count: Scalars['Int']; period: Scalars['String']['output'];
period: Scalars['String']; count: Scalars['Int']['output'];
sum: Scalars['Int']; sum: Scalars['Int']['output'];
}; };
export type Transaction = { export type SchemaTransaction = {
__typename?: 'Transaction'; __typename?: 'Transaction';
amount: Scalars['Float']; id: Scalars['ID']['output'];
created: Scalars['DateTime']; user: SchemaUser;
id: Scalars['ID']; amount: Scalars['Float']['output'];
user: User; created: Scalars['DateTime']['output'];
}; };
export type TransactionOrdering = { export type SchemaTransactionOrdering = {
created?: InputMaybe<Ordering>; created?: InputMaybe<SchemaOrdering>;
}; };
export type User = { export type SchemaUser = {
__typename?: 'User'; __typename?: 'User';
firstName: Scalars['String']; id: Scalars['ID']['output'];
id: Scalars['ID']; firstName: Scalars['String']['output'];
lastName: Scalars['String']; lastName: Scalars['String']['output'];
profile?: Maybe<UserProfile>; profile?: Maybe<SchemaUserProfile>;
}; };
export type UserInput = { export type SchemaUserInput = {
email?: InputMaybe<Scalars['String']>; email?: InputMaybe<Scalars['String']['input']>;
firstName?: InputMaybe<Scalars['String']>; firstName?: InputMaybe<Scalars['String']['input']>;
lastName?: InputMaybe<Scalars['String']>; lastName?: InputMaybe<Scalars['String']['input']>;
}; };
export type UserProfile = { export type SchemaUserProfile = {
__typename?: 'UserProfile'; __typename?: 'UserProfile';
balance: Scalars['Float']; id: Scalars['ID']['output'];
id: Scalars['ID']; balance: Scalars['Float']['output'];
image: Scalars['String']; image: Scalars['String']['output'];
lastPurchaseDate: Scalars['DateTime']; lastPurchaseDate: Scalars['DateTime']['output'];
orderSumTotal: Scalars['Float']; orderSumTotal: Scalars['Float']['output'];
productOrderStats: Array<ProductOrderStat>; productOrderStats: Array<SchemaProductOrderStat>;
topMonths: Array<TopMonth>; topMonths: Array<SchemaTopMonth>;
};
export type OrderLineInput = {
productId: string | number;
amount: number;
}; };
export type CreateUserMutationVariables = Exact<{ export type CreateUserMutationVariables = Exact<{
firstName: Scalars['String']; firstName: string;
lastName: Scalars['String']; lastName: string;
email: Scalars['String']; email: string;
}>; }>;
export type CreateUserMutation = { __typename?: 'Mutation', createUser: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string } | null } }; export type CreateUserMutation = { __typename: 'Mutation', createUser: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string } | null } };
export type CreateDepositMutationVariables = Exact<{ export type CreateDepositMutationVariables = Exact<{
userId: Scalars['ID']; userId: string | number;
amount: Scalars['Int']; amount: number;
}>; }>;
export type CreateDepositMutation = { __typename?: 'Mutation', createDeposit: { __typename?: 'AboveMaxSaldo', error: boolean, message: string, maxSaldo: number, above: number } | { __typename?: 'CreateDepositSuccess', transaction: { __typename?: 'Transaction', id: string, amount: number, user: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number } | null } } } | { __typename?: 'FormErrors', error: boolean, message: string, fields: Array<{ __typename?: 'FieldError', field: string, message: string }> } }; export type CreateDepositMutation = { __typename: 'Mutation', createDeposit:
| { __typename: 'CreateDepositSuccess', transaction: { __typename: 'Transaction', id: string, amount: number, user: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: number } | null } } }
| { __typename: 'AboveMaxSaldo', error: boolean, message: string, maxSaldo: number, above: number }
| { __typename: 'FormErrors', error: boolean, message: string, fields: Array<{ __typename: 'FieldError', field: string, message: string }> }
};
export type OrderListQueryVariables = Exact<{ export type OrderListQueryVariables = Exact<{
limit?: InputMaybe<Scalars['Int']>; limit?: number | null | undefined;
offset?: InputMaybe<Scalars['Int']>; offset?: number | null | undefined;
}>; }>;
export type OrderListQuery = { __typename?: 'Query', orderList: Array<{ __typename?: 'Order', id: string, isExternal: boolean, created: any, orderSum: number, customer?: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number } | null } | null, orderlines?: Array<{ __typename?: 'OrderLine', id: string, amount: number, unitPrice: number, product: { __typename?: 'Product', id: string, name: string, salePriceExt: number, salePriceInt: number } }> | null }> }; export type OrderListQuery = { __typename: 'Query', orderList: Array<{ __typename: 'Order', id: string, isExternal: boolean, created: string, orderSum: number, customer: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: number } | null } | null, orderlines: Array<{ __typename: 'OrderLine', id: string, amount: number, unitPrice: number, product: { __typename: 'Product', id: string, name: string, salePriceExt: number, salePriceInt: number } }> | null }> };
export type TransactionListQueryVariables = Exact<{ export type TransactionListQueryVariables = Exact<{
limit?: InputMaybe<Scalars['Int']>; limit?: number | null | undefined;
offset?: InputMaybe<Scalars['Int']>; offset?: number | null | undefined;
}>; }>;
export type TransactionListQuery = { __typename?: 'Query', transactionList: Array<{ __typename?: 'Transaction', id: string, created: any, amount: number, user: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number } | null } }> }; export type TransactionListQuery = { __typename: 'Query', transactionList: Array<{ __typename: 'Transaction', id: string, created: string, amount: number, user: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: number } | null } }> };
export type AllProductsQueryVariables = Exact<{ export type AllProductsQueryVariables = Exact<{
active: Scalars['Boolean']; active: boolean;
}>; }>;
export type AllProductsQuery = { __typename?: 'Query', allProducts: Array<{ __typename?: 'Product', id: string, name: string, imageUrl: string, salePriceExt: number, salePriceInt: number }> }; export type AllProductsQuery = { __typename: 'Query', allProducts: Array<{ __typename: 'Product', id: string, name: string, imageUrl: string, salePriceExt: number, salePriceInt: number }> };
export type AllUsersQueryVariables = Exact<{ [key: string]: never; }>; export type AllUsersQueryVariables = Exact<{ [key: string]: never; }>;
export type AllUsersQuery = { __typename?: 'Query', allUsers: Array<{ __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number, image: string, lastPurchaseDate: any } | null }> }; export type AllUsersQuery = { __typename: 'Query', allUsers: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: number, image: string, lastPurchaseDate: string } | null }> };
export type CreateOrderMutationVariables = Exact<{ export type CreateOrderMutationVariables = Exact<{
customerId: Scalars['ID']; customerId: string | number;
orderLines: Array<OrderLineInput> | OrderLineInput; orderLines: Array<OrderLineInput> | OrderLineInput;
isExternal: Scalars['Boolean']; isExternal: boolean;
}>; }>;
export type CreateOrderMutation = { __typename?: 'Mutation', createOrder: { __typename?: 'CreateOrderSuccess', order: { __typename?: 'Order', id: string, isExternal: boolean, orderSum: number, customer?: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number } | null } | null, orderlines?: Array<{ __typename?: 'OrderLine', id: string, amount: number, unitPrice: number, product: { __typename?: 'Product', id: string, name: string, salePriceExt: number, salePriceInt: number } }> | null } } | { __typename?: 'FormErrors', error: boolean, message: string, fields: Array<{ __typename?: 'FieldError', field: string, message: string }> } | { __typename?: 'InsufficientFunds', error: boolean, message: string, amountLacking: number } }; export type CreateOrderMutation = { __typename: 'Mutation', createOrder:
| { __typename: 'CreateOrderSuccess', order: { __typename: 'Order', id: string, isExternal: boolean, orderSum: number, customer: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: number } | null } | null, orderlines: Array<{ __typename: 'OrderLine', id: string, amount: number, unitPrice: number, product: { __typename: 'Product', id: string, name: string, salePriceExt: number, salePriceInt: number } }> | null } }
| { __typename: 'InsufficientFunds', error: boolean, message: string, amountLacking: number }
| { __typename: 'FormErrors', error: boolean, message: string, fields: Array<{ __typename: 'FieldError', field: string, message: string }> }
};
export type AllProductStatsQueryVariables = Exact<{ export type AllProductStatsQueryVariables = Exact<{
active?: InputMaybe<Scalars['Boolean']>; active?: boolean | null | undefined;
}>; }>;
export type AllProductStatsQuery = { __typename?: 'Query', allProducts: Array<{ __typename?: 'Product', id: string, name: string, userCounts: any }> }; export type AllProductStatsQuery = { __typename: 'Query', allProducts: Array<{ __typename: 'Product', id: string, name: string, userCounts: Array<{ name: string; count: number }> }> };
export type OrderStatsQueryVariables = Exact<{ [key: string]: never; }>; export type OrderStatsQueryVariables = Exact<{ [key: string]: never; }>;
export type OrderStatsQuery = { __typename?: 'Query', orderStats: { __typename?: 'OrderStats', yearly: Array<{ __typename?: 'OrderStatsByTime', period: number, count: string }>, monthly: Array<{ __typename?: 'OrderStatsByTime', period: number, count: string }>, daily: Array<{ __typename?: 'OrderStatsByTime', period: number, count: string }>, hourly: Array<{ __typename?: 'OrderStatsByTime', period: number, count: string }> } }; export type OrderStatsQuery = { __typename: 'Query', orderStats: { __typename: 'OrderStats', yearly: Array<{ __typename: 'OrderStatsByTime', period: number, count: string }>, monthly: Array<{ __typename: 'OrderStatsByTime', period: number, count: string }>, daily: Array<{ __typename: 'OrderStatsByTime', period: number, count: string }>, hourly: Array<{ __typename: 'OrderStatsByTime', period: number, count: string }> } };
export type ProductStatsQueryVariables = Exact<{ [key: string]: never; }>; export type ProductStatsQueryVariables = Exact<{ [key: string]: never; }>;
export type ProductStatsQuery = { __typename?: 'Query', productStats: Array<{ __typename?: 'ProductOrderStat', productName: string, data: Array<{ __typename?: 'Point', x: string, y: number }> }> }; export type ProductStatsQuery = { __typename: 'Query', productStats: Array<{ __typename: 'ProductOrderStat', productName: string, data: Array<{ __typename: 'Point', x: string, y: number }> }> };
export type UserDetailQueryVariables = Exact<{ export type UserDetailQueryVariables = Exact<{
userId: Scalars['ID']; userId: string | number;
}>; }>;
export type UserDetailQuery = { __typename?: 'Query', user: { __typename?: 'User', id: string, firstName: string, lastName: string, profile?: { __typename?: 'UserProfile', id: string, balance: number, image: string, lastPurchaseDate: any, orderSumTotal: number, topMonths: Array<{ __typename?: 'TopMonth', period: string, sum: number, count: number }>, productOrderStats: Array<{ __typename?: 'ProductOrderStat', productName: string, data: Array<{ __typename?: 'Point', x: string, y: number }> }> } | null } }; export type UserDetailQuery = { __typename: 'Query', user: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: number, image: string, lastPurchaseDate: string, orderSumTotal: number, topMonths: Array<{ __typename: 'TopMonth', period: string, sum: number, count: number }>, productOrderStats: Array<{ __typename: 'ProductOrderStat', productName: string, data: Array<{ __typename: 'Point', x: string, y: number }> }> } | null } };
export const CreateUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateUser"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"firstName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createUser"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"firstName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"firstName"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"lastName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastName"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode<CreateUserMutation, CreateUserMutationVariables>; export const CreateUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateUser"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"firstName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createUser"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"firstName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"firstName"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"lastName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastName"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"profile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode<CreateUserMutation, CreateUserMutationVariables>;
+2 -5
View File
@@ -1,8 +1,5 @@
/* svgs */ /// <reference types="vite-plugin-svgr/client" />
declare module "*.svg" {
import { ReactElement, SVGProps } from "react";
export const ReactComponent: (props: SVGProps<SVGElement>) => ReactElement;
}
/* CSS modules */ /* CSS modules */
declare module "*.module.css"; declare module "*.module.css";
+1 -1
View File
@@ -2,6 +2,6 @@ export function formatCurrency(value: number) {
if (isNaN(value)) { if (isNaN(value)) {
return ""; return "";
} else { } else {
return value.toFixed(2).replace(/\.00$/, ''); return value.toFixed(2).replace(/\.00$/, "");
} }
} }
+2 -1
View File
@@ -8,6 +8,7 @@ type FilterableUser = AllUsersQuery["allUsers"][0];
export const activeUserFilter = (user: FilterableUser) => { export const activeUserFilter = (user: FilterableUser) => {
const balance = user.profile?.balance || 0; const balance = user.profile?.balance || 0;
const active = parseISO(user.profile?.lastPurchaseDate) >= subDays(new Date(), activeLimitDays); const active =
!!user.profile?.lastPurchaseDate && parseISO(user.profile.lastPurchaseDate) >= subDays(new Date(), activeLimitDays);
return balance >= 15 && active; return balance >= 15 && active;
}; };