Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3178d4bff |
No files matched your search
@@ -15,4 +15,3 @@ static/assets
|
|||||||
static/index.html
|
static/index.html
|
||||||
.python-version
|
.python-version
|
||||||
.env
|
.env
|
||||||
.coverage
|
|
||||||
+8
-5
@@ -2,27 +2,30 @@ variables:
|
|||||||
PIP_CACHE_DIR: "${CI_PROJECT_DIR}/.cache/pip"
|
PIP_CACHE_DIR: "${CI_PROJECT_DIR}/.cache/pip"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
image: nikolaik/python-nodejs:python3.11-nodejs20
|
image: nikolaik/python-nodejs:python3.8-nodejs16
|
||||||
cache:
|
cache:
|
||||||
paths:
|
paths:
|
||||||
- .cache/pip
|
- .cache/pip
|
||||||
- .venv
|
- .venv
|
||||||
|
before_script:
|
||||||
|
- poetry config virtualenvs.in-project true
|
||||||
|
- poetry install
|
||||||
|
|
||||||
django-checks:
|
django-checks:
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- uv run manage.py migrate
|
- poetry run python manage.py migrate
|
||||||
- uv run manage.py check
|
- poetry run python manage.py check
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- uv run bin/test
|
- poetry run bin/test
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- uv run bin/pre-commit
|
- poetry run bin/lint
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
|
|||||||
+16
-10
@@ -1,12 +1,18 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: local
|
||||||
rev: 'v0.11.7'
|
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: black
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
name: black
|
||||||
- id: ruff-format
|
entry: black
|
||||||
- repo: https://github.com/adamchainz/django-upgrade
|
args: [--check]
|
||||||
rev: "1.24.0"
|
language: system
|
||||||
hooks:
|
require_serial: true
|
||||||
- id: django-upgrade
|
types: [python]
|
||||||
args: [--target-version, "5.2"]
|
- id: isort
|
||||||
|
name: isort
|
||||||
|
entry: isort
|
||||||
|
args: [--check]
|
||||||
|
language: system
|
||||||
|
types: [python]
|
||||||
@@ -1,47 +1,36 @@
|
|||||||
# Getting started
|
# Getting started
|
||||||
|
|
||||||
Before starting, make sure you have Python 3.11 or newer and Node.js 20 available.
|
Before starting, make sure you have a working python and nodejs setup with pyenv and nvm.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run manage.py migrate
|
apt-get install libmysqlclient-dev
|
||||||
|
poetry install
|
||||||
|
poetry shell
|
||||||
|
pre-commit install
|
||||||
|
python manage.py migrate
|
||||||
|
bin/run
|
||||||
cp .env-default .env
|
cp .env-default .env
|
||||||
uv run manage.py seed_demo_data
|
|
||||||
|
|
||||||
# Optional: create an admin login
|
# in another terminal
|
||||||
uv run manage.py createsuperuser
|
cd frontend
|
||||||
|
npm install
|
||||||
# Install frontend packages once
|
npm start
|
||||||
cd frontend && npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Start the backend and frontend in separate terminals from the project root:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uv run bin/run
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uv run bin/run-frontend
|
|
||||||
```
|
|
||||||
|
|
||||||
Vite prints the frontend URL when it starts (normally <http://localhost:5173/>; if that port is busy, it selects another). Django runs at <http://localhost:3000/>; open the admin directly at <http://localhost:3000/admin/> or through the frontend dev proxy at the printed Vite URL followed by `/admin/`. The GraphQL API is at <http://localhost:3000/graphql/>; the frontend uses Vite's proxy for development API requests.
|
|
||||||
|
|
||||||
To test the frontend against a different backend, set `VITE_DEV_API_PROXY` when starting Vite, for example `VITE_DEV_API_PROXY=https://bbs.neuf.no uv run bin/run-frontend`. This affects the development proxy only; use caution with a live backend, since app changes and submissions may modify production data.
|
|
||||||
|
|
||||||
To create the admin during data seeding instead, set `BBS_SEED_ADMIN_PASSWORD` for that command; the command only creates a local `admin` superuser if one does not already exist.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mise run test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- We use JS code generation to work with queries/mutations in our graphql API: <https://github.com/dotansimha/graphql-typed-document-node>
|
- We use JS code generation to work with queries/mutations in our graphql API: https://github.com/dotansimha/graphql-typed-document-node
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
- improve product listing (light purple background)
|
||||||
|
- Stats: products over time
|
||||||
|
- inventory views
|
||||||
|
|
||||||
|
|
||||||
## TODO-old
|
## TODO-old
|
||||||
|
|
||||||
- Larger pictures
|
- Larger pictures
|
||||||
- Compact GUI for max 768 vertical pixels
|
- Compact GUI for max 768 vertical pixels
|
||||||
- Search right of in title
|
- Search right of in title
|
||||||
|
|
||||||
Regular → Executable
File mode changed.
+2
-9
@@ -1,20 +1,13 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from bbs.models import (
|
from bbs.models import *
|
||||||
InventoryTransaction,
|
|
||||||
Order,
|
|
||||||
OrderLine,
|
|
||||||
Product,
|
|
||||||
Transaction,
|
|
||||||
UserProfile,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Product)
|
|
||||||
class ProductAdmin(admin.ModelAdmin):
|
class ProductAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Product, ProductAdmin)
|
||||||
admin.site.register(InventoryTransaction)
|
admin.site.register(InventoryTransaction)
|
||||||
admin.site.register(Order)
|
admin.site.register(Order)
|
||||||
admin.site.register(OrderLine)
|
admin.site.register(OrderLine)
|
||||||
|
|||||||
+3
-1
@@ -1,3 +1,5 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
|
|
||||||
|
|
||||||
@@ -17,6 +19,6 @@ class FieldError(Error):
|
|||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class FormErrors:
|
class FormErrors:
|
||||||
fields: list[FieldError]
|
fields: List[FieldError]
|
||||||
message: str = ""
|
message: str = ""
|
||||||
error: bool = True
|
error: bool = True
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
from decimal import Decimal
|
|
||||||
from typing import Annotated
|
|
||||||
|
|
||||||
import strawberry
|
|
||||||
from django.db import transaction
|
|
||||||
from strawberry import ID, Info
|
|
||||||
|
|
||||||
from bbs import models
|
|
||||||
from bbs.api.errors import FieldError, FormErrors
|
|
||||||
from bbs.api.types.inventory import InventoryTransaction
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry.input
|
|
||||||
class InventoryTransactionInput:
|
|
||||||
product_id: ID
|
|
||||||
amount: int
|
|
||||||
unit_price: Decimal
|
|
||||||
comment: str = ""
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
|
||||||
class CreateInventoryTransactionSuccess:
|
|
||||||
inventory_transaction: InventoryTransaction
|
|
||||||
|
|
||||||
|
|
||||||
CreateInventoryTransactionResponse = Annotated[
|
|
||||||
CreateInventoryTransactionSuccess | FormErrors,
|
|
||||||
strawberry.union("CreateInventoryTransactionResponse"),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry.mutation
|
|
||||||
@transaction.atomic
|
|
||||||
def create_inventory_transaction(
|
|
||||||
info: Info,
|
|
||||||
data: InventoryTransactionInput,
|
|
||||||
) -> CreateInventoryTransactionResponse:
|
|
||||||
user = info.context.request.user
|
|
||||||
if not user.is_authenticated:
|
|
||||||
message = "Logg inn for å registrere varetransaksjoner."
|
|
||||||
return FormErrors(message=message, fields=[FieldError(field="user", message=message)])
|
|
||||||
|
|
||||||
try:
|
|
||||||
product = models.Product.objects.get(pk=data.product_id, active=True)
|
|
||||||
except (models.Product.DoesNotExist, ValueError):
|
|
||||||
message = "Velg et aktivt produkt."
|
|
||||||
return FormErrors(message=message, fields=[FieldError(field="product_id", message=message)])
|
|
||||||
|
|
||||||
product.inventory_amount += data.amount
|
|
||||||
product.save()
|
|
||||||
inventory_transaction = models.InventoryTransaction.objects.create(
|
|
||||||
user=user,
|
|
||||||
product=product,
|
|
||||||
amount=data.amount,
|
|
||||||
unit_price=data.unit_price,
|
|
||||||
comment=data.comment,
|
|
||||||
)
|
|
||||||
return CreateInventoryTransactionSuccess(inventory_transaction=inventory_transaction)
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
|
||||||
class InventoryMutations:
|
|
||||||
create_inventory_transaction = create_inventory_transaction
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Annotated, Union
|
from typing import List
|
||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -27,18 +27,16 @@ class CreateOrderSuccess:
|
|||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class InsufficientFunds(Error): # noqa: N818
|
class InsufficientFunds(Error):
|
||||||
amount_lacking: float
|
amount_lacking: float
|
||||||
|
|
||||||
|
|
||||||
CreateOrderResponse = Annotated[
|
CreateOrderResponse = strawberry.union("CreateOrderResponse", [CreateOrderSuccess, InsufficientFunds, FormErrors])
|
||||||
Union[CreateOrderSuccess, InsufficientFunds, FormErrors], strawberry.union("CreateOrderResponse") # noqa: UP007
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry.mutation
|
@strawberry.mutation
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create_order(customer_id: ID, order_lines: list[OrderLineInput], is_external: bool = False) -> CreateOrderResponse: # noqa: FBT001
|
def create_order(customer_id: ID, order_lines: List[OrderLineInput], is_external: bool = False) -> CreateOrderResponse:
|
||||||
# clean and validate
|
# clean and validate
|
||||||
try:
|
try:
|
||||||
user = UserModel.objects.get(pk=customer_id)
|
user = UserModel.objects.get(pk=customer_id)
|
||||||
@@ -70,8 +68,7 @@ def create_order(customer_id: ID, order_lines: list[OrderLineInput], is_external
|
|||||||
|
|
||||||
if not is_external and user.profile.balance <= order_sum:
|
if not is_external and user.profile.balance <= order_sum:
|
||||||
return InsufficientFunds(
|
return InsufficientFunds(
|
||||||
message="Du har ikke nok penger på bok.",
|
message="Du har ikke nok penger på bok.", amount_lacking=abs(order_sum - user.profile.balance)
|
||||||
amount_lacking=abs(order_sum - user.profile.balance),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# create
|
# create
|
||||||
@@ -94,14 +91,12 @@ class CreateDepositSuccess:
|
|||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class AboveMaxSaldo(Error): # noqa: N818
|
class AboveMaxSaldo(Error):
|
||||||
max_saldo: float
|
max_saldo: float
|
||||||
above: float
|
above: float
|
||||||
|
|
||||||
|
|
||||||
CreateDepositResponse = Annotated[
|
CreateDepositResponse = strawberry.union("CreateDepositResponse", [CreateDepositSuccess, AboveMaxSaldo, FormErrors])
|
||||||
Union[CreateDepositSuccess, AboveMaxSaldo, FormErrors], strawberry.union("CreateDepositResponse") # noqa: UP007
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry.mutation
|
@strawberry.mutation
|
||||||
@@ -123,7 +118,7 @@ def create_deposit(user_id: ID, amount: int) -> CreateDepositResponse:
|
|||||||
fields=[FieldError(field="amount", message=message)],
|
fields=[FieldError(field="amount", message=message)],
|
||||||
)
|
)
|
||||||
# 1337 is special <3
|
# 1337 is special <3
|
||||||
if amount + user.profile.balance > settings.BBS_SALDO_MAX and amount + user.profile.balance != 1337: # noqa: PLR2004
|
if amount + user.profile.balance > settings.BBS_SALDO_MAX and amount + user.profile.balance != 1337:
|
||||||
return AboveMaxSaldo(
|
return AboveMaxSaldo(
|
||||||
message="Du prøver å sette inn for mye penger",
|
message="Du prøver å sette inn for mye penger",
|
||||||
max_saldo=settings.BBS_SALDO_MAX,
|
max_saldo=settings.BBS_SALDO_MAX,
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
from django.contrib.auth.models import User as DjangoUser
|
from django.contrib.auth import get_user_model
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from strawberry import auto
|
from strawberry_django import auto
|
||||||
|
|
||||||
from bbs import models
|
from bbs import models
|
||||||
from bbs.api.types.users import User
|
from bbs.api.types.users import User
|
||||||
|
|
||||||
|
DjangoUser = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.input(DjangoUser)
|
@strawberry_django.input(DjangoUser)
|
||||||
class UserInput:
|
class UserInput:
|
||||||
@@ -17,12 +19,9 @@ class UserInput:
|
|||||||
|
|
||||||
@strawberry.mutation
|
@strawberry.mutation
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def register_mutation(input: UserInput) -> User: # noqa: A002
|
def register_mutation(input: UserInput) -> User:
|
||||||
user = DjangoUser.objects.create_user(
|
user = DjangoUser.objects.create_user(
|
||||||
first_name=input.first_name,
|
first_name=input.first_name, last_name=input.last_name, email=input.email, username=input.email
|
||||||
last_name=input.last_name,
|
|
||||||
email=input.email,
|
|
||||||
username=input.email,
|
|
||||||
)
|
)
|
||||||
models.UserProfile.objects.create(user=user)
|
models.UserProfile.objects.create(user=user)
|
||||||
return user
|
return user
|
||||||
|
|||||||
@@ -1,113 +1,11 @@
|
|||||||
from datetime import datetime
|
from typing import List
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
from django.db.models import F, Sum
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from bbs import models
|
from bbs.api.types.inventory import Transaction, TransactionOrdering
|
||||||
from bbs.api.types.inventory import (
|
|
||||||
InventoryReport,
|
|
||||||
InventoryReportProduct,
|
|
||||||
InventoryTransaction,
|
|
||||||
InventoryTransactionOrdering,
|
|
||||||
Transaction,
|
|
||||||
TransactionOrdering,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class InventoryQueries:
|
class InventoryQueries:
|
||||||
transaction_list: list[Transaction] = strawberry_django.field(pagination=True, ordering=TransactionOrdering)
|
transaction_list: List[Transaction] = strawberry_django.field(pagination=True, order=TransactionOrdering)
|
||||||
inventory_transaction_list: list[InventoryTransaction] = strawberry_django.field(
|
|
||||||
pagination=True,
|
|
||||||
ordering=InventoryTransactionOrdering,
|
|
||||||
)
|
|
||||||
|
|
||||||
@strawberry.field
|
|
||||||
def inventory_report(self, start_time: datetime, end_time: datetime) -> InventoryReport:
|
|
||||||
if timezone.is_naive(start_time):
|
|
||||||
start_time = timezone.make_aware(start_time)
|
|
||||||
if timezone.is_naive(end_time):
|
|
||||||
end_time = timezone.make_aware(end_time)
|
|
||||||
if start_time > end_time:
|
|
||||||
raise ValueError("The report start date must not be after the end date.")
|
|
||||||
|
|
||||||
purchases = (
|
|
||||||
models.InventoryTransaction.objects.filter(created__range=(start_time, end_time))
|
|
||||||
.values("product_id", "product__name")
|
|
||||||
.annotate(units=Sum("amount"), value=Sum(F("amount") * F("unit_price")))
|
|
||||||
)
|
|
||||||
sales = (
|
|
||||||
models.OrderLine.objects.filter(order__created__range=(start_time, end_time))
|
|
||||||
.values("product_id", "product__name")
|
|
||||||
.annotate(units=Sum("amount"), value=Sum(F("amount") * F("unit_price")))
|
|
||||||
)
|
|
||||||
|
|
||||||
product_totals = {}
|
|
||||||
for product in purchases:
|
|
||||||
product_totals[product["product_id"]] = {
|
|
||||||
"product_id": product["product_id"],
|
|
||||||
"product_name": product["product__name"],
|
|
||||||
"purchase_units": product["units"] or 0,
|
|
||||||
"purchase_value": product["value"] or Decimal(0),
|
|
||||||
"sales_units": 0,
|
|
||||||
"sales_value": Decimal(0),
|
|
||||||
}
|
|
||||||
|
|
||||||
for product in sales:
|
|
||||||
product_data = product_totals.setdefault(
|
|
||||||
product["product_id"],
|
|
||||||
{
|
|
||||||
"product_id": product["product_id"],
|
|
||||||
"product_name": product["product__name"],
|
|
||||||
"purchase_units": 0,
|
|
||||||
"purchase_value": Decimal(0),
|
|
||||||
"sales_units": 0,
|
|
||||||
"sales_value": Decimal(0),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
product_data["sales_units"] = product["units"] or 0
|
|
||||||
product_data["sales_value"] = product["value"] or Decimal(0)
|
|
||||||
|
|
||||||
product_data = sorted(product_totals.values(), key=lambda product: product["product_name"])
|
|
||||||
inventory_purchases = sum((product["purchase_value"] for product in product_data), Decimal(0))
|
|
||||||
product_sales = sum((product["sales_value"] for product in product_data), Decimal(0))
|
|
||||||
|
|
||||||
deposits = (
|
|
||||||
models.Transaction.objects.filter(created__range=(start_time, end_time)).aggregate(total=Sum("amount"))[
|
|
||||||
"total"
|
|
||||||
]
|
|
||||||
or Decimal(0)
|
|
||||||
)
|
|
||||||
order_purchases = (
|
|
||||||
models.Order.objects.filter(created__range=(start_time, end_time)).aggregate(total=Sum("order_sum"))[
|
|
||||||
"total"
|
|
||||||
]
|
|
||||||
or Decimal(0)
|
|
||||||
)
|
|
||||||
deposits_start = (
|
|
||||||
models.Transaction.objects.filter(created__lt=start_time).aggregate(total=Sum("amount"))["total"]
|
|
||||||
or Decimal(0)
|
|
||||||
)
|
|
||||||
orders_start = (
|
|
||||||
models.Order.objects.filter(created__lt=start_time).aggregate(total=Sum("order_sum"))["total"] or Decimal(0)
|
|
||||||
)
|
|
||||||
deposits_end = (
|
|
||||||
models.Transaction.objects.filter(created__lte=end_time).aggregate(total=Sum("amount"))["total"]
|
|
||||||
or Decimal(0)
|
|
||||||
)
|
|
||||||
orders_end = (
|
|
||||||
models.Order.objects.filter(created__lte=end_time).aggregate(total=Sum("order_sum"))["total"] or Decimal(0)
|
|
||||||
)
|
|
||||||
|
|
||||||
return InventoryReport(
|
|
||||||
products=[InventoryReportProduct(**product) for product in product_data],
|
|
||||||
inventory_purchases=inventory_purchases,
|
|
||||||
product_sales=product_sales,
|
|
||||||
deposits=deposits,
|
|
||||||
order_purchases=order_purchases,
|
|
||||||
balance_at_start=deposits_start - orders_start,
|
|
||||||
balance_at_end=deposits_end - orders_end,
|
|
||||||
)
|
|
||||||
+8
-17
@@ -1,23 +1,17 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
|
|
||||||
from bbs import models
|
from bbs import models
|
||||||
from bbs.api.types.shop import (
|
from bbs.api.types.shop import Order, OrderOrdering, OrderStats, OrderStatsByTime, Point, Product, ProductOrderStat
|
||||||
Order,
|
|
||||||
OrderOrdering,
|
|
||||||
OrderStats,
|
|
||||||
OrderStatsByTime,
|
|
||||||
Point,
|
|
||||||
Product,
|
|
||||||
ProductOrderStat,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class ShopQueries:
|
class ShopQueries:
|
||||||
all_products: list[Product] = strawberry_django.field()
|
all_products: List[Product] = strawberry_django.field()
|
||||||
all_orders: list[Order] = strawberry_django.field()
|
all_orders: List[Order] = strawberry_django.field()
|
||||||
order_list: list[Order] = strawberry_django.field(pagination=True, ordering=OrderOrdering)
|
order_list: List[Order] = strawberry_django.field(pagination=True, order=OrderOrdering)
|
||||||
|
|
||||||
@strawberry.field
|
@strawberry.field
|
||||||
def order_stats(self) -> OrderStats:
|
def order_stats(self) -> OrderStats:
|
||||||
@@ -32,11 +26,8 @@ class ShopQueries:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@strawberry.field
|
@strawberry.field
|
||||||
def product_stats(self) -> list[ProductOrderStat]:
|
def product_stats(self) -> List[ProductOrderStat]:
|
||||||
return [
|
return [
|
||||||
ProductOrderStat(
|
ProductOrderStat(product_name=stat["product_name"], data=[Point(**p) for p in stat["data"]])
|
||||||
product_name=stat["product_name"],
|
|
||||||
data=[Point(**p) for p in stat["data"]],
|
|
||||||
)
|
|
||||||
for stat in models.OrderLine.objects.product_order_stats().as_series()
|
for stat in models.OrderLine.objects.product_order_stats().as_series()
|
||||||
]
|
]
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
|
import strawberry_django
|
||||||
from django.contrib.auth.models import User as DjangoUser
|
from django.contrib.auth.models import User as DjangoUser
|
||||||
from strawberry import ID
|
from strawberry import ID
|
||||||
|
|
||||||
@@ -7,9 +10,7 @@ from bbs.api.types.users import User
|
|||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class UsersQueries:
|
class UsersQueries:
|
||||||
@strawberry.field
|
all_users: List[User] = strawberry_django.field()
|
||||||
def all_users(self) -> list[User]:
|
|
||||||
return DjangoUser.objects.filter(profile__isnull=False)
|
|
||||||
|
|
||||||
@strawberry.field
|
@strawberry.field
|
||||||
def user(self, user_id: ID) -> User:
|
def user(self, user_id: ID) -> User:
|
||||||
|
|||||||
+4
-6
@@ -1,8 +1,7 @@
|
|||||||
import strawberry
|
import strawberry
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from strawberry.extensions import SchemaExtension
|
from strawberry.extensions import Extension
|
||||||
|
|
||||||
from bbs.api.mutations.inventory import InventoryMutations
|
|
||||||
from bbs.api.mutations.shop import ShopMutations
|
from bbs.api.mutations.shop import ShopMutations
|
||||||
from bbs.api.mutations.users import UsersMutations
|
from bbs.api.mutations.users import UsersMutations
|
||||||
from bbs.api.queries.inventory import InventoryQueries
|
from bbs.api.queries.inventory import InventoryQueries
|
||||||
@@ -16,13 +15,12 @@ class Query(InventoryQueries, ShopQueries, UsersQueries):
|
|||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class Mutation(InventoryMutations, ShopMutations, UsersMutations):
|
class Mutation(ShopMutations, UsersMutations):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HandleNotFound(SchemaExtension):
|
class HandleNotFound(Extension):
|
||||||
def on_operation(self):
|
def on_request_end(self):
|
||||||
yield
|
|
||||||
result = self.execution_context.result
|
result = self.execution_context.result
|
||||||
if result.errors:
|
if result.errors:
|
||||||
for error in result.errors:
|
for error in result.errors:
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
from decimal import Decimal
|
|
||||||
from typing import TYPE_CHECKING, Annotated
|
|
||||||
|
|
||||||
import strawberry
|
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
from strawberry import ID, auto
|
from strawberry_django import auto
|
||||||
|
|
||||||
from bbs import models
|
from bbs import models
|
||||||
|
from bbs.api.types.users import User
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .shop import Product
|
|
||||||
from .users import User
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.order_type(models.Transaction)
|
@strawberry_django.ordering.order(models.Transaction)
|
||||||
class TransactionOrdering:
|
class TransactionOrdering:
|
||||||
created: auto
|
created: auto
|
||||||
|
|
||||||
@@ -20,43 +13,6 @@ class TransactionOrdering:
|
|||||||
@strawberry_django.type(models.Transaction)
|
@strawberry_django.type(models.Transaction)
|
||||||
class Transaction:
|
class Transaction:
|
||||||
id: auto
|
id: auto
|
||||||
user: Annotated["User", strawberry.lazy(".users")]
|
user: "User"
|
||||||
amount: auto
|
amount: auto
|
||||||
created: auto
|
created: auto
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.type(models.InventoryTransaction)
|
|
||||||
class InventoryTransaction:
|
|
||||||
id: auto
|
|
||||||
user: Annotated["User", strawberry.lazy(".users")]
|
|
||||||
product: Annotated["Product", strawberry.lazy(".shop")]
|
|
||||||
amount: auto
|
|
||||||
unit_price: auto
|
|
||||||
comment: auto
|
|
||||||
created: auto
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.order_type(models.InventoryTransaction)
|
|
||||||
class InventoryTransactionOrdering:
|
|
||||||
created: auto
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
|
||||||
class InventoryReportProduct:
|
|
||||||
product_id: ID
|
|
||||||
product_name: str
|
|
||||||
purchase_units: int
|
|
||||||
purchase_value: Decimal
|
|
||||||
sales_units: int
|
|
||||||
sales_value: Decimal
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
|
||||||
class InventoryReport:
|
|
||||||
products: list[InventoryReportProduct]
|
|
||||||
inventory_purchases: Decimal
|
|
||||||
product_sales: Decimal
|
|
||||||
deposits: Decimal
|
|
||||||
order_purchases: Decimal
|
|
||||||
balance_at_start: Decimal
|
|
||||||
balance_at_end: Decimal
|
|
||||||
+13
-22
@@ -1,18 +1,14 @@
|
|||||||
from decimal import Decimal
|
from typing import List
|
||||||
from typing import TYPE_CHECKING, Annotated
|
|
||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
from strawberry import auto
|
from strawberry_django import auto
|
||||||
from strawberry.scalars import JSON
|
|
||||||
|
|
||||||
from bbs import models
|
from bbs import models
|
||||||
|
from bbs.api.types.utils import JSONScalar
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .users import User
|
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.filter_type(models.Product)
|
@strawberry_django.filters.filter(models.Product)
|
||||||
class ProductFilter:
|
class ProductFilter:
|
||||||
id: auto
|
id: auto
|
||||||
active: auto
|
active: auto
|
||||||
@@ -25,16 +21,11 @@ class Product:
|
|||||||
image_url: str
|
image_url: str
|
||||||
sale_price_int: auto
|
sale_price_int: auto
|
||||||
sale_price_ext: auto
|
sale_price_ext: auto
|
||||||
active: auto
|
|
||||||
inventory_amount: auto
|
|
||||||
updated: auto
|
|
||||||
wholesale_unit_price: Decimal
|
|
||||||
wholesale_value: Decimal
|
|
||||||
# FIXME: stricter types
|
# FIXME: stricter types
|
||||||
user_counts: JSON
|
user_counts: JSONScalar
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.order_type(models.Order)
|
@strawberry_django.ordering.order(models.Order)
|
||||||
class OrderOrdering:
|
class OrderOrdering:
|
||||||
created: auto
|
created: auto
|
||||||
|
|
||||||
@@ -42,10 +33,10 @@ class OrderOrdering:
|
|||||||
@strawberry_django.type(models.Order)
|
@strawberry_django.type(models.Order)
|
||||||
class Order:
|
class Order:
|
||||||
id: auto
|
id: auto
|
||||||
customer: Annotated["User", strawberry.lazy(".users")] | None
|
customer: strawberry.LazyType["User", "bbs.api.types.users"]
|
||||||
order_sum: auto
|
order_sum: auto
|
||||||
created: auto
|
created: auto
|
||||||
orderlines: list["OrderLine"]
|
orderlines: List["OrderLine"]
|
||||||
is_external: bool
|
is_external: bool
|
||||||
|
|
||||||
|
|
||||||
@@ -66,10 +57,10 @@ class OrderStatsByTime:
|
|||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class OrderStats:
|
class OrderStats:
|
||||||
yearly: list[OrderStatsByTime]
|
yearly: List[OrderStatsByTime]
|
||||||
monthly: list[OrderStatsByTime]
|
monthly: List[OrderStatsByTime]
|
||||||
daily: list[OrderStatsByTime]
|
daily: List[OrderStatsByTime]
|
||||||
hourly: list[OrderStatsByTime]
|
hourly: List[OrderStatsByTime]
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
@@ -81,4 +72,4 @@ class Point:
|
|||||||
@strawberry.type
|
@strawberry.type
|
||||||
class ProductOrderStat:
|
class ProductOrderStat:
|
||||||
product_name: str
|
product_name: str
|
||||||
data: list[Point]
|
data: List[Point]
|
||||||
+13
-17
@@ -1,20 +1,22 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from typing import List
|
||||||
|
|
||||||
import strawberry
|
import strawberry
|
||||||
import strawberry_django
|
import strawberry_django
|
||||||
from django.contrib.auth.models import User as DjangoUser
|
from django.contrib.auth import get_user_model
|
||||||
from strawberry import auto
|
from strawberry_django import auto
|
||||||
|
|
||||||
from bbs import models
|
from bbs import models
|
||||||
from bbs.api.types.shop import Point, ProductOrderStat
|
from bbs.api.types.shop import Point, ProductOrderStat
|
||||||
|
|
||||||
|
UserModel = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
@strawberry.type
|
@strawberry.type
|
||||||
class TopMonth:
|
class TopMonth:
|
||||||
period: str
|
period: str
|
||||||
count: int
|
count: int
|
||||||
sum: float
|
sum: int
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.type(models.UserProfile)
|
@strawberry_django.type(models.UserProfile)
|
||||||
@@ -23,29 +25,23 @@ class UserProfile:
|
|||||||
balance: auto
|
balance: auto
|
||||||
image: auto
|
image: auto
|
||||||
last_purchase_date: datetime
|
last_purchase_date: datetime
|
||||||
order_sum_total: Decimal
|
order_sum_total: float
|
||||||
|
|
||||||
@strawberry.field()
|
@strawberry.field()
|
||||||
def top_months(self) -> list[TopMonth]:
|
def top_months(self) -> List[TopMonth]:
|
||||||
return [
|
return [TopMonth(**month) for month in self.top_months()]
|
||||||
TopMonth(period=month["period"], count=month["count"], sum=float(month["sum"]))
|
|
||||||
for month in self.top_months()
|
|
||||||
]
|
|
||||||
|
|
||||||
@strawberry.field()
|
@strawberry.field()
|
||||||
def product_order_stats(self) -> list[ProductOrderStat]:
|
def product_order_stats(self) -> List[ProductOrderStat]:
|
||||||
return [
|
return [
|
||||||
ProductOrderStat(
|
ProductOrderStat(product_name=stat["product_name"], data=[Point(**p) for p in stat["data"]])
|
||||||
product_name=stat["product_name"],
|
|
||||||
data=[Point(**p) for p in stat["data"]],
|
|
||||||
)
|
|
||||||
for stat in self.product_order_stats().as_series()
|
for stat in self.product_order_stats().as_series()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.type(DjangoUser)
|
@strawberry_django.type(UserModel)
|
||||||
class User:
|
class User:
|
||||||
id: auto
|
id: auto
|
||||||
first_name: auto
|
first_name: auto
|
||||||
last_name: auto
|
last_name: auto
|
||||||
profile: UserProfile | None
|
profile: UserProfile
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import json
|
||||||
|
from typing import Any, NewType
|
||||||
|
|
||||||
|
import strawberry
|
||||||
|
|
||||||
|
JSONScalar = strawberry.scalar(
|
||||||
|
NewType("JSONScalar", Any),
|
||||||
|
serialize=lambda v: v,
|
||||||
|
parse_value=lambda v: json.loads(v),
|
||||||
|
description="The GenericScalar scalar type represents a generic GraphQL scalar value that could be: List or Object.",
|
||||||
|
)
|
||||||
+2
-10
@@ -2,21 +2,13 @@ from django import forms
|
|||||||
|
|
||||||
from bbs.models import InventoryTransaction, Product
|
from bbs.models import InventoryTransaction, Product
|
||||||
|
|
||||||
MAX_DIGITS = 9
|
|
||||||
DECIMAL_PLACES = 1
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryTransactionForm(forms.ModelForm):
|
class InventoryTransactionForm(forms.ModelForm):
|
||||||
unit_price = forms.DecimalField(
|
unit_price = forms.FloatField(required=True, localize=True)
|
||||||
max_digits=MAX_DIGITS,
|
|
||||||
decimal_places=DECIMAL_PLACES,
|
|
||||||
required=True,
|
|
||||||
localize=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Only show active products
|
# Only show active products
|
||||||
super().__init__(*args, **kwargs)
|
super(InventoryTransactionForm, self).__init__(*args, **kwargs)
|
||||||
self.fields["product"].queryset = Product.objects.filter(active=True)
|
self.fields["product"].queryset = Product.objects.filter(active=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ class Command(BaseCommand):
|
|||||||
def add_arguments(self, parser: CommandParser):
|
def add_arguments(self, parser: CommandParser):
|
||||||
parser.add_argument("--filename", default="schema.graphql", help="`-` prints to stdout")
|
parser.add_argument("--filename", default="schema.graphql", help="`-` prints to stdout")
|
||||||
|
|
||||||
def handle(self, *args, filename=None, **options): # noqa: ARG002
|
def handle(self, *args, filename=None, **options):
|
||||||
schema_output = print_schema(schema_symbol)
|
schema_output = print_schema(schema_symbol)
|
||||||
if filename != "-":
|
if filename != "-":
|
||||||
with Path(settings.BASE_DIR, filename).open("w+") as fp:
|
with Path(settings.BASE_DIR, filename).open("w+") as fp:
|
||||||
fp.write(schema_output)
|
fp.write(schema_output)
|
||||||
self.stdout.write(self.style.SUCCESS("OK"))
|
self.stdout.write(self.style.SUCCESS("OK"))
|
||||||
else:
|
else:
|
||||||
pass
|
print(schema_output)
|
||||||
@@ -10,10 +10,11 @@ class Command(BaseCommand):
|
|||||||
parser.add_argument("--dry-run", action="store_true")
|
parser.add_argument("--dry-run", action="store_true")
|
||||||
parser.add_argument("--force", action="store_true")
|
parser.add_argument("--force", action="store_true")
|
||||||
|
|
||||||
def handle(self, *args, dry_run=False, force=False, **options): # noqa: ARG002
|
def handle(self, *args, dry_run=False, force=False, **options):
|
||||||
for profile in UserProfile.objects.all().order_by("id"):
|
for profile in UserProfile.objects.all().order_by("id"):
|
||||||
if dry_run:
|
if dry_run:
|
||||||
pass
|
print(profile.user.email, profile.image_url())
|
||||||
elif force or not profile.image:
|
else:
|
||||||
|
if force or not profile.image:
|
||||||
profile.image = profile.image_url()
|
profile.image = profile.image_url()
|
||||||
profile.save()
|
profile.save()
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
import os
|
|
||||||
from datetime import datetime, time, timedelta
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.core.management import BaseCommand
|
|
||||||
from django.db.models import Sum
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from bbs.models import InventoryTransaction, Order, OrderLine, Product, Transaction, UserProfile
|
|
||||||
|
|
||||||
DEMO_USERS = (
|
|
||||||
("demo-ada", "Ada", "Nord", "ada@example.test", Decimal("300.0")),
|
|
||||||
("demo-bjørn", "Bjørn", "Berg", "bjorn@example.test", Decimal("250.0")),
|
|
||||||
("demo-casey", "Casey", "Sol", "casey@example.test", Decimal("200.0")),
|
|
||||||
)
|
|
||||||
|
|
||||||
DEMO_PRODUCTS = (
|
|
||||||
("Demo Cola", Decimal("25.0"), Decimal("30.0"), 30),
|
|
||||||
("Demo Kaffe", Decimal("15.0"), Decimal("20.0"), 30),
|
|
||||||
("Demo Sjokolade", Decimal("20.0"), Decimal("25.0"), 30),
|
|
||||||
("Demo Energidrikk", Decimal("35.0"), Decimal("40.0"), 30),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Create repeatable demo users, products, deposits, and orders for local testing."
|
|
||||||
|
|
||||||
def handle(self, *args, **options): # noqa: ARG002
|
|
||||||
users = self.seed_users()
|
|
||||||
products = self.seed_products(users[0][0])
|
|
||||||
self.seed_deposits(users)
|
|
||||||
self.seed_orders(users, products)
|
|
||||||
self.update_balances(users)
|
|
||||||
self.create_admin()
|
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS("Demo data is ready."))
|
|
||||||
if not os.environ.get("BBS_SEED_ADMIN_PASSWORD"):
|
|
||||||
self.stdout.write("Create an admin login with: uv run manage.py createsuperuser")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def seed_users():
|
|
||||||
user_model = get_user_model()
|
|
||||||
users = []
|
|
||||||
for username, first_name, last_name, email, deposit_amount in DEMO_USERS:
|
|
||||||
user, _ = user_model.objects.get_or_create(
|
|
||||||
username=username,
|
|
||||||
defaults={"first_name": first_name, "last_name": last_name, "email": email},
|
|
||||||
)
|
|
||||||
UserProfile.objects.get_or_create(user=user)
|
|
||||||
users.append((user, deposit_amount))
|
|
||||||
return users
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def seed_products(stock_user):
|
|
||||||
products = {}
|
|
||||||
for name, sale_price_int, sale_price_ext, stock in DEMO_PRODUCTS:
|
|
||||||
product, created = Product.objects.get_or_create(
|
|
||||||
name=name,
|
|
||||||
defaults={
|
|
||||||
"sale_price_int": sale_price_int,
|
|
||||||
"sale_price_ext": sale_price_ext,
|
|
||||||
"inventory_amount": stock,
|
|
||||||
"description": "Demo product for local development.",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
products[name] = product
|
|
||||||
if created:
|
|
||||||
InventoryTransaction.objects.get_or_create(
|
|
||||||
user=stock_user,
|
|
||||||
product=product,
|
|
||||||
comment="Demo opening stock",
|
|
||||||
defaults={"amount": stock, "unit_price": Decimal("10.0")},
|
|
||||||
)
|
|
||||||
return products
|
|
||||||
|
|
||||||
def seed_deposits(self, users):
|
|
||||||
for index, (user, deposit_amount) in enumerate(users):
|
|
||||||
created_at = self.timestamp(days_ago=32 + index)
|
|
||||||
deposit, created = Transaction.objects.get_or_create(
|
|
||||||
user=user,
|
|
||||||
created=created_at,
|
|
||||||
defaults={"amount": deposit_amount},
|
|
||||||
)
|
|
||||||
if created:
|
|
||||||
deposit.created = created_at
|
|
||||||
deposit.save(update_fields=["created"])
|
|
||||||
|
|
||||||
def seed_orders(self, users, products):
|
|
||||||
orders = (
|
|
||||||
(0, 24, (("Demo Cola", 2), ("Demo Sjokolade", 1))),
|
|
||||||
(1, 19, (("Demo Kaffe", 2), ("Demo Sjokolade", 1))),
|
|
||||||
(2, 11, (("Demo Energidrikk", 1),)),
|
|
||||||
(0, 4, (("Demo Kaffe", 1), ("Demo Cola", 1))),
|
|
||||||
(1, 1, (("Demo Cola", 2),)),
|
|
||||||
)
|
|
||||||
for user_index, days_ago, lines in orders:
|
|
||||||
user = users[user_index][0]
|
|
||||||
created_at = self.timestamp(days_ago=days_ago)
|
|
||||||
total = sum(
|
|
||||||
(products[name].sale_price_int * amount for name, amount in lines),
|
|
||||||
start=Decimal("0"),
|
|
||||||
)
|
|
||||||
order, created = Order.objects.get_or_create(
|
|
||||||
customer=user,
|
|
||||||
created=created_at,
|
|
||||||
defaults={"order_sum": total},
|
|
||||||
)
|
|
||||||
if created:
|
|
||||||
order.created = created_at
|
|
||||||
order.save(update_fields=["created"])
|
|
||||||
for name, amount in lines:
|
|
||||||
product = products[name]
|
|
||||||
OrderLine.objects.create(
|
|
||||||
order=order,
|
|
||||||
product=product,
|
|
||||||
amount=amount,
|
|
||||||
unit_price=product.sale_price_int,
|
|
||||||
)
|
|
||||||
product.inventory_amount -= amount
|
|
||||||
product.save(update_fields=["inventory_amount"])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def update_balances(users):
|
|
||||||
for user, _ in users:
|
|
||||||
deposits = Transaction.objects.filter(user=user).aggregate(total=Sum("amount"))["total"] or Decimal("0")
|
|
||||||
purchases = Order.objects.filter(customer=user).aggregate(total=Sum("order_sum"))["total"] or Decimal("0")
|
|
||||||
profile = user.profile
|
|
||||||
profile.balance = deposits - purchases
|
|
||||||
profile.save(update_fields=["balance"])
|
|
||||||
|
|
||||||
def create_admin(self):
|
|
||||||
admin_password = os.environ.get("BBS_SEED_ADMIN_PASSWORD")
|
|
||||||
if not admin_password:
|
|
||||||
return
|
|
||||||
|
|
||||||
user_model = get_user_model()
|
|
||||||
if user_model.objects.filter(username="admin").exists():
|
|
||||||
self.stdout.write("Existing 'admin' account left unchanged.")
|
|
||||||
else:
|
|
||||||
user_model.objects.create_superuser(
|
|
||||||
username="admin",
|
|
||||||
email="admin@example.test",
|
|
||||||
password=admin_password,
|
|
||||||
)
|
|
||||||
self.stdout.write("Created superuser 'admin'.")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def timestamp(days_ago: int) -> datetime:
|
|
||||||
date = timezone.localdate() - timedelta(days=days_ago)
|
|
||||||
return timezone.make_aware(datetime.combine(date, time(hour=12)))
|
|
||||||
@@ -3,6 +3,7 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
@@ -179,6 +180,6 @@ class Migration(migrations.Migration):
|
|||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name="orderline",
|
name="orderline",
|
||||||
unique_together={("order", "product")},
|
unique_together=set([("order", "product")]),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# Generated by Django 4.0.2 on 2022-03-05 19:26
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("bbs", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="inventorytransaction",
|
|
||||||
name="amount",
|
|
||||||
field=models.IntegerField(verbose_name="Antall"),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="inventorytransaction",
|
|
||||||
name="unit_price",
|
|
||||||
field=models.DecimalField(decimal_places=1, max_digits=9, verbose_name="Enhetspris"),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="orderline",
|
|
||||||
name="unit_price",
|
|
||||||
field=models.DecimalField(decimal_places=1, max_digits=9, verbose_name="Enhetspris"),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="product",
|
|
||||||
name="alcohol_percent",
|
|
||||||
field=models.DecimalField(decimal_places=1, max_digits=9, null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="product",
|
|
||||||
name="inventory_amount",
|
|
||||||
field=models.IntegerField(default=0),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="product",
|
|
||||||
name="sale_price_ext",
|
|
||||||
field=models.DecimalField(decimal_places=1, max_digits=9),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="product",
|
|
||||||
name="sale_price_int",
|
|
||||||
field=models.DecimalField(decimal_places=1, max_digits=9),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="product",
|
|
||||||
name="volume_liter",
|
|
||||||
field=models.DecimalField(decimal_places=1, max_digits=9, null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="transaction",
|
|
||||||
name="amount",
|
|
||||||
field=models.DecimalField(decimal_places=1, max_digits=9),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="userprofile",
|
|
||||||
name="balance",
|
|
||||||
field=models.DecimalField(decimal_places=1, default=0, max_digits=9),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 4.0.2 on 2023-06-28 19:11
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("bbs", "0002_alter_inventorytransaction_amount_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="order",
|
|
||||||
name="order_sum",
|
|
||||||
field=models.DecimalField(db_column="sum", decimal_places=1, max_digits=9),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
+30
-41
@@ -1,7 +1,7 @@
|
|||||||
import hashlib # noqa: EXE002
|
import hashlib
|
||||||
from datetime import UTC, datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -17,8 +17,6 @@ from django.db.models.functions import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
EXTERNAL_USER = "Ekstern"
|
EXTERNAL_USER = "Ekstern"
|
||||||
MAX_DIGITS = 9
|
|
||||||
DECIMAL_PLACES = 1
|
|
||||||
|
|
||||||
|
|
||||||
def _format_counts(per_user):
|
def _format_counts(per_user):
|
||||||
@@ -38,15 +36,14 @@ def _format_counts(per_user):
|
|||||||
|
|
||||||
class Product(models.Model):
|
class Product(models.Model):
|
||||||
name = models.CharField(max_length=64)
|
name = models.CharField(max_length=64)
|
||||||
sale_price_int = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES)
|
sale_price_int = models.FloatField()
|
||||||
sale_price_ext = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES)
|
sale_price_ext = models.FloatField()
|
||||||
description = models.CharField(max_length=256, blank=True)
|
description = models.CharField(max_length=256, blank=True)
|
||||||
volume_liter = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, null=True)
|
volume_liter = models.FloatField(null=True)
|
||||||
alcohol_percent = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, null=True)
|
alcohol_percent = models.FloatField(null=True)
|
||||||
image = models.ImageField(upload_to="products", blank=True)
|
image = models.ImageField(upload_to="products", blank=True)
|
||||||
customer_support = models.CharField(max_length=32, blank=True)
|
customer_support = models.CharField(max_length=32, blank=True)
|
||||||
# FIXME: rename to inventory_quantity
|
inventory_amount = models.FloatField(default=0)
|
||||||
inventory_amount = models.IntegerField(default=0)
|
|
||||||
active = models.BooleanField(default=True)
|
active = models.BooleanField(default=True)
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
updated = models.DateTimeField(auto_now=True)
|
updated = models.DateTimeField(auto_now=True)
|
||||||
@@ -86,26 +83,21 @@ class Product(models.Model):
|
|||||||
|
|
||||||
return _format_counts(per_user)
|
return _format_counts(per_user)
|
||||||
|
|
||||||
def __str__(self): # noqa: DJ012
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class Meta: # noqa: DJ012
|
class Meta:
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
|
||||||
|
|
||||||
class OrderManager(models.Manager):
|
class OrderManager(models.Manager):
|
||||||
def count_by_created(self, extract_class: type[Extract]):
|
def count_by_created(self, extract_class: Type[Extract]):
|
||||||
period = extract_class("created")
|
period = extract_class("created")
|
||||||
return self.get_queryset().annotate(period=period).values("period").annotate(count=Count("id"))
|
return self.get_queryset().annotate(period=period).values("period").annotate(count=Count("id"))
|
||||||
|
|
||||||
def count_by_created_period(self, period: str):
|
def count_by_created_period(self, period: str):
|
||||||
periods = {
|
periods = {"yearly": ExtractYear, "monthly": ExtractMonth, "daily": ExtractIsoWeekDay, "hourly": ExtractHour}
|
||||||
"yearly": ExtractYear,
|
assert period in periods
|
||||||
"monthly": ExtractMonth,
|
|
||||||
"daily": ExtractIsoWeekDay,
|
|
||||||
"hourly": ExtractHour,
|
|
||||||
}
|
|
||||||
assert period in periods # noqa: S101
|
|
||||||
return self.count_by_created(periods[period])
|
return self.count_by_created(periods[period])
|
||||||
|
|
||||||
|
|
||||||
@@ -117,7 +109,7 @@ class Order(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
related_name="orders",
|
related_name="orders",
|
||||||
)
|
)
|
||||||
order_sum = models.DecimalField(db_column="sum", max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES)
|
order_sum = models.FloatField(db_column="sum")
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
objects = OrderManager()
|
objects = OrderManager()
|
||||||
@@ -128,7 +120,7 @@ class Order(models.Model):
|
|||||||
def reciept(self):
|
def reciept(self):
|
||||||
return ", ".join([str(ol) for ol in self.orderlines.all()])
|
return ", ".join([str(ol) for ol in self.orderlines.all()])
|
||||||
|
|
||||||
def __str__(self): # noqa: DJ012
|
def __str__(self):
|
||||||
return f"{self.customer or EXTERNAL_USER}: {self.order_sum} kr"
|
return f"{self.customer or EXTERNAL_USER}: {self.order_sum} kr"
|
||||||
|
|
||||||
|
|
||||||
@@ -156,10 +148,8 @@ class OrderLineManager(models.QuerySet):
|
|||||||
class OrderLine(models.Model):
|
class OrderLine(models.Model):
|
||||||
order = models.ForeignKey("bbs.Order", on_delete=models.CASCADE, related_name="orderlines")
|
order = models.ForeignKey("bbs.Order", on_delete=models.CASCADE, related_name="orderlines")
|
||||||
product = models.ForeignKey("bbs.Product", on_delete=models.CASCADE, related_name="orderlines")
|
product = models.ForeignKey("bbs.Product", on_delete=models.CASCADE, related_name="orderlines")
|
||||||
# FIXME: rename to quantity
|
|
||||||
amount = models.IntegerField()
|
amount = models.IntegerField()
|
||||||
# FIXME: rename to price
|
unit_price = models.FloatField(verbose_name="Enhetspris")
|
||||||
unit_price = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, verbose_name="Enhetspris")
|
|
||||||
|
|
||||||
objects = OrderLineManager.as_manager()
|
objects = OrderLineManager.as_manager()
|
||||||
|
|
||||||
@@ -167,10 +157,10 @@ class OrderLine(models.Model):
|
|||||||
def price(self):
|
def price(self):
|
||||||
return self.amount * self.unit_price
|
return self.amount * self.unit_price
|
||||||
|
|
||||||
def __str__(self): # noqa: DJ012
|
def __str__(self):
|
||||||
return f"{self.amount} {self.product} ({self.unit_price} kr per)"
|
return f"{self.amount} {self.product} ({self.unit_price} kr per)"
|
||||||
|
|
||||||
class Meta: # noqa: DJ012
|
class Meta:
|
||||||
unique_together = ("order", "product")
|
unique_together = ("order", "product")
|
||||||
|
|
||||||
|
|
||||||
@@ -178,11 +168,11 @@ class Transaction(models.Model):
|
|||||||
"""Money"""
|
"""Money"""
|
||||||
|
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="transactions")
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="transactions")
|
||||||
amount = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES)
|
amount = models.FloatField()
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.id}: {self.user}: {self.amount}"
|
return f"{self.id}: { self.user}: {self.amount}"
|
||||||
|
|
||||||
|
|
||||||
class InventoryTransaction(models.Model):
|
class InventoryTransaction(models.Model):
|
||||||
@@ -199,10 +189,8 @@ class InventoryTransaction(models.Model):
|
|||||||
related_name="transactions",
|
related_name="transactions",
|
||||||
verbose_name="Produkt",
|
verbose_name="Produkt",
|
||||||
)
|
)
|
||||||
# FIXME: rename to quantity
|
amount = models.FloatField(verbose_name="Antall")
|
||||||
amount = models.IntegerField(verbose_name="Antall")
|
unit_price = models.FloatField(verbose_name="Enhetspris")
|
||||||
# FIXME: rename to price
|
|
||||||
unit_price = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, verbose_name="Enhetspris")
|
|
||||||
comment = models.TextField(verbose_name="Kommentar", blank=True)
|
comment = models.TextField(verbose_name="Kommentar", blank=True)
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
@@ -210,13 +198,13 @@ class InventoryTransaction(models.Model):
|
|||||||
def price(self):
|
def price(self):
|
||||||
return self.amount * self.unit_price
|
return self.amount * self.unit_price
|
||||||
|
|
||||||
def __str__(self): # noqa: DJ012
|
def __str__(self):
|
||||||
return f"{self.id}: {self.user}: {self.product}. {self.amount:.0f} stykker ({self.unit_price} kr per)"
|
return f"{self.id}: {self.user}: {self.product}. {self.amount:.0f} stykker ({self.unit_price} kr per)"
|
||||||
|
|
||||||
|
|
||||||
class UserProfile(models.Model):
|
class UserProfile(models.Model):
|
||||||
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile")
|
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile")
|
||||||
balance = models.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, default=0)
|
balance = models.FloatField(default=0.0)
|
||||||
image = models.URLField(blank=True)
|
image = models.URLField(blank=True)
|
||||||
|
|
||||||
def last_purchase(self):
|
def last_purchase(self):
|
||||||
@@ -224,7 +212,8 @@ class UserProfile(models.Model):
|
|||||||
|
|
||||||
def last_purchase_date(self) -> datetime:
|
def last_purchase_date(self) -> datetime:
|
||||||
last = self.last_purchase()
|
last = self.last_purchase()
|
||||||
return last.created if last else datetime.min.replace(tzinfo=UTC)
|
last_p = last.created if last else datetime.min
|
||||||
|
return last_p
|
||||||
|
|
||||||
def top_months(self, limit=5):
|
def top_months(self, limit=5):
|
||||||
return (
|
return (
|
||||||
@@ -238,21 +227,21 @@ class UserProfile(models.Model):
|
|||||||
def product_order_stats(self):
|
def product_order_stats(self):
|
||||||
return OrderLine.objects.filter(order__customer__pk=self.pk).product_order_stats()
|
return OrderLine.objects.filter(order__customer__pk=self.pk).product_order_stats()
|
||||||
|
|
||||||
def order_sum_total(self) -> Decimal:
|
def order_sum_total(self) -> float:
|
||||||
return Order.objects.filter(customer_id=self.user_id).aggregate(sum=Sum("order_sum"))["sum"] or 0
|
return Order.objects.filter(customer_id=self.user_id).aggregate(sum=Sum("order_sum"))["sum"] or 0
|
||||||
|
|
||||||
def image_url(self) -> str:
|
def image_url(self) -> str:
|
||||||
if self.user.email:
|
if self.user.email:
|
||||||
md5_hash = hashlib.md5(self.user.email.lower().strip().encode()).hexdigest() # noqa: S324
|
md5_hash = hashlib.md5(self.user.email.lower().strip().encode()).hexdigest()
|
||||||
return f"https://www.gravatar.com/avatar/{md5_hash}?d=identicon"
|
return f"https://www.gravatar.com/avatar/{md5_hash}?d=identicon"
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def save(self, **kwargs): # noqa: DJ012
|
def save(self, **kwargs):
|
||||||
if not self.image:
|
if not self.image:
|
||||||
self.image = self.image_url()
|
self.image = self.image_url()
|
||||||
|
|
||||||
super().save(**kwargs)
|
super().save(**kwargs)
|
||||||
|
|
||||||
def __str__(self): # noqa: DJ012
|
def __str__(self):
|
||||||
return f"{self.user}"
|
return f"{self.user}"
|
||||||
+14
-13
@@ -1,13 +1,12 @@
|
|||||||
# Django settings for bbs project. # noqa: EXE002
|
# Django settings for bbs project.
|
||||||
import contextlib
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # noqa: PTH120
|
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ADMINS = (
|
ADMINS = (
|
||||||
# ('Your Name', 'your_email@example.com'), # noqa: ERA001
|
# ('Your Name', 'your_email@example.com'),
|
||||||
)
|
)
|
||||||
MANAGERS = ADMINS
|
MANAGERS = ADMINS
|
||||||
|
|
||||||
@@ -21,18 +20,18 @@ DATABASES = {
|
|||||||
TIME_ZONE = "Europe/Oslo"
|
TIME_ZONE = "Europe/Oslo"
|
||||||
LANGUAGE_CODE = "nb"
|
LANGUAGE_CODE = "nb"
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
USE_TZ = True
|
USE_L10N = True
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
# Media and uploads
|
# Media and uploads
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media") # noqa: PTH118
|
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
# Static files
|
# Static files
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") # noqa: PTH118
|
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
|
||||||
STATIC_URL = "/static/"
|
STATIC_URL = "/static/"
|
||||||
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),) # noqa: PTH118
|
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),)
|
||||||
|
|
||||||
STATICFILES_FINDERS = (
|
STATICFILES_FINDERS = (
|
||||||
"django.contrib.staticfiles.finders.FileSystemFinder",
|
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||||
@@ -40,13 +39,13 @@ STATICFILES_FINDERS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Make this unique, and don't share it with anybody.
|
# Make this unique, and don't share it with anybody.
|
||||||
SECRET_KEY = "osv$uy-ea$*%lc)+xiwt5@2b7au%u*c!pa6hmh#vl)*+tp%fss" # noqa: S105
|
SECRET_KEY = "osv$uy-ea$*%lc)+xiwt5@2b7au%u*c!pa6hmh#vl)*+tp%fss"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
"DIRS": [
|
"DIRS": [
|
||||||
os.path.join(BASE_DIR, "templates"), # noqa: PTH118
|
os.path.join(BASE_DIR, "templates"),
|
||||||
],
|
],
|
||||||
"APP_DIRS": True,
|
"APP_DIRS": True,
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
@@ -98,7 +97,7 @@ LOGIN_REDIRECT_URL = "/"
|
|||||||
SESSION_COOKIE_AGE = 1209600 # 15 minutes
|
SESSION_COOKIE_AGE = 1209600 # 15 minutes
|
||||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
||||||
|
|
||||||
BBS_SALDO_MAX = 1000
|
BBS_SALDO_MAX = 600
|
||||||
BBS_LIMIT_DEPOSITS = False
|
BBS_LIMIT_DEPOSITS = False
|
||||||
|
|
||||||
# Thumbnails
|
# Thumbnails
|
||||||
@@ -142,5 +141,7 @@ CORS_ALLOWED_ORIGINS = [
|
|||||||
"http://localhost:3000",
|
"http://localhost:3000",
|
||||||
]
|
]
|
||||||
|
|
||||||
with contextlib.suppress(ImportError):
|
try:
|
||||||
from .local_settings import * # noqa: F403
|
from .local_settings import *
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
Whitespace-only changes.
@@ -1,38 +0,0 @@
|
|||||||
from bbs.forms import DateRangeForm, InventoryTransactionForm
|
|
||||||
from bbs.models import Product
|
|
||||||
|
|
||||||
|
|
||||||
def test_inventory_form_filters_active_products(db): # noqa: ARG001
|
|
||||||
active = Product.objects.create(name="active", sale_price_int=10, sale_price_ext=12, active=True)
|
|
||||||
inactive = Product.objects.create(name="inactive", sale_price_int=10, sale_price_ext=12, active=False)
|
|
||||||
product_queryset = InventoryTransactionForm().fields["product"].queryset
|
|
||||||
|
|
||||||
assert active in product_queryset
|
|
||||||
assert inactive not in product_queryset
|
|
||||||
|
|
||||||
|
|
||||||
def test_inventory_form_valid(db): # noqa: ARG001
|
|
||||||
product = Product.objects.create(name="borg", sale_price_ext=35, sale_price_int=30)
|
|
||||||
form = InventoryTransactionForm(
|
|
||||||
data={"product": product.pk, "amount": 5, "unit_price": "12.5", "comment": ""},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert form.is_valid()
|
|
||||||
|
|
||||||
|
|
||||||
def test_inventory_form_invalid_missing_required_fields(db): # noqa: ARG001
|
|
||||||
form = InventoryTransactionForm(data={})
|
|
||||||
|
|
||||||
assert not form.is_valid()
|
|
||||||
assert "product" in form.errors
|
|
||||||
|
|
||||||
|
|
||||||
def test_date_range_form_allows_missing_dates():
|
|
||||||
assert DateRangeForm(data={}).is_valid()
|
|
||||||
|
|
||||||
|
|
||||||
def test_date_range_form_accepts_dates():
|
|
||||||
form = DateRangeForm(data={"start_time": "2026-01-01 00:00:00", "end_time": "2026-01-31 23:59:59"})
|
|
||||||
|
|
||||||
assert form.is_valid()
|
|
||||||
assert form.cleaned_data["start_time"] is not None
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.core.management import call_command
|
|
||||||
|
|
||||||
from bbs.models import UserProfile
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def user(db): # noqa: ARG001
|
|
||||||
user = get_user_model().objects.create_user(username="profile-image-user", email="image@example.com")
|
|
||||||
UserProfile.objects.create(user=user)
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
def test_profile_images_updates_missing_image(user):
|
|
||||||
UserProfile.objects.filter(pk=user.profile.pk).update(image="")
|
|
||||||
|
|
||||||
call_command("profile_images")
|
|
||||||
|
|
||||||
user.profile.refresh_from_db()
|
|
||||||
assert user.profile.image != ""
|
|
||||||
|
|
||||||
|
|
||||||
def test_profile_images_dry_run_skips_update(user):
|
|
||||||
UserProfile.objects.filter(pk=user.profile.pk).update(image="")
|
|
||||||
|
|
||||||
call_command("profile_images", dry_run=True)
|
|
||||||
|
|
||||||
user.profile.refresh_from_db()
|
|
||||||
assert user.profile.image == ""
|
|
||||||
|
|
||||||
|
|
||||||
def test_profile_images_force_overwrites_existing_image(user):
|
|
||||||
UserProfile.objects.filter(pk=user.profile.pk).update(image="https://example.com/old.jpg")
|
|
||||||
|
|
||||||
call_command("profile_images", force=True)
|
|
||||||
|
|
||||||
user.profile.refresh_from_db()
|
|
||||||
assert user.profile.image not in ("", "https://example.com/old.jpg")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_export_schema_writes_file(tmp_path, settings):
|
|
||||||
settings.BASE_DIR = str(tmp_path)
|
|
||||||
|
|
||||||
call_command("export_schema")
|
|
||||||
|
|
||||||
schema_file = tmp_path / "schema.graphql"
|
|
||||||
assert schema_file.exists()
|
|
||||||
assert "type Query" in schema_file.read_text()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_export_schema_stdout_does_not_write_file(tmp_path, settings, capsys):
|
|
||||||
settings.BASE_DIR = str(tmp_path)
|
|
||||||
|
|
||||||
call_command("export_schema", filename="-")
|
|
||||||
|
|
||||||
assert not (tmp_path / "schema.graphql").exists()
|
|
||||||
assert capsys.readouterr().out == ""
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
from datetime import UTC, datetime
|
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
|
|
||||||
from bbs.models import UserProfile
|
|
||||||
|
|
||||||
|
|
||||||
def test_last_purchase_date_without_orders_is_timezone_aware(db): # noqa: ARG001
|
|
||||||
user = get_user_model().objects.create_user(username="no-purchases")
|
|
||||||
profile = UserProfile.objects.create(user=user)
|
|
||||||
|
|
||||||
assert profile.last_purchase_date() == datetime.min.replace(tzinfo=UTC)
|
|
||||||
+4
-164
@@ -1,49 +1,23 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.test import Client
|
from django.test import Client
|
||||||
from django.utils import timezone
|
|
||||||
from strawberry.django.test import GraphQLTestClient
|
from strawberry.django.test import GraphQLTestClient
|
||||||
|
|
||||||
from bbs.models import InventoryTransaction, Order, OrderLine, Product, Transaction
|
from bbs.models import Order, Product
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="gql_client")
|
@pytest.fixture(name="gql_client")
|
||||||
def gql_client_fixture(client: Client):
|
def gql_client_fixture(client: Client):
|
||||||
return GraphQLTestClient(client)
|
yield GraphQLTestClient(client)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="product")
|
@pytest.fixture(name="product")
|
||||||
def product_fix(db): # noqa: ARG001
|
def product_fix(db):
|
||||||
return Product.objects.create(name="borg", sale_price_ext=35, sale_price_int=30)
|
return Product.objects.create(name="borg", sale_price_ext=35, sale_price_int=30)
|
||||||
|
|
||||||
|
|
||||||
def test_user_not_found(db, gql_client): # noqa: ARG001
|
|
||||||
result = gql_client.query(
|
|
||||||
"""
|
|
||||||
query User($userId: ID!) {
|
|
||||||
user(userId: $userId) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
{"userId": "999"},
|
|
||||||
assert_no_errors=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result.errors
|
|
||||||
error = result.errors[0]
|
|
||||||
assert error["path"] == ["user"]
|
|
||||||
assert error["extensions"]["code"] == "NOT_FOUND"
|
|
||||||
assert error["extensions"]["model"] == "User"
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_order(product, gql_client):
|
def test_create_order(product, gql_client):
|
||||||
assert Order.objects.count() == 0
|
assert Order.objects.count() == 0
|
||||||
variables = {
|
variables = dict(customerId="external", isExternal=True, orderLines=[{"productId": product.id, "amount": 1}])
|
||||||
"customerId": "external",
|
|
||||||
"isExternal": True,
|
|
||||||
"orderLines": [{"productId": product.id, "amount": 1}],
|
|
||||||
}
|
|
||||||
query = """
|
query = """
|
||||||
mutation CreateOrder($customerId: ID!, $isExternal: Boolean!, $orderLines: [OrderLineInput!]!) {
|
mutation CreateOrder($customerId: ID!, $isExternal: Boolean!, $orderLines: [OrderLineInput!]!) {
|
||||||
createOrder(customerId: $customerId, isExternal: $isExternal, orderLines: $orderLines) {
|
createOrder(customerId: $customerId, isExternal: $isExternal, orderLines: $orderLines) {
|
||||||
@@ -72,137 +46,3 @@ def test_create_order(product, gql_client):
|
|||||||
assert Order.objects.count() == 1
|
assert Order.objects.count() == 1
|
||||||
o = Order.objects.first()
|
o = Order.objects.first()
|
||||||
assert o.orderlines.first().product.id == product.id
|
assert o.orderlines.first().product.id == product.id
|
||||||
|
|
||||||
|
|
||||||
def test_create_inventory_transaction_requires_login(product, gql_client):
|
|
||||||
result = gql_client.query(
|
|
||||||
"""
|
|
||||||
mutation CreateInventoryTransaction($data: InventoryTransactionInput!) {
|
|
||||||
createInventoryTransaction(data: $data) {
|
|
||||||
... on FormErrors { message }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
variables={"data": {"productId": str(product.id), "amount": 5, "unitPrice": "12.5"}},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data["createInventoryTransaction"]["message"] == "Logg inn for å registrere varetransaksjoner."
|
|
||||||
product.refresh_from_db()
|
|
||||||
assert product.inventory_amount == 0
|
|
||||||
assert InventoryTransaction.objects.count() == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_inventory_transaction_updates_stock(product, gql_client, client):
|
|
||||||
user = get_user_model().objects.create_user(username="stock-manager")
|
|
||||||
client.force_login(user)
|
|
||||||
|
|
||||||
result = gql_client.query(
|
|
||||||
"""
|
|
||||||
mutation CreateInventoryTransaction($data: InventoryTransactionInput!) {
|
|
||||||
createInventoryTransaction(data: $data) {
|
|
||||||
... on CreateInventoryTransactionSuccess {
|
|
||||||
inventoryTransaction {
|
|
||||||
amount
|
|
||||||
unitPrice
|
|
||||||
product { id inventoryAmount }
|
|
||||||
user { id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
variables={
|
|
||||||
"data": {
|
|
||||||
"productId": str(product.id),
|
|
||||||
"amount": 5,
|
|
||||||
"unitPrice": "12.5",
|
|
||||||
"comment": "Restock",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not result.errors
|
|
||||||
transaction = result.data["createInventoryTransaction"]["inventoryTransaction"]
|
|
||||||
assert transaction["amount"] == 5
|
|
||||||
assert transaction["unitPrice"] == "12.5"
|
|
||||||
assert transaction["product"]["inventoryAmount"] == 5
|
|
||||||
assert transaction["user"]["id"] == str(user.id)
|
|
||||||
product.refresh_from_db()
|
|
||||||
assert product.inventory_amount == 5
|
|
||||||
|
|
||||||
|
|
||||||
def test_inventory_report_aggregates_period_data(product, gql_client):
|
|
||||||
user = get_user_model().objects.create_user(username="report-user")
|
|
||||||
InventoryTransaction.objects.create(user=user, product=product, amount=5, unit_price=10)
|
|
||||||
transaction = Transaction.objects.create(user=user, amount=100)
|
|
||||||
order = Order.objects.create(order_sum=30)
|
|
||||||
OrderLine.objects.create(order=order, product=product, amount=2, unit_price=15)
|
|
||||||
start_time = timezone.make_naive(timezone.now() - timezone.timedelta(days=1))
|
|
||||||
end_time = timezone.make_naive(timezone.now() + timezone.timedelta(days=1))
|
|
||||||
|
|
||||||
result = gql_client.query(
|
|
||||||
"""
|
|
||||||
query InventoryReport($startTime: DateTime!, $endTime: DateTime!) {
|
|
||||||
inventoryReport(startTime: $startTime, endTime: $endTime) {
|
|
||||||
products {
|
|
||||||
productId
|
|
||||||
purchaseUnits
|
|
||||||
purchaseValue
|
|
||||||
salesUnits
|
|
||||||
salesValue
|
|
||||||
}
|
|
||||||
inventoryPurchases
|
|
||||||
productSales
|
|
||||||
deposits
|
|
||||||
orderPurchases
|
|
||||||
balanceAtStart
|
|
||||||
balanceAtEnd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
variables={"startTime": start_time.isoformat(), "endTime": end_time.isoformat()},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not result.errors
|
|
||||||
report = result.data["inventoryReport"]
|
|
||||||
assert report["products"] == [
|
|
||||||
{
|
|
||||||
"productId": str(product.id),
|
|
||||||
"purchaseUnits": 5,
|
|
||||||
"purchaseValue": "50",
|
|
||||||
"salesUnits": 2,
|
|
||||||
"salesValue": "30",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
assert report["inventoryPurchases"] == "50"
|
|
||||||
assert report["productSales"] == "30"
|
|
||||||
assert report["deposits"] == "100"
|
|
||||||
assert report["orderPurchases"] == "30"
|
|
||||||
assert report["balanceAtStart"] == "0"
|
|
||||||
assert report["balanceAtEnd"] == "70"
|
|
||||||
assert transaction.amount == 100
|
|
||||||
|
|
||||||
products_result = gql_client.query(
|
|
||||||
"""
|
|
||||||
query InventoryProducts {
|
|
||||||
allProducts {
|
|
||||||
id
|
|
||||||
active
|
|
||||||
inventoryAmount
|
|
||||||
wholesaleUnitPrice
|
|
||||||
wholesaleValue
|
|
||||||
}
|
|
||||||
inventoryTransactionList(ordering: [{created: DESC}], pagination: {limit: 20, offset: 0}) {
|
|
||||||
amount
|
|
||||||
unitPrice
|
|
||||||
product { name }
|
|
||||||
user { firstName lastName }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not products_result.errors
|
|
||||||
assert products_result.data["allProducts"][0]["active"]
|
|
||||||
assert products_result.data["inventoryTransactionList"][0]["amount"] == 5
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.core.management import call_command
|
|
||||||
from django.test import Client
|
|
||||||
from strawberry.django.test import GraphQLTestClient
|
|
||||||
|
|
||||||
from bbs.models import InventoryTransaction, Order, Product, Transaction
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_seed_demo_data_is_repeatable():
|
|
||||||
call_command("seed_demo_data")
|
|
||||||
call_command("seed_demo_data")
|
|
||||||
|
|
||||||
assert get_user_model().objects.filter(username__startswith="demo-").count() == 3
|
|
||||||
assert Product.objects.filter(name__startswith="Demo ").count() == 4
|
|
||||||
assert Order.objects.count() == 5
|
|
||||||
assert Transaction.objects.count() == 3
|
|
||||||
assert InventoryTransaction.objects.count() == 4
|
|
||||||
assert sum(product.inventory_amount for product in Product.objects.filter(name__startswith="Demo ")) == 109
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_demo_data_is_available_when_an_admin_has_no_profile():
|
|
||||||
user_model = get_user_model()
|
|
||||||
user_model.objects.create_superuser(username="admin", email="admin@example.test", password="password")
|
|
||||||
call_command("seed_demo_data")
|
|
||||||
|
|
||||||
gql_client = GraphQLTestClient(Client())
|
|
||||||
result = gql_client.query(
|
|
||||||
"""
|
|
||||||
{
|
|
||||||
allProducts(filters: {active: true}) { name }
|
|
||||||
allUsers { firstName lastName profile { balance } }
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not result.errors
|
|
||||||
assert result.data
|
|
||||||
assert len(result.data["allProducts"]) == 4
|
|
||||||
assert len(result.data["allUsers"]) == 3
|
|
||||||
|
|
||||||
ada = user_model.objects.get(username="demo-ada")
|
|
||||||
detail_result = gql_client.query(
|
|
||||||
"""
|
|
||||||
query UserDetail($userId: ID!) {
|
|
||||||
user(userId: $userId) {
|
|
||||||
firstName
|
|
||||||
profile {
|
|
||||||
balance
|
|
||||||
lastPurchaseDate
|
|
||||||
orderSumTotal
|
|
||||||
topMonths { period sum count }
|
|
||||||
productOrderStats { productName data { x y } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
variables={"userId": str(ada.pk)},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not detail_result.errors
|
|
||||||
assert detail_result.data["user"]["profile"]["topMonths"]
|
|
||||||
assert isinstance(detail_result.data["user"]["profile"]["topMonths"][0]["sum"], float)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from django.test import Client
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("path", ["/inventory", "/inventory/add", "/report"])
|
|
||||||
def test_inventory_pages_serve_the_spa_entry(client: Client, path: str):
|
|
||||||
response = client.get(path)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert b'<div id="root"></div>' in b"".join(response.streaming_content)
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
from bbs.utils import format_username
|
|
||||||
|
|
||||||
|
|
||||||
def test_format_username():
|
|
||||||
assert format_username("Ada", "Lovelace") == "adalovela"
|
|
||||||
|
|
||||||
|
|
||||||
def test_format_username_truncates_names():
|
|
||||||
assert format_username("Aleksandar", "Vukovic") == "aleksavukovi"
|
|
||||||
|
|
||||||
|
|
||||||
def test_format_username_removes_spaces():
|
|
||||||
assert format_username("Mary Jane", "van Dijk") == "maryjavandij"
|
|
||||||
|
|
||||||
|
|
||||||
def test_format_username_drops_non_ascii_characters():
|
|
||||||
assert format_username("Ångström", "Ø") == "ngstr"
|
|
||||||
|
|
||||||
|
|
||||||
def test_format_username_allows_empty_names():
|
|
||||||
assert format_username("", "") == ""
|
|
||||||
+8
-15
@@ -2,30 +2,23 @@ from django.conf import settings
|
|||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.views import LoginView, LogoutView
|
from django.contrib.auth.views import LoginView, LogoutView
|
||||||
from django.urls import path
|
from django.urls import path, re_path
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from strawberry.django.views import GraphQLView
|
from strawberry.django.views import GraphQLView
|
||||||
|
|
||||||
from bbs.api.schema import schema
|
from bbs.api.schema import schema
|
||||||
from bbs.views.spa import spa_index
|
from bbs.views.reports import inventory, inventory_add, report
|
||||||
|
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("inventory", spa_index, name="inventory"),
|
# TODO: migrate reports to SPA
|
||||||
path("inventory/", spa_index),
|
re_path(r"^inventory/$", inventory, name="inventory"),
|
||||||
path("inventory/add", spa_index, name="inventory-add"),
|
re_path(r"^inventory/add/$", inventory_add, name="inventory-add"),
|
||||||
path("inventory/add/", spa_index),
|
re_path(r"^report$", report, name="report"),
|
||||||
path("report", spa_index, name="report"),
|
|
||||||
path("report/", spa_index),
|
|
||||||
path("accounts/login/", LoginView.as_view(), name="django.contrib.auth.views.login"),
|
path("accounts/login/", LoginView.as_view(), name="django.contrib.auth.views.login"),
|
||||||
path(
|
path("accounts/logout/", LogoutView.as_view(), name="django.contrib.auth.views.logout"),
|
||||||
"accounts/logout/",
|
|
||||||
LogoutView.as_view(),
|
|
||||||
name="django.contrib.auth.views.logout",
|
|
||||||
),
|
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("graphql/", csrf_exempt(GraphQLView.as_view(schema=schema)), name="graphql"),
|
path("graphql/", GraphQLView.as_view(schema=schema), name="graphql"),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += static("/media", document_root=settings.MEDIA_ROOT)
|
urlpatterns += static("/media", document_root=settings.MEDIA_ROOT)
|
||||||
|
|||||||
+15
-15
@@ -1,9 +1,8 @@
|
|||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from bbs.forms import DateRangeForm, InventoryTransactionForm
|
from bbs.forms import DateRangeForm, InventoryTransactionForm
|
||||||
from bbs.models import InventoryTransaction, Order, Product, Transaction, UserProfile
|
from bbs.models import InventoryTransaction, Order, Product, Transaction, UserProfile
|
||||||
@@ -11,12 +10,12 @@ from bbs.models import InventoryTransaction, Order, Product, Transaction, UserPr
|
|||||||
|
|
||||||
def report(request):
|
def report(request):
|
||||||
# FIXME: split this up in smaller pieces
|
# FIXME: split this up in smaller pieces
|
||||||
datetime_now = timezone.localtime()
|
datetime_now = datetime.now()
|
||||||
start_time = timezone.localtime() - timedelta(days=30)
|
start_time = datetime.now() - timedelta(days=30)
|
||||||
# start of day, 1 month
|
# start of day, 1 month
|
||||||
start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0)
|
start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
# end of day
|
# end of day
|
||||||
end_time = timezone.localtime().replace(hour=23, minute=59, second=59, microsecond=999)
|
end_time = datetime.now().replace(hour=23, minute=59, second=59, microsecond=999)
|
||||||
|
|
||||||
if request.GET.get("start_time", False) or request.GET.get("end_time", False):
|
if request.GET.get("start_time", False) or request.GET.get("end_time", False):
|
||||||
form = DateRangeForm(request.GET)
|
form = DateRangeForm(request.GET)
|
||||||
@@ -51,14 +50,14 @@ def report(request):
|
|||||||
k: {
|
k: {
|
||||||
"name": k,
|
"name": k,
|
||||||
"transactions": group,
|
"transactions": group,
|
||||||
"transactions_sum": sum(x["transactions__price"] for x in group),
|
"transactions_sum": sum(map(lambda x: x["transactions__price"], group)),
|
||||||
"transactions_units": sum(x["transactions__amount"] for x in group),
|
"transactions_units": sum(map(lambda x: x["transactions__amount"], group)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
itrans = InventoryTransaction.objects.filter(created__range=[start_time, end_time])
|
itrans = InventoryTransaction.objects.filter(created__range=[start_time, end_time])
|
||||||
inv_in = sum(x[1].get("transactions_sum", 0) for x in inventory_products.items())
|
inv_in = sum(map(lambda x: x[1].get("transactions_sum", 0), inventory_products.items()))
|
||||||
|
|
||||||
# out
|
# out
|
||||||
out_products = (
|
out_products = (
|
||||||
@@ -76,8 +75,8 @@ def report(request):
|
|||||||
product = {
|
product = {
|
||||||
"name": k,
|
"name": k,
|
||||||
"orderlines": group,
|
"orderlines": group,
|
||||||
"orderlines_sum": sum(x["orderlines__price"] for x in group),
|
"orderlines_sum": sum(map(lambda x: x["orderlines__price"], group)),
|
||||||
"orderlines_units": sum(x["orderlines__amount"] for x in group),
|
"orderlines_units": sum(map(lambda x: x["orderlines__amount"], group)),
|
||||||
}
|
}
|
||||||
if k in inventory_products:
|
if k in inventory_products:
|
||||||
# add diff
|
# add diff
|
||||||
@@ -89,7 +88,7 @@ def report(request):
|
|||||||
p[1]["value_diff"] = p[1].get("transactions_sum", 0) - p[1].get("orderlines_sum", 0)
|
p[1]["value_diff"] = p[1].get("transactions_sum", 0) - p[1].get("orderlines_sum", 0)
|
||||||
p[1]["units_diff"] = p[1].get("transactions_units", 0) - p[1].get("orderlines_units", 0)
|
p[1]["units_diff"] = p[1].get("transactions_units", 0) - p[1].get("orderlines_units", 0)
|
||||||
|
|
||||||
inv_out = sum(x[1].get("orderlines_sum", 0) for x in inventory_products.items())
|
inv_out = sum(map(lambda x: x[1].get("orderlines_sum", 0), inventory_products.items()))
|
||||||
|
|
||||||
inv_diff = inv_in - inv_out
|
inv_diff = inv_in - inv_out
|
||||||
|
|
||||||
@@ -111,7 +110,7 @@ def report(request):
|
|||||||
purchases_diff = purchases_end - purchases_start
|
purchases_diff = purchases_end - purchases_start
|
||||||
saldo_diff = saldo_end - saldo_start
|
saldo_diff = saldo_end - saldo_start
|
||||||
|
|
||||||
return render(request, "report.html", locals()) # noqa: DJ003
|
return render(request, "report.html", locals())
|
||||||
|
|
||||||
|
|
||||||
def inventory(request):
|
def inventory(request):
|
||||||
@@ -121,7 +120,7 @@ def inventory(request):
|
|||||||
wholesale_value = sum([p.wholesale_value for p in products_active])
|
wholesale_value = sum([p.wholesale_value for p in products_active])
|
||||||
num_products = sum(products_active.values_list("inventory_amount", flat=True))
|
num_products = sum(products_active.values_list("inventory_amount", flat=True))
|
||||||
|
|
||||||
return render(request, "inventory.html", locals()) # noqa: DJ003
|
return render(request, "inventory.html", locals())
|
||||||
|
|
||||||
|
|
||||||
def inventory_add(request):
|
def inventory_add(request):
|
||||||
@@ -134,12 +133,13 @@ def inventory_add(request):
|
|||||||
itrans.user = request.user
|
itrans.user = request.user
|
||||||
itrans.product.save()
|
itrans.product.save()
|
||||||
itrans.save()
|
itrans.save()
|
||||||
messages.success(request, f"Satt inn {itrans.amount} stk {itrans.product}.")
|
messages.success(request, "Satt inn {0} stk {1}.".format(itrans.amount, itrans.product))
|
||||||
return redirect("inventory-add")
|
return redirect("inventory-add")
|
||||||
|
|
||||||
|
else:
|
||||||
messages.error(request, "Feil med skjemaet, se under.")
|
messages.error(request, "Feil med skjemaet, se under.")
|
||||||
form = InventoryTransactionForm(data=request.POST)
|
form = InventoryTransactionForm(data=request.POST)
|
||||||
else:
|
else:
|
||||||
form = InventoryTransactionForm()
|
form = InventoryTransactionForm()
|
||||||
|
|
||||||
return render(request, "inventory_add.html", locals()) # noqa: DJ003
|
return render(request, "inventory_add.html", locals())
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from django.contrib.staticfiles import finders
|
|
||||||
from django.http import FileResponse, Http404
|
|
||||||
|
|
||||||
|
|
||||||
def spa_index(_request):
|
|
||||||
index_path = finders.find("index.html")
|
|
||||||
if not index_path:
|
|
||||||
raise Http404("The frontend has not been built.")
|
|
||||||
return FileResponse(Path(index_path).open("rb"), content_type="text/html")
|
|
||||||
+3
-5
@@ -1,8 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
source "$HOME/.local/bin/env"
|
|
||||||
|
|
||||||
export VITE_API_URL="https://bbs.neuf.no/api"
|
export VITE_API_URL="https://bbs.neuf.no/api"
|
||||||
PROJECT_PATH=/var/www/neuf.no/bbs
|
PROJECT_PATH=/var/www/neuf.no/bbs
|
||||||
PROCESS_NAME="bbs.neuf.no"
|
PROCESS_NAME="bbs.neuf.no"
|
||||||
@@ -12,9 +10,9 @@ cd "$PROJECT_PATH" || exit 1
|
|||||||
git pull
|
git pull
|
||||||
|
|
||||||
# Backend
|
# Backend
|
||||||
uv sync
|
poetry install
|
||||||
umask 022; uv run manage.py collectstatic --noinput # Collect static
|
umask 022; poetry run python manage.py collectstatic --noinput # Collect static
|
||||||
uv run manage.py migrate # Run DB migrations
|
poetry run python manage.py migrate # Run DB migrations
|
||||||
|
|
||||||
# Frontend
|
# Frontend
|
||||||
bin/build-frontend
|
bin/build-frontend
|
||||||
|
|||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SRC_DIR="bbs"
|
||||||
|
|
||||||
|
black $SRC_DIR
|
||||||
|
isort $SRC_DIR
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SRC_DIR="bbs"
|
||||||
|
|
||||||
|
black --check $SRC_DIR
|
||||||
|
isort --diff $SRC_DIR
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
uvx pre-commit run --all-files
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
exec python manage.py runserver 127.0.0.1:3000
|
python manage.py runserver
|
||||||
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cd frontend && npm run start -- --host 127.0.0.1
|
cd frontend && npm run start
|
||||||
|
|
||||||
@@ -19,7 +19,6 @@ 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",
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
v20
|
v16
|
||||||
+2
-24
@@ -3,28 +3,6 @@ documents: ./src/**/*.graphql
|
|||||||
generates:
|
generates:
|
||||||
./src/types.ts:
|
./src/types.ts:
|
||||||
plugins:
|
plugins:
|
||||||
- typescript:
|
- typescript
|
||||||
typesPrefix: Schema
|
- typescript-operations
|
||||||
scalars:
|
|
||||||
DateTime:
|
|
||||||
input: string
|
|
||||||
output: string
|
|
||||||
Decimal:
|
|
||||||
input: string
|
|
||||||
output: string
|
|
||||||
JSON:
|
|
||||||
input: unknown
|
|
||||||
output: "Array<{ name: string; count: number }>"
|
|
||||||
- typescript-operations:
|
|
||||||
nonOptionalTypename: true
|
|
||||||
scalars:
|
|
||||||
DateTime:
|
|
||||||
input: string
|
|
||||||
output: string
|
|
||||||
Decimal:
|
|
||||||
input: string
|
|
||||||
output: string
|
|
||||||
JSON:
|
|
||||||
input: unknown
|
|
||||||
output: "Array<{ name: string; count: number }>"
|
|
||||||
- typed-document-node
|
- typed-document-node
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/src/assets/brick.svg" />
|
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap" rel="stylesheet">
|
||||||
|
|||||||
Generated
+16675
-5288
File diff suppressed because it is too large.
Load diff
+37
-31
@@ -7,54 +7,60 @@
|
|||||||
"check-types": "tsc",
|
"check-types": "tsc",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"format:js": "eslint --fix src",
|
"format:js": "eslint --fix src",
|
||||||
"format:styles": "prettier --write \"src/**/*.css\"",
|
"format:styles": "stylelint --fix src",
|
||||||
"generate": "node scripts/generate-types.mjs",
|
"generate": "graphql-codegen",
|
||||||
"lint:js": "eslint src",
|
"lint:js": "eslint src",
|
||||||
"lint:styles": "prettier --check \"src/**/*.css\"",
|
"lint:styles": "stylelint src",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"test": "node --test"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.14.1",
|
"@apollo/client": "^3.5.10",
|
||||||
"@nivo/bar": "^0.99.0",
|
"@nivo/bar": "^0.79.1",
|
||||||
"@nivo/line": "^0.99.0",
|
"@nivo/line": "^0.79.1",
|
||||||
"@nivo/pie": "^0.99.0",
|
"@nivo/pie": "^0.79.1",
|
||||||
"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": "^18.3.1",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^17.0.2",
|
||||||
"react-hook-form": "^7.30.0",
|
"react-hook-form": "^7.30.0",
|
||||||
"react-router-dom": "^7.18.4",
|
"react-router-dom": "^6.3.0",
|
||||||
"use-url-search-params": "^2.5.1"
|
"use-url-search-params": "^2.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/core": "^6.2.2",
|
"@graphql-codegen/cli": "^2.6.2",
|
||||||
"@graphql-codegen/typescript": "^6.1.2",
|
"@graphql-codegen/typed-document-node": "^2.2.8",
|
||||||
"@graphql-codegen/typed-document-node": "^7.2.0",
|
"@graphql-codegen/typescript-operations": "^2.3.5",
|
||||||
"@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": "^18.3.28",
|
"@types/react": "^17.0.44",
|
||||||
"@types/react-dom": "^18.3.7",
|
"@types/react-dom": "^17.0.15",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.72.0",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@typescript-eslint/parser": "^8.72.0",
|
"@typescript-eslint/eslint-plugin": "^5.19.0",
|
||||||
"eslint": "^8.57.1",
|
"@typescript-eslint/parser": "^5.19.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"@vitejs/plugin-react": "^1.3.1",
|
||||||
|
"@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": "^5.5.7",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"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",
|
||||||
"prettier": "^3.6.2",
|
"jest": "^27.5.1",
|
||||||
"typescript": "^5.9.3",
|
"prettier": "^2.6.2",
|
||||||
"vite": "^6.4.5",
|
"stylelint": "^14.7.0",
|
||||||
"vite-plugin-svgr": "^5.2.0",
|
"stylelint-config-idiomatic-order": "^8.1.0",
|
||||||
"yaml": "^2.8.1"
|
"stylelint-config-prettier": "^9.0.3",
|
||||||
|
"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": ">=16",
|
||||||
},
|
"npm": ">=7"
|
||||||
"overrides": {
|
|
||||||
"@whatwg-node/promise-helpers": "^1.3.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
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,5 +1,4 @@
|
|||||||
.app {
|
.app {
|
||||||
flex: 1 0 auto;
|
|
||||||
width: min(100% - 2rem, 1220px);
|
|
||||||
margin: 1rem auto;
|
margin: 1rem auto;
|
||||||
|
max-width: 1220px;
|
||||||
}
|
}
|
||||||
@@ -4,15 +4,11 @@ import { Route, Routes as ReactRouterRoutes } from "react-router-dom";
|
|||||||
const Splash = React.lazy(() => import("./components/Splash"));
|
const Splash = React.lazy(() => import("./components/Splash"));
|
||||||
const UserCreate = React.lazy(() => import("./features/auth/UserCreate"));
|
const UserCreate = React.lazy(() => import("./features/auth/UserCreate"));
|
||||||
const Deposit = React.lazy(() => import("./features/deposit/Deposit"));
|
const Deposit = React.lazy(() => import("./features/deposit/Deposit"));
|
||||||
const Inventory = React.lazy(() => import("./features/inventory/Inventory"));
|
|
||||||
const InventoryAdd = React.lazy(() => import("./features/inventory/InventoryAdd"));
|
|
||||||
const InventoryReport = React.lazy(() => import("./features/inventory/InventoryReport"));
|
|
||||||
const Log = React.lazy(() => import("./features/log/Log"));
|
const Log = React.lazy(() => import("./features/log/Log"));
|
||||||
const Register = React.lazy(() => import("./features/register/Register"));
|
const Register = React.lazy(() => import("./features/register/Register"));
|
||||||
const OrderStats = React.lazy(() => import("./features/stats/OrderStats"));
|
const OrderStats = React.lazy(() => import("./features/stats/OrderStats"));
|
||||||
const ProductStats = React.lazy(() => import("./features/stats/ProductStats"));
|
const ProductStats = React.lazy(() => import("./features/stats/ProductStats"));
|
||||||
const Stats = React.lazy(() => import("./features/stats/Stats"));
|
const Stats = React.lazy(() => import("./features/stats/Stats"));
|
||||||
const UserStats = React.lazy(() => import("./features/stats/UserStats"));
|
|
||||||
const UserDetail = React.lazy(() => import("./features/users/UserDetail"));
|
const UserDetail = React.lazy(() => import("./features/users/UserDetail"));
|
||||||
const UserList = React.lazy(() => import("./features/users/UserList"));
|
const UserList = React.lazy(() => import("./features/users/UserList"));
|
||||||
|
|
||||||
@@ -20,14 +16,10 @@ export const Routes = (): JSX.Element => (
|
|||||||
<React.Suspense fallback={<div>Loading...</div>}>
|
<React.Suspense fallback={<div>Loading...</div>}>
|
||||||
<ReactRouterRoutes>
|
<ReactRouterRoutes>
|
||||||
<Route path="/deposit" element={<Deposit />} />
|
<Route path="/deposit" element={<Deposit />} />
|
||||||
<Route path="/inventory" element={<Inventory />} />
|
|
||||||
<Route path="/inventory/add" element={<InventoryAdd />} />
|
|
||||||
<Route path="/report" element={<InventoryReport />} />
|
|
||||||
<Route path="/log" element={<Log />} />
|
<Route path="/log" element={<Log />} />
|
||||||
<Route path="/stats" element={<Stats />} />
|
<Route path="/stats" element={<Stats />} />
|
||||||
<Route path="/stats/orders" element={<OrderStats />} />
|
<Route path="/stats/orders" element={<OrderStats />} />
|
||||||
<Route path="/stats/products" element={<ProductStats />} />
|
<Route path="/stats/products" element={<ProductStats />} />
|
||||||
<Route path="/stats/users" element={<UserStats />} />
|
|
||||||
<Route path="/users" element={<UserList />} />
|
<Route path="/users" element={<UserList />} />
|
||||||
<Route path="/users/create" element={<UserCreate />} />
|
<Route path="/users/create" element={<UserCreate />} />
|
||||||
<Route path="/user/:userId" element={<UserDetail />} />
|
<Route path="/user/:userId" element={<UserDetail />} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ApolloClient, InMemoryCache } from "@apollo/client";
|
import { ApolloClient, InMemoryCache } from "@apollo/client";
|
||||||
|
|
||||||
const client = new ApolloClient({
|
const client = new ApolloClient({
|
||||||
uri: import.meta.env.DEV ? "/graphql/" : `${import.meta.env.VITE_API_URL}/graphql/`,
|
uri: `${import.meta.env.VITE_API_URL}/graphql/`,
|
||||||
cache: new InMemoryCache(),
|
cache: new InMemoryCache(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
.footer {
|
.footer {
|
||||||
flex-shrink: 0;
|
position: absolute;
|
||||||
padding: 0 0 1rem;
|
right: 0;
|
||||||
|
bottom: 1rem;
|
||||||
|
left: 0;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
color: rgba(255, 255, 255, 0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import Brick from "../assets/brick.svg?react";
|
import { ReactComponent as Brick } from "../assets/brick.svg";
|
||||||
import styles from "./Footer.module.css";
|
import styles from "./Footer.module.css";
|
||||||
|
|
||||||
export const Footer = (): JSX.Element => {
|
export const Footer = (): JSX.Element => {
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
.errorMessage {
|
.errorMessage {
|
||||||
width: 100%;
|
font-family: Rocket Rinder, monospace;
|
||||||
margin-top: 30vh;
|
|
||||||
color: var(--brand);
|
|
||||||
font-family:
|
|
||||||
Rocket Rinder,
|
|
||||||
monospace;
|
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
color: var(--brand);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 30vh;
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
.header {
|
.header {
|
||||||
width: min(100% - 2rem, 1220px);
|
|
||||||
max-width: 1220px;
|
|
||||||
margin: 1rem auto;
|
margin: 1rem auto;
|
||||||
|
max-width: 1220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 5rem;
|
height: 5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem 1rem;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,21 +17,14 @@
|
|||||||
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:
|
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);
|
||||||
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:
|
box-shadow: 0 0 4px var(--brand), 0 0 11px var(--brand), 0 0 19px var(--brand), 0 0 30px var(--brand);
|
||||||
0 0 4px var(--brand),
|
|
||||||
0 0 11px var(--brand),
|
|
||||||
0 0 19px var(--brand),
|
|
||||||
0 0 30px var(--brand);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.logoNav {
|
.logoNav {
|
||||||
@@ -44,8 +34,6 @@
|
|||||||
.navItems {
|
.navItems {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 1;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@@ -63,47 +51,16 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.nav {
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logoNav {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navItems {
|
|
||||||
justify-content: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navLink {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.5rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand {
|
|
||||||
padding-left: 0;
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FIXME: Duplicates Splash styles */
|
/* FIXME: Duplicates Splash styles */
|
||||||
.brand {
|
.brand {
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
color: var(--brand);
|
color: var(--brand);
|
||||||
font-family:
|
font-family: Rocket Rinder, monospace;
|
||||||
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:
|
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);
|
||||||
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 {
|
||||||
@@ -111,9 +68,7 @@
|
|||||||
left: -8px;
|
left: -8px;
|
||||||
animation: flicker 1.5s infinite alternate;
|
animation: flicker 1.5s infinite alternate;
|
||||||
color: var(--interlace);
|
color: var(--interlace);
|
||||||
font-family:
|
font-family: Rocket Clouds DEMO, serif;
|
||||||
Rocket Clouds DEMO,
|
|
||||||
serif;
|
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -128,10 +83,7 @@
|
|||||||
53%,
|
53%,
|
||||||
57%,
|
57%,
|
||||||
100% {
|
100% {
|
||||||
text-shadow:
|
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);
|
||||||
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%,
|
||||||
|
|||||||
@@ -32,16 +32,6 @@ export const Header = (): JSX.Element => {
|
|||||||
Logg
|
Logg
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<NavLink to="/inventory" className={navLinkStyles}>
|
|
||||||
Varebeholdning
|
|
||||||
</NavLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<NavLink to="/report" className={navLinkStyles}>
|
|
||||||
Rapport
|
|
||||||
</NavLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<NavLink to="/stats" className={navLinkStyles}>
|
<NavLink to="/stats" className={navLinkStyles}>
|
||||||
Statistikk
|
Statistikk
|
||||||
|
|||||||
@@ -18,31 +18,22 @@
|
|||||||
.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%);
|
||||||
box-shadow:
|
border-radius: 50% 50%;
|
||||||
0 0 4px var(--sun),
|
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun), 0 0 30px 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:
|
font-family: Rocket Rinder, monospace;
|
||||||
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:
|
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);
|
||||||
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 {
|
||||||
@@ -51,9 +42,7 @@
|
|||||||
left: 0rem;
|
left: 0rem;
|
||||||
animation: flicker 1.5s infinite alternate;
|
animation: flicker 1.5s infinite alternate;
|
||||||
color: var(--interlace);
|
color: var(--interlace);
|
||||||
font-family:
|
font-family: Rocket Clouds DEMO, serif;
|
||||||
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;
|
||||||
@@ -68,10 +57,7 @@
|
|||||||
53%,
|
53%,
|
||||||
57%,
|
57%,
|
||||||
100% {
|
100% {
|
||||||
text-shadow:
|
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);
|
||||||
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%,
|
||||||
|
|||||||
@@ -16,17 +16,11 @@
|
|||||||
|
|
||||||
.primary {
|
.primary {
|
||||||
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
|
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
|
||||||
box-shadow:
|
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
|
||||||
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:
|
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);
|
||||||
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 {
|
||||||
|
|||||||
@@ -1,47 +1,33 @@
|
|||||||
.decoration {
|
|
||||||
position: absolute;
|
|
||||||
z-index: -1;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: clip;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wireframe {
|
.wireframe {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
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;
|
||||||
animation-duration: 10s;
|
display: grid;
|
||||||
|
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;
|
||||||
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell {
|
.cell {
|
||||||
border-right: 2px solid var(--sun);
|
|
||||||
border-bottom: 2px solid var(--sun);
|
border-bottom: 2px solid var(--sun);
|
||||||
|
border-right: 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.decoration {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes isometric-rotate {
|
@keyframes isometric-rotate {
|
||||||
0% {
|
0% {
|
||||||
transform: rotateX(70deg) rotateY(0deg) rotateZ(-45deg);
|
transform: rotateX(70deg) rotateY(0deg) rotateZ(-45deg);
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ import React from "react";
|
|||||||
import styles from "./Wireframe.module.css";
|
import styles from "./Wireframe.module.css";
|
||||||
|
|
||||||
export const Wireframe = () => (
|
export const Wireframe = () => (
|
||||||
<div className={styles.decoration}>
|
|
||||||
<div className={styles.wireframe}>
|
<div className={styles.wireframe}>
|
||||||
{[...Array(64)].map((_, idx) => (
|
{[...Array(64)].map((_, idx) => (
|
||||||
<div key={idx} className={styles.cell} />
|
<div key={idx} className={styles.cell} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
@@ -3,8 +3,3 @@
|
|||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form input {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
@@ -1,113 +1,17 @@
|
|||||||
.deposit {
|
|
||||||
display: grid;
|
|
||||||
align-items: start;
|
|
||||||
gap: 1rem 2rem;
|
|
||||||
grid-template:
|
|
||||||
"heading amount" auto
|
|
||||||
"search amount" auto
|
|
||||||
"users amount" 1fr
|
|
||||||
/ minmax(0, 1fr) minmax(260px, 320px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
grid-area: heading;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
grid-area: search;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search input {
|
|
||||||
width: min(100%, 420px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.userList {
|
.userList {
|
||||||
display: grid;
|
display: grid;
|
||||||
max-height: min(62vh, 680px);
|
height: 800px;
|
||||||
padding: 0.5rem 0.5rem 0.5rem 0;
|
padding-top: 2rem;
|
||||||
gap: 1rem;
|
grid-template-columns: repeat(5, 220px);
|
||||||
grid-area: users;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
column-gap: 16px;
|
||||||
|
row-gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.amountSection {
|
.amountSection {
|
||||||
position: sticky;
|
margin-top: 32px;
|
||||||
top: 1rem;
|
|
||||||
padding: 1.25rem;
|
|
||||||
border: 1px solid rgba(221, 81, 127, 0.45);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: rgba(35, 15, 41, 0.84);
|
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.24);
|
|
||||||
grid-area: amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
.amountSection h2 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectedUser {
|
|
||||||
min-height: 1.5em;
|
|
||||||
margin: 0 0 1.25rem;
|
|
||||||
color: rgba(255, 255, 255, 0.78);
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectedUser strong {
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.amountLabel {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
color: var(--sun);
|
|
||||||
}
|
|
||||||
|
|
||||||
.amountInput {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.amountInput input {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
font-size: 1.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.amountInput span {
|
|
||||||
color: rgba(255, 255, 255, 0.8);
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout {
|
.checkout {
|
||||||
display: flex;
|
margin-top: 32px;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.75rem;
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
|
||||||
.deposit {
|
|
||||||
gap: 0.75rem;
|
|
||||||
grid-template:
|
|
||||||
"heading" auto
|
|
||||||
"search" auto
|
|
||||||
"amount" auto
|
|
||||||
"users" auto
|
|
||||||
/ minmax(0, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.amountSection {
|
|
||||||
position: static;
|
|
||||||
}
|
|
||||||
|
|
||||||
.userList {
|
|
||||||
max-height: 45vh;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -8,13 +8,12 @@ const Deposit = (): JSX.Element => {
|
|||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
selectedUser,
|
selectedUser,
|
||||||
toggleSelectedUser,
|
setSelectedUser,
|
||||||
amount,
|
amount,
|
||||||
setAmount,
|
setAmount,
|
||||||
userQuery,
|
userQuery,
|
||||||
setUserQuery,
|
setUserQuery,
|
||||||
filteredUsers,
|
filteredUsers,
|
||||||
selectedUserDetails,
|
|
||||||
reset,
|
reset,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
} = useDeposit();
|
} = useDeposit();
|
||||||
@@ -24,67 +23,45 @@ const Deposit = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.deposit}>
|
<div>
|
||||||
<h2 className={styles.heading}>Hvem skal sette inn penger?</h2>
|
<section>
|
||||||
<div className={styles.search}>
|
<h2>Hvem skal sette inn penger?</h2>
|
||||||
<input
|
<input
|
||||||
name="userQuery"
|
name="userQuery"
|
||||||
onChange={(event: React.FormEvent<HTMLInputElement>) => setUserQuery(event.currentTarget.value)}
|
onChange={(event: React.FormEvent<HTMLInputElement>) => setUserQuery(event.currentTarget.value)}
|
||||||
value={userQuery}
|
value={userQuery}
|
||||||
placeholder="Filtrér på navn"
|
placeholder="Filtrér på navn"
|
||||||
aria-label="Filtrér personer på navn"
|
|
||||||
/>
|
/>
|
||||||
{userQuery !== "" && (
|
{userQuery !== "" && (
|
||||||
<button type="button" onClick={() => setUserQuery("")}>
|
<button type="button" onClick={() => setUserQuery("")}>
|
||||||
Vis alle
|
Vis alle
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<section className={styles.amountSection} aria-label="Innskudd">
|
|
||||||
<h2>Hvor mye?</h2>
|
|
||||||
{selectedUserDetails ? (
|
|
||||||
<p className={styles.selectedUser}>
|
|
||||||
Innskudd til{" "}
|
|
||||||
<strong>
|
|
||||||
{selectedUserDetails.firstName} {selectedUserDetails.lastName}
|
|
||||||
</strong>
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p className={styles.selectedUser}>Velg en person fra listen</p>
|
|
||||||
)}
|
|
||||||
<label className={styles.amountLabel} htmlFor="deposit-amount">
|
|
||||||
Beløp
|
|
||||||
</label>
|
|
||||||
<div className={styles.amountInput}>
|
|
||||||
<input
|
|
||||||
id="deposit-amount"
|
|
||||||
name="amount"
|
|
||||||
inputMode="decimal"
|
|
||||||
value={amount}
|
|
||||||
onChange={(event) => setAmount(event.target.value)}
|
|
||||||
placeholder="0"
|
|
||||||
/>
|
|
||||||
<span>kr</span>
|
|
||||||
</div>
|
|
||||||
<div className={styles.checkout}>
|
|
||||||
<button type="button" onClick={onSubmit} className="primary">
|
|
||||||
Sett inn
|
|
||||||
</button>
|
|
||||||
<button type="button" onClick={reset}>
|
|
||||||
Start på nytt
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<div className={styles.userList}>
|
<div className={styles.userList}>
|
||||||
{filteredUsers.map((user) => (
|
{filteredUsers.map((user) => (
|
||||||
<UserCard
|
<UserCard
|
||||||
key={user.id}
|
key={user.id}
|
||||||
active={selectedUser === user.id}
|
active={selectedUser === user.id}
|
||||||
onUser={() => toggleSelectedUser(user.id)}
|
onUser={() => {
|
||||||
|
setSelectedUser(user.id);
|
||||||
|
}}
|
||||||
user={user}
|
user={user}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className={styles.amountSection}>
|
||||||
|
<h2>Hvor mye?</h2>
|
||||||
|
<input name="amount" value={amount} onChange={(event) => setAmount(event.target.value)} placeholder="0" />
|
||||||
|
</section>
|
||||||
|
<section className={styles.checkout}>
|
||||||
|
<button type="button" onClick={onSubmit} className="primary">
|
||||||
|
Sett inn
|
||||||
|
</button>{" "}
|
||||||
|
<button type="button" onClick={reset}>
|
||||||
|
Start på nytt
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,13 +10,9 @@ export const useDeposit = () => {
|
|||||||
const [selectedUser, setSelectedUser] = useState("");
|
const [selectedUser, setSelectedUser] = useState("");
|
||||||
const [amount, setAmount] = useState("");
|
const [amount, setAmount] = useState("");
|
||||||
const [userQuery, setUserQuery] = useState("");
|
const [userQuery, setUserQuery] = useState("");
|
||||||
const toggleSelectedUser = (userId: string) => {
|
|
||||||
setSelectedUser((currentUserId) => (currentUserId === userId ? "" : userId));
|
|
||||||
};
|
|
||||||
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 selectedUserDetails = data?.allUsers.find((user) => user.id === selectedUser);
|
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
@@ -34,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;
|
||||||
@@ -46,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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -59,13 +55,12 @@ export const useDeposit = () => {
|
|||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
selectedUser,
|
selectedUser,
|
||||||
toggleSelectedUser,
|
setSelectedUser,
|
||||||
amount,
|
amount,
|
||||||
setAmount,
|
setAmount,
|
||||||
userQuery,
|
userQuery,
|
||||||
setUserQuery,
|
setUserQuery,
|
||||||
filteredUsers,
|
filteredUsers,
|
||||||
selectedUserDetails,
|
|
||||||
reset,
|
reset,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
.section {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableWrap {
|
|
||||||
max-width: 100%;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableWrap table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inactive {
|
|
||||||
opacity: 0.65;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transactions {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.75rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form {
|
|
||||||
display: grid;
|
|
||||||
max-width: 36rem;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form label {
|
|
||||||
display: grid;
|
|
||||||
gap: 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form textarea,
|
|
||||||
.form select {
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 4px;
|
|
||||||
background: rgb(35, 15, 41);
|
|
||||||
color: var(--text);
|
|
||||||
font: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form textarea {
|
|
||||||
min-height: 6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 1rem 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: var(--sun);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.tableWrap table {
|
|
||||||
min-width: 42rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
import { GQLError } from "../../components/GQLError";
|
|
||||||
import { InventoryPageDocument } from "../../types";
|
|
||||||
import { formatCurrency } from "../../utils/currency";
|
|
||||||
import { formatTime } from "../../utils/time";
|
|
||||||
import styles from "./Inventory.module.css";
|
|
||||||
|
|
||||||
const PAGE_SIZE = 20;
|
|
||||||
|
|
||||||
const Inventory = () => {
|
|
||||||
const [offset, setOffset] = useState(0);
|
|
||||||
const { data, loading, error } = useQuery(InventoryPageDocument, {
|
|
||||||
variables: { limit: PAGE_SIZE, offset },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (loading) return <div>Loading...</div>;
|
|
||||||
if (error) return <GQLError error={error} />;
|
|
||||||
|
|
||||||
const products = [...(data?.allProducts || [])].sort(
|
|
||||||
(a, b) =>
|
|
||||||
Number(b.active) - Number(a.active) || a.inventoryAmount - b.inventoryAmount || a.name.localeCompare(b.name),
|
|
||||||
);
|
|
||||||
const activeProducts = products.filter((product) => product.active);
|
|
||||||
const transactions = data?.inventoryTransactionList || [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>Varebeholdning</h2>
|
|
||||||
<div className={styles.summary}>
|
|
||||||
<div>Antall aktive varer: {activeProducts.reduce((total, product) => total + product.inventoryAmount, 0)}</div>
|
|
||||||
<div>
|
|
||||||
Innkjøpsverdi:{" "}
|
|
||||||
{formatCurrency(activeProducts.reduce((total, product) => total + Number(product.wholesaleValue), 0))} kr
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<section className={styles.section}>
|
|
||||||
<div className={styles.summary}>
|
|
||||||
<h3>Varer</h3>
|
|
||||||
<Link className="btn-primary" to="/inventory/add">
|
|
||||||
Registrer varetransaksjon
|
|
||||||
</Link>
|
|
||||||
<Link to="/report">Åpne rapport</Link>
|
|
||||||
</div>
|
|
||||||
<div className={styles.tableWrap}>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Vare</th>
|
|
||||||
<th scope="col">Beholdning</th>
|
|
||||||
<th scope="col">Innkjøpspris</th>
|
|
||||||
<th scope="col">Verdi</th>
|
|
||||||
<th scope="col">I salg</th>
|
|
||||||
<th scope="col">Sist oppdatert</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{products.map((product) => (
|
|
||||||
<tr key={product.id} className={!product.active ? styles.inactive : undefined}>
|
|
||||||
<td>{product.name}</td>
|
|
||||||
<td>{product.inventoryAmount}</td>
|
|
||||||
<td>{formatCurrency(Number(product.wholesaleUnitPrice))} kr</td>
|
|
||||||
<td>{formatCurrency(Number(product.wholesaleValue))} kr</td>
|
|
||||||
<td>{product.active ? "Ja" : "Nei"}</td>
|
|
||||||
<td>{formatTime(product.updated)}</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className={styles.transactions}>
|
|
||||||
<h3>Varetransaksjoner</h3>
|
|
||||||
<div className={styles.tableWrap}>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Når</th>
|
|
||||||
<th scope="col">Vare</th>
|
|
||||||
<th scope="col">Antall</th>
|
|
||||||
<th scope="col">Enhetspris</th>
|
|
||||||
<th scope="col">Hvem</th>
|
|
||||||
<th scope="col">Kommentar</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{transactions.map((transaction) => (
|
|
||||||
<tr key={transaction.id}>
|
|
||||||
<td>{formatTime(transaction.created)}</td>
|
|
||||||
<td>{transaction.product.name}</td>
|
|
||||||
<td>{transaction.amount}</td>
|
|
||||||
<td>{formatCurrency(Number(transaction.unitPrice))} kr</td>
|
|
||||||
<td>
|
|
||||||
{transaction.user.firstName} {transaction.user.lastName}
|
|
||||||
</td>
|
|
||||||
<td>{transaction.comment}</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
{!transactions.length && (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={6}>Ingen varetransaksjoner ennå.</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div className={styles.pagination}>
|
|
||||||
<button type="button" disabled={offset === 0} onClick={() => setOffset(Math.max(0, offset - PAGE_SIZE))}>
|
|
||||||
Forrige
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled={transactions.length < PAGE_SIZE}
|
|
||||||
onClick={() => setOffset(offset + PAGE_SIZE)}
|
|
||||||
>
|
|
||||||
Neste
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Inventory;
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
import { useMutation, useQuery } from "@apollo/client";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
import { GQLError } from "../../components/GQLError";
|
|
||||||
import { useToast } from "../../components/ToastProvider";
|
|
||||||
import { CreateInventoryTransactionDocument, InventoryProductsDocument } from "../../types";
|
|
||||||
import styles from "./Inventory.module.css";
|
|
||||||
|
|
||||||
const InventoryAdd = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { showToast } = useToast();
|
|
||||||
const { data, loading, error } = useQuery(InventoryProductsDocument);
|
|
||||||
const [createInventoryTransaction, { loading: saving }] = useMutation(CreateInventoryTransactionDocument);
|
|
||||||
const [productId, setProductId] = useState("");
|
|
||||||
const [amount, setAmount] = useState("");
|
|
||||||
const [unitPrice, setUnitPrice] = useState("");
|
|
||||||
const [comment, setComment] = useState("");
|
|
||||||
const products = data?.allProducts || [];
|
|
||||||
|
|
||||||
if (loading) return <div>Loading...</div>;
|
|
||||||
if (error) return <GQLError error={error} />;
|
|
||||||
|
|
||||||
const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
try {
|
|
||||||
const result = await createInventoryTransaction({
|
|
||||||
variables: {
|
|
||||||
data: {
|
|
||||||
productId,
|
|
||||||
amount: Number(amount),
|
|
||||||
unitPrice,
|
|
||||||
comment,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const response = result.data?.createInventoryTransaction;
|
|
||||||
if (response?.__typename === "CreateInventoryTransactionSuccess") {
|
|
||||||
showToast(`Registrerte ${amount} stk ${response.inventoryTransaction.product.name}.`);
|
|
||||||
navigate("/inventory");
|
|
||||||
} else if (response?.__typename === "FormErrors") {
|
|
||||||
showToast(response.message, "danger");
|
|
||||||
} else {
|
|
||||||
showToast("Kunne ikke registrere varetransaksjonen.", "danger");
|
|
||||||
}
|
|
||||||
} catch (mutationError) {
|
|
||||||
showToast(
|
|
||||||
mutationError instanceof Error ? mutationError.message : "Kunne ikke registrere varetransaksjonen.",
|
|
||||||
"danger",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>Registrer varetransaksjon</h2>
|
|
||||||
<p>
|
|
||||||
Du må være innlogget for å registrere varer. <a href="/accounts/login/">Logg inn</a>.
|
|
||||||
</p>
|
|
||||||
<form className={styles.form} onSubmit={onSubmit}>
|
|
||||||
<label>
|
|
||||||
Vare
|
|
||||||
<select required value={productId} onChange={(event) => setProductId(event.target.value)}>
|
|
||||||
<option value="">Velg vare</option>
|
|
||||||
{products.map((product) => (
|
|
||||||
<option key={product.id} value={product.id}>
|
|
||||||
{product.name} ({product.inventoryAmount} på lager)
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Antall
|
|
||||||
<input required type="number" step="1" value={amount} onChange={(event) => setAmount(event.target.value)} />
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Innkjøpspris per enhet (kr)
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
step="0.1"
|
|
||||||
type="number"
|
|
||||||
value={unitPrice}
|
|
||||||
onChange={(event) => setUnitPrice(event.target.value)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Kommentar
|
|
||||||
<textarea value={comment} onChange={(event) => setComment(event.target.value)} />
|
|
||||||
</label>
|
|
||||||
<div className={styles.summary}>
|
|
||||||
<button type="submit" className="primary" disabled={saving || !products.length}>
|
|
||||||
{saving ? "Lagrer..." : "Registrer"}
|
|
||||||
</button>
|
|
||||||
<Link to="/inventory">Avbryt</Link>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{!products.length && <p>Ingen aktive varer å registrere.</p>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default InventoryAdd;
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import { format, subDays } from "date-fns";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
|
|
||||||
import { GQLError } from "../../components/GQLError";
|
|
||||||
import { InventoryReportDocument } from "../../types";
|
|
||||||
import { formatCurrency } from "../../utils/currency";
|
|
||||||
import styles from "./Inventory.module.css";
|
|
||||||
|
|
||||||
const today = () => format(new Date(), "yyyy-MM-dd");
|
|
||||||
const monthAgo = () => format(subDays(new Date(), 30), "yyyy-MM-dd");
|
|
||||||
const timestampAt = (date: string, endOfDay = false) => `${date}T${endOfDay ? "23:59:59.999999" : "00:00:00"}`;
|
|
||||||
|
|
||||||
const InventoryReport = () => {
|
|
||||||
const [startDate, setStartDate] = useState(monthAgo);
|
|
||||||
const [endDate, setEndDate] = useState(today);
|
|
||||||
const [appliedRange, setAppliedRange] = useState({ startDate: monthAgo(), endDate: today() });
|
|
||||||
const invalidRange = startDate > endDate;
|
|
||||||
const { data, loading, error } = useQuery(InventoryReportDocument, {
|
|
||||||
variables: {
|
|
||||||
startTime: timestampAt(appliedRange.startDate),
|
|
||||||
endTime: timestampAt(appliedRange.endDate, true),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (!invalidRange) setAppliedRange({ startDate, endDate });
|
|
||||||
};
|
|
||||||
|
|
||||||
if (error) return <GQLError error={error} />;
|
|
||||||
|
|
||||||
const report = data?.inventoryReport;
|
|
||||||
const balanceChange = report ? Number(report.balanceAtEnd) - Number(report.balanceAtStart) : 0;
|
|
||||||
const inventoryChange = report ? Number(report.inventoryPurchases) - Number(report.productSales) : 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>Varerapport</h2>
|
|
||||||
<form className={styles.summary} onSubmit={onSubmit}>
|
|
||||||
<label>
|
|
||||||
Fra
|
|
||||||
<input type="date" required value={startDate} onChange={(event) => setStartDate(event.target.value)} />
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Til
|
|
||||||
<input type="date" required value={endDate} onChange={(event) => setEndDate(event.target.value)} />
|
|
||||||
</label>
|
|
||||||
<button type="submit" disabled={invalidRange}>
|
|
||||||
Oppdater
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{invalidRange && <p className={styles.error}>Startdato må være før eller lik sluttdato.</p>}
|
|
||||||
{loading && <div>Loading...</div>}
|
|
||||||
{report && (
|
|
||||||
<>
|
|
||||||
<section className={styles.report}>
|
|
||||||
<h3>Beholdningsendring</h3>
|
|
||||||
<div className={styles.tableWrap}>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Vare</th>
|
|
||||||
<th scope="col">Innkjøp</th>
|
|
||||||
<th scope="col">Salg</th>
|
|
||||||
<th scope="col">Endring verdi</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{report.products.map((product) => (
|
|
||||||
<tr key={product.productId}>
|
|
||||||
<td>{product.productName}</td>
|
|
||||||
<td>
|
|
||||||
{formatCurrency(Number(product.purchaseValue))} kr ({product.purchaseUnits} stk)
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{formatCurrency(Number(product.salesValue))} kr ({product.salesUnits} stk)
|
|
||||||
</td>
|
|
||||||
<td>{formatCurrency(Number(product.purchaseValue) - Number(product.salesValue))} kr</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
{!report.products.length && (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={4}>Ingen varetransaksjoner i perioden.</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Totalt</th>
|
|
||||||
<td>{formatCurrency(Number(report.inventoryPurchases))} kr</td>
|
|
||||||
<td>{formatCurrency(Number(report.productSales))} kr</td>
|
|
||||||
<td>{formatCurrency(inventoryChange)} kr</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className={styles.report}>
|
|
||||||
<h3>Innskudd og kjøp</h3>
|
|
||||||
<div className={styles.tableWrap}>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Post</th>
|
|
||||||
<th scope="col">Saldo</th>
|
|
||||||
<th scope="col">Innskudd</th>
|
|
||||||
<th scope="col">Kjøp</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Saldo ved periodestart</th>
|
|
||||||
<td>{formatCurrency(Number(report.balanceAtStart))} kr</td>
|
|
||||||
<td />
|
|
||||||
<td />
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Endring i perioden</th>
|
|
||||||
<td>{formatCurrency(balanceChange)} kr</td>
|
|
||||||
<td>{formatCurrency(Number(report.deposits))} kr</td>
|
|
||||||
<td>{formatCurrency(Number(report.orderPurchases))} kr</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Saldo ved periodeslutt</th>
|
|
||||||
<td>{formatCurrency(Number(report.balanceAtEnd))} kr</td>
|
|
||||||
<td />
|
|
||||||
<td />
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default InventoryReport;
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
mutation createInventoryTransaction($data: InventoryTransactionInput!) {
|
|
||||||
createInventoryTransaction(data: $data) {
|
|
||||||
... on CreateInventoryTransactionSuccess {
|
|
||||||
inventoryTransaction {
|
|
||||||
id
|
|
||||||
product {
|
|
||||||
name
|
|
||||||
inventoryAmount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
... on FormErrors {
|
|
||||||
message
|
|
||||||
fields {
|
|
||||||
field
|
|
||||||
message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
query inventoryPage($limit: Int = 20, $offset: Int = 0) {
|
|
||||||
allProducts {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
active
|
|
||||||
inventoryAmount
|
|
||||||
updated
|
|
||||||
wholesaleUnitPrice
|
|
||||||
wholesaleValue
|
|
||||||
}
|
|
||||||
inventoryTransactionList(ordering: [{ created: DESC }], pagination: { limit: $limit, offset: $offset }) {
|
|
||||||
id
|
|
||||||
created
|
|
||||||
amount
|
|
||||||
unitPrice
|
|
||||||
comment
|
|
||||||
product {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
user {
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
query inventoryProducts {
|
|
||||||
allProducts(filters: { active: true }) {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
inventoryAmount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
query inventoryReport($startTime: DateTime!, $endTime: DateTime!) {
|
|
||||||
inventoryReport(startTime: $startTime, endTime: $endTime) {
|
|
||||||
products {
|
|
||||||
productId
|
|
||||||
productName
|
|
||||||
purchaseUnits
|
|
||||||
purchaseValue
|
|
||||||
salesUnits
|
|
||||||
salesValue
|
|
||||||
}
|
|
||||||
inventoryPurchases
|
|
||||||
productSales
|
|
||||||
deposits
|
|
||||||
orderPurchases
|
|
||||||
balanceAtStart
|
|
||||||
balanceAtEnd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,7 @@
|
|||||||
.log {
|
.log {
|
||||||
display: grid;
|
display: grid;
|
||||||
min-width: 0;
|
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
|
||||||
|
|
||||||
.tableWrap {
|
|
||||||
max-width: 100%;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableWrap table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
.disabled,
|
.disabled,
|
||||||
.disabled a:link {
|
.disabled a:link {
|
||||||
@@ -30,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);
|
||||||
border-radius: 4px;
|
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.pagination a:hover {
|
.pagination a:hover {
|
||||||
color: white;
|
color: white;
|
||||||
@@ -40,20 +30,3 @@
|
|||||||
.disabled a:hover {
|
.disabled a:hover {
|
||||||
color: rgba(121, 152, 238, 0.5);
|
color: rgba(121, 152, 238, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
|
||||||
.log {
|
|
||||||
grid-template-columns: minmax(0, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.pagination {
|
|
||||||
gap: 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination a {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.6rem 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,6 @@ const Log = () => {
|
|||||||
<div className={styles.log}>
|
<div className={styles.log}>
|
||||||
<div>
|
<div>
|
||||||
<h2>Ordre</h2>
|
<h2>Ordre</h2>
|
||||||
<div className={styles.tableWrap}>
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -52,16 +51,15 @@ const Log = () => {
|
|||||||
<td>
|
<td>
|
||||||
{(order.orderlines ?? []).map((line) => (
|
{(order.orderlines ?? []).map((line) => (
|
||||||
<div key={line.id}>
|
<div key={line.id}>
|
||||||
{Number(line.amount)}x {line.product.name}
|
{line.amount}x {line.product.name}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</td>
|
</td>
|
||||||
<td>{Number(order.orderSum)}</td>
|
<td>{order.orderSum}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
<nav aria-label="Order pagination">
|
<nav aria-label="Order pagination">
|
||||||
<ul className={styles.pagination}>
|
<ul className={styles.pagination}>
|
||||||
<li className={clsx({ [styles.disabled]: orderOffset <= 0 })}>
|
<li className={clsx({ [styles.disabled]: orderOffset <= 0 })}>
|
||||||
@@ -91,7 +89,6 @@ const Log = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Transaksjoner</h2>
|
<h2>Transaksjoner</h2>
|
||||||
<div className={styles.tableWrap}>
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -107,12 +104,11 @@ const Log = () => {
|
|||||||
<td>
|
<td>
|
||||||
{trans.user.firstName} {trans.user.lastName}
|
{trans.user.firstName} {trans.user.lastName}
|
||||||
</td>
|
</td>
|
||||||
<td>{Number(trans.amount)}</td>
|
<td>{trans.amount}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
<nav aria-label="Transaction pagination">
|
<nav aria-label="Transaction pagination">
|
||||||
<ul className={styles.pagination}>
|
<ul className={styles.pagination}>
|
||||||
<li className={clsx({ [styles.disabled]: transactionOffset <= 0 })}>
|
<li className={clsx({ [styles.disabled]: transactionOffset <= 0 })}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
query orderList($limit: Int = 10, $offset: Int = 0) {
|
query orderList($limit: Int = 10, $offset: Int = 0) {
|
||||||
orderList(ordering: [{created: DESC}], pagination: {limit: $limit, offset: $offset}) {
|
orderList(order: {created: DESC}, pagination: {limit: $limit, offset: $offset}) {
|
||||||
id
|
id
|
||||||
isExternal
|
isExternal
|
||||||
created
|
created
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
query transactionList($limit: Int = 10, $offset: Int = 0) {
|
query transactionList($limit: Int = 10, $offset: Int = 0) {
|
||||||
transactionList(ordering: [{created: DESC}], pagination: {limit: $limit, offset: $offset}) {
|
transactionList(order: {created: DESC}, pagination: {limit: $limit, offset: $offset}) {
|
||||||
id
|
id
|
||||||
created
|
created
|
||||||
amount
|
amount
|
||||||
|
|||||||
@@ -1,56 +1,51 @@
|
|||||||
.product {
|
.product {
|
||||||
position: relative;
|
|
||||||
display: flex;
|
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;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: rgb(88, 59, 99);
|
|
||||||
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:
|
box-shadow: 0 0 4px var(--background), 0 0 11px var(--background), 0 0 19px var(--background);
|
||||||
0 0 4px var(--background),
|
|
||||||
0 0 11px var(--background),
|
|
||||||
0 0 19px var(--background);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.productText {
|
.productText {
|
||||||
overflow: hidden;
|
|
||||||
color: white;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
text-shadow: none;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-shadow: none;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.amount {
|
.amount {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
display: none;
|
right: 0;
|
||||||
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;
|
||||||
border-left-color: rgb(35, 15, 41);
|
|
||||||
background-color: var(--sun-end);
|
|
||||||
color: var(--background);
|
color: var(--background);
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
|
border-left-color: rgb(35, 15, 41);
|
||||||
|
background-color: var(--sun-end);
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import React, { MouseEventHandler } from "react";
|
import React, { MouseEventHandler } from "react";
|
||||||
|
|
||||||
import { AllProductsQuery } from "../../types";
|
import { Product } from "../../types";
|
||||||
import styles from "./ProductCard.module.css";
|
import styles from "./ProductCard.module.css";
|
||||||
|
|
||||||
type ProductCardProps = {
|
type ProductCardProps = {
|
||||||
product: AllProductsQuery["allProducts"][number];
|
product: Omit<Product, "userCounts">;
|
||||||
onProduct: MouseEventHandler;
|
onProduct: MouseEventHandler;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
amount: number;
|
amount: number;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
.userList {
|
.userList {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
grid-template-columns: repeat(5, 220px);
|
||||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 190px), 1fr));
|
column-gap: 16px;
|
||||||
|
row-gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.showAll {
|
.showAll {
|
||||||
@@ -10,8 +11,9 @@
|
|||||||
|
|
||||||
.productList {
|
.productList {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
grid-template-columns: repeat(5, 220px);
|
||||||
grid-template-columns: repeat(auto-fill, minmax(min(100%, 190px), 1fr));
|
column-gap: 16px;
|
||||||
|
row-gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.productListSection {
|
.productListSection {
|
||||||
@@ -24,23 +26,9 @@
|
|||||||
|
|
||||||
.total {
|
.total {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
min-width: min(200px, 100%);
|
min-width: 200px;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.total {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { AllProductsQuery, OrderLineInput } from "../../types";
|
import { OrderLineInput, 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";
|
||||||
@@ -15,8 +15,8 @@ const Register = (): JSX.Element => {
|
|||||||
loading,
|
loading,
|
||||||
showAll,
|
showAll,
|
||||||
setShowAll,
|
setShowAll,
|
||||||
toggleSelectedUser,
|
|
||||||
selectedUser,
|
selectedUser,
|
||||||
|
toggleSelectedUser,
|
||||||
addToOrder,
|
addToOrder,
|
||||||
order,
|
order,
|
||||||
total,
|
total,
|
||||||
@@ -55,7 +55,7 @@ const Register = (): JSX.Element => {
|
|||||||
<section className={styles.productListSection}>
|
<section className={styles.productListSection}>
|
||||||
<h2>Kjøper hva?</h2>
|
<h2>Kjøper hva?</h2>
|
||||||
<div className={styles.productList}>
|
<div className={styles.productList}>
|
||||||
{products.map((product: AllProductsQuery["allProducts"][number]) => (
|
{products.map((product: Omit<Product, "userCounts">) => (
|
||||||
<ProductCard
|
<ProductCard
|
||||||
key={product.id}
|
key={product.id}
|
||||||
product={product}
|
product={product}
|
||||||
|
|||||||
@@ -2,25 +2,16 @@ 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 {
|
import { AllProductsDocument, AllUsersDocument, CreateOrderDocument, OrderLineInput, Product } from "../../types";
|
||||||
AllProductsDocument,
|
|
||||||
AllProductsQuery,
|
|
||||||
AllUsersDocument,
|
|
||||||
CreateOrderDocument,
|
|
||||||
OrderLineInput,
|
|
||||||
} from "../../types";
|
|
||||||
import { formatCurrency } from "../../utils/currency";
|
|
||||||
import { activeUserFilter } from "../../utils/users";
|
import { activeUserFilter } from "../../utils/users";
|
||||||
|
|
||||||
const USER_EXTERNAL = "external";
|
const USER_EXTERNAL = "external";
|
||||||
|
|
||||||
type Product = AllProductsQuery["allProducts"][number];
|
const sumTotal = (products: Omit<Product, "userCounts">[], order: OrderLineInput[], isExternal: boolean) => {
|
||||||
|
|
||||||
const sumTotal = (products: Product[], order: OrderLineInput[], isExternal: boolean) => {
|
|
||||||
const prices = order.map((orderLine: OrderLineInput) => {
|
const prices = order.map((orderLine: OrderLineInput) => {
|
||||||
const product = products.find((product) => product.id === orderLine.productId);
|
const product = products.find((product: Omit<Product, "userCounts">) => product.id === orderLine.productId);
|
||||||
if (!product) return 0;
|
if (!product) return 0;
|
||||||
return orderLine.amount * Number(isExternal ? product.salePriceExt : product.salePriceInt);
|
return orderLine.amount * (isExternal ? product.salePriceExt : product.salePriceInt);
|
||||||
});
|
});
|
||||||
return prices.reduce((partialSum, a) => partialSum + a, 0);
|
return prices.reduce((partialSum, a) => partialSum + a, 0);
|
||||||
};
|
};
|
||||||
@@ -48,7 +39,7 @@ export const useRegister = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toggleSelectedUser = (userId: string) => {
|
const toggleSelectedUser = (userId: string) => {
|
||||||
setSelectedUser((currentUserId) => (currentUserId === userId ? "" : userId));
|
setSelectedUser(selectedUser == userId ? "" : userId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addToOrder = (productId: string) => {
|
const addToOrder = (productId: string) => {
|
||||||
@@ -66,13 +57,13 @@ export const useRegister = () => {
|
|||||||
setOrder(newOrder);
|
setOrder(newOrder);
|
||||||
};
|
};
|
||||||
|
|
||||||
const total = formatCurrency(sumTotal(products, order, selectedUser === USER_EXTERNAL));
|
const total = sumTotal(products, order, selectedUser === USER_EXTERNAL);
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
let res;
|
let res;
|
||||||
try {
|
try {
|
||||||
res = await createOrderMutation();
|
res = await createOrderMutation();
|
||||||
} catch {
|
} catch (e) {
|
||||||
showToast("Whoops");
|
showToast("Whoops");
|
||||||
console.log("woops");
|
console.log("woops");
|
||||||
return;
|
return;
|
||||||
@@ -105,8 +96,9 @@ export const useRegister = () => {
|
|||||||
products,
|
products,
|
||||||
showAll,
|
showAll,
|
||||||
setShowAll,
|
setShowAll,
|
||||||
toggleSelectedUser,
|
setSelectedUser,
|
||||||
selectedUser,
|
selectedUser,
|
||||||
|
toggleSelectedUser,
|
||||||
addToOrder,
|
addToOrder,
|
||||||
order,
|
order,
|
||||||
total,
|
total,
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { BarDatum, ResponsiveBar } from "@nivo/bar";
|
import { Bar, BarDatum } from "@nivo/bar";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { OrderStatsDocument, SchemaOrderStatsByTime } from "../../types";
|
import { OrderStatsDocument, OrderStatsByTime } from "../../types";
|
||||||
import styles from "./Stats.module.css";
|
|
||||||
import { StatsView } from "./StatsView";
|
import { StatsView } from "./StatsView";
|
||||||
import { chartTheme } from "./chartTheme";
|
|
||||||
|
|
||||||
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" };
|
||||||
|
|
||||||
@@ -25,18 +23,16 @@ const months = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const OrderBar = ({ data }: { data: BarDatum[] }) => (
|
const OrderBar = ({ data }: { data: BarDatum[] }) => (
|
||||||
<div className={styles.chart}>
|
<Bar
|
||||||
<ResponsiveBar
|
|
||||||
data={data}
|
data={data}
|
||||||
theme={chartTheme}
|
margin={{ left: 60, bottom: 40 }}
|
||||||
margin={{ top: 10, right: 10, bottom: 70, left: 60 }}
|
width={1200}
|
||||||
|
height={400}
|
||||||
colorBy="indexValue"
|
colorBy="indexValue"
|
||||||
enableLabel={false}
|
enableLabel={false}
|
||||||
tooltipLabel={(d) => d.id.toString()}
|
tooltipLabel={(d) => d.id.toString()}
|
||||||
axisLeft={{ legend: "Ordre", legendPosition: "middle", legendOffset: -50 }}
|
axisLeft={{ legend: "Ordre", legendPosition: "middle", legendOffset: -50 }}
|
||||||
axisBottom={{ tickRotation: -35 }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const OrderStats = () => {
|
const OrderStats = () => {
|
||||||
@@ -46,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: SchemaOrderStatsByTime) => ({
|
data={(data?.orderStats?.hourly || []).map((stat: OrderStatsByTime) => ({
|
||||||
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,
|
||||||
@@ -54,7 +50,7 @@ const OrderStats = () => {
|
|||||||
/>
|
/>
|
||||||
<h3>Ordre per ukedag</h3>
|
<h3>Ordre per ukedag</h3>
|
||||||
<OrderBar
|
<OrderBar
|
||||||
data={(data?.orderStats?.daily || []).map((stat: SchemaOrderStatsByTime) => ({
|
data={(data?.orderStats?.daily || []).map((stat: OrderStatsByTime) => ({
|
||||||
/* @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,
|
||||||
@@ -63,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: SchemaOrderStatsByTime) => ({
|
data={(data?.orderStats?.monthly || []).map((stat: OrderStatsByTime) => ({
|
||||||
/* @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,
|
||||||
@@ -72,7 +68,7 @@ const OrderStats = () => {
|
|||||||
/>
|
/>
|
||||||
<h3>Ordre per år</h3>
|
<h3>Ordre per år</h3>
|
||||||
<OrderBar
|
<OrderBar
|
||||||
data={(data?.orderStats?.yearly || []).map((stat: SchemaOrderStatsByTime) => ({
|
data={(data?.orderStats?.yearly || []).map((stat: OrderStatsByTime) => ({
|
||||||
id: stat.period,
|
id: stat.period,
|
||||||
label: stat.period,
|
label: stat.period,
|
||||||
value: stat.count,
|
value: stat.count,
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { ResponsivePie } from "@nivo/pie";
|
import { Pie } from "@nivo/pie";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { AllProductStatsDocument } from "../../types";
|
import { AllProductStatsDocument } from "../../types";
|
||||||
import styles from "./Stats.module.css";
|
import styles from "./Stats.module.css";
|
||||||
import { StatsView } from "./StatsView";
|
import { StatsView } from "./StatsView";
|
||||||
import { chartTheme } from "./chartTheme";
|
|
||||||
|
|
||||||
type UserCount = {
|
type UserCount = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -23,20 +22,17 @@ const ProductStats = () => {
|
|||||||
{(data?.allProducts || []).map((product) => (
|
{(data?.allProducts || []).map((product) => (
|
||||||
<div key={product.id}>
|
<div key={product.id}>
|
||||||
{product.name}
|
{product.name}
|
||||||
<div className={styles.productChart}>
|
<Pie
|
||||||
<ResponsivePie
|
margin={{ top: 40, right: 120, bottom: 40, left: 120 }}
|
||||||
margin={{ top: 30, right: 50, bottom: 30, left: 50 }}
|
|
||||||
data={product.userCounts.map((uc: UserCount) => ({
|
data={product.userCounts.map((uc: UserCount) => ({
|
||||||
id: uc.name,
|
id: uc.name,
|
||||||
label: uc.name,
|
label: uc.name,
|
||||||
value: uc.count,
|
value: uc.count,
|
||||||
}))}
|
}))}
|
||||||
theme={chartTheme}
|
width={400}
|
||||||
arcLabelsTextColor="#17121a"
|
height={300}
|
||||||
arcLabelsSkipAngle={8}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</StatsView>
|
</StatsView>
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
.products {
|
.products {
|
||||||
display: grid;
|
display: grid;
|
||||||
max-width: 100%;
|
max-width: 1200px;
|
||||||
gap: 1.5rem 1rem;
|
gap: 1rem;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
|
grid-template-columns: repeat(3, 1fr);
|
||||||
}
|
|
||||||
|
|
||||||
.productChart {
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart {
|
|
||||||
width: 100%;
|
|
||||||
height: 360px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.statsNav {
|
.statsNav {
|
||||||
@@ -36,14 +27,3 @@
|
|||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.statsNav ul {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart {
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,29 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { ResponsiveLine } from "@nivo/line";
|
import { Line } from "@nivo/line";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { ProductStatsDocument } from "../../types";
|
import { ProductStatsDocument } from "../../types";
|
||||||
import styles from "./Stats.module.css";
|
|
||||||
import { StatsView } from "./StatsView";
|
import { StatsView } from "./StatsView";
|
||||||
import { chartTheme, chartTooltipStyle } from "./chartTheme";
|
|
||||||
|
|
||||||
const Stats = () => {
|
const Stats = () => {
|
||||||
const { data, loading } = useQuery(ProductStatsDocument);
|
const { data, loading } = useQuery(ProductStatsDocument);
|
||||||
|
|
||||||
const series = (data?.productStats || []).map(({ productName: id, data }) => ({
|
const series: any[] = [];
|
||||||
id,
|
(data?.productStats || [])?.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 (
|
||||||
<StatsView loading={loading}>
|
<StatsView loading={loading}>
|
||||||
<h2>Kjøp over tid</h2>
|
<h2>Kjøp over tid</h2>
|
||||||
<div className={styles.chart}>
|
<Line
|
||||||
<ResponsiveLine
|
|
||||||
data={series}
|
data={series}
|
||||||
theme={chartTheme}
|
width={1000}
|
||||||
|
height={400}
|
||||||
margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
|
margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
|
||||||
xScale={{
|
xScale={{
|
||||||
type: "time",
|
type: "time",
|
||||||
@@ -44,24 +43,28 @@ const Stats = () => {
|
|||||||
axisBottom={{
|
axisBottom={{
|
||||||
format: "%b %Y",
|
format: "%b %Y",
|
||||||
legend: "Date",
|
legend: "Date",
|
||||||
legendPosition: "middle",
|
legendOffset: -40,
|
||||||
legendOffset: -20,
|
|
||||||
}}
|
}}
|
||||||
useMesh
|
useMesh
|
||||||
pointLabelYOffset={0}
|
pointLabelYOffset={0}
|
||||||
tooltip={({ point }) => (
|
tooltip={({ point }) => (
|
||||||
<div style={chartTooltipStyle}>
|
<div
|
||||||
|
style={{
|
||||||
|
background: "white",
|
||||||
|
padding: "9px 12px",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<strong>{point.data.xFormatted}</strong>
|
<strong>{point.data.xFormatted}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ background: point.seriesColor, width: 13, height: 13, display: "inline-block" }} />{" "}
|
<span style={{ background: point.serieColor, width: 13, height: 13, display: "inline-block" }} />{" "}
|
||||||
<strong>{String(point.data.yFormatted)}</strong> {point.seriesId}
|
<strong>{point.data.yFormatted}</strong> {point.serieId}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</StatsView>
|
</StatsView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const StatsNav = () => (
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<NavLink className={navLinkStyles} to="/stats/users">
|
<NavLink className="disabled" to="/stats/users" title="TODO">
|
||||||
Folk
|
Folk
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import { ResponsiveBar } from "@nivo/bar";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { GQLError } from "../../components/GQLError";
|
|
||||||
import { UserStatsDocument } from "../../types";
|
|
||||||
import styles from "./Stats.module.css";
|
|
||||||
import { StatsView } from "./StatsView";
|
|
||||||
import { chartTheme } from "./chartTheme";
|
|
||||||
|
|
||||||
const UserStats = () => {
|
|
||||||
const { data, loading, error } = useQuery(UserStatsDocument);
|
|
||||||
|
|
||||||
if (error) return <GQLError error={error} />;
|
|
||||||
|
|
||||||
const users = (data?.allUsers || [])
|
|
||||||
.filter((user) => Number(user.profile?.orderSumTotal || 0) > 0)
|
|
||||||
.map((user) => ({
|
|
||||||
name: `${user.firstName}${user.lastName ? ` ${user.lastName[0]}.` : ""}`,
|
|
||||||
total: Number(user.profile?.orderSumTotal || 0),
|
|
||||||
}))
|
|
||||||
.sort((a, b) => b.total - a.total)
|
|
||||||
.slice(0, 10);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StatsView loading={loading}>
|
|
||||||
<h3>Topp 10 etter total kjøpesum</h3>
|
|
||||||
{users.length ? (
|
|
||||||
<div className={styles.chart} style={{ height: Math.max(320, users.length * 44) }}>
|
|
||||||
<ResponsiveBar
|
|
||||||
data={users}
|
|
||||||
keys={["total"]}
|
|
||||||
indexBy="name"
|
|
||||||
layout="horizontal"
|
|
||||||
theme={chartTheme}
|
|
||||||
margin={{ top: 10, right: 35, bottom: 40, left: 100 }}
|
|
||||||
padding={0.3}
|
|
||||||
enableLabel={false}
|
|
||||||
axisBottom={{
|
|
||||||
legend: "Kjøp totalt (kr)",
|
|
||||||
legendPosition: "middle",
|
|
||||||
legendOffset: 32,
|
|
||||||
}}
|
|
||||||
axisLeft={null}
|
|
||||||
valueFormat={(value) => `${Number(value).toLocaleString("no")} kr`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p>Ingen kjøp å vise ennå.</p>
|
|
||||||
)}
|
|
||||||
</StatsView>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UserStats;
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
export const chartTheme = {
|
|
||||||
background: "transparent",
|
|
||||||
text: {
|
|
||||||
fill: "#f7effa",
|
|
||||||
fontFamily: '"IBM Plex Mono", monospace',
|
|
||||||
fontSize: 13,
|
|
||||||
textShadow: "none",
|
|
||||||
},
|
|
||||||
axis: {
|
|
||||||
domain: {
|
|
||||||
line: { stroke: "#b9a3c5", strokeWidth: 1 },
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
line: { stroke: "#b9a3c5", strokeWidth: 1 },
|
|
||||||
text: { fill: "#f7effa", fontSize: 12, textShadow: "none" },
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
text: { fill: "#f7effa", fontSize: 13, textShadow: "none" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
line: { stroke: "rgba(221, 81, 127, 0.24)", strokeWidth: 1 },
|
|
||||||
},
|
|
||||||
legends: {
|
|
||||||
text: { fill: "#f7effa", fontSize: 12, textShadow: "none" },
|
|
||||||
title: {
|
|
||||||
text: { fill: "#f7effa", fontSize: 13, textShadow: "none" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
labels: {
|
|
||||||
text: {
|
|
||||||
fill: "#ffffff",
|
|
||||||
fontSize: 13,
|
|
||||||
textShadow: "none",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
container: {
|
|
||||||
background: "#2b1733",
|
|
||||||
color: "#ffffff",
|
|
||||||
fontFamily: '"IBM Plex Mono", monospace',
|
|
||||||
fontSize: 13,
|
|
||||||
border: "1px solid #dd517f",
|
|
||||||
borderRadius: 4,
|
|
||||||
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.45)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const chartTooltipStyle = {
|
|
||||||
background: "#2b1733",
|
|
||||||
color: "#ffffff",
|
|
||||||
padding: "9px 12px",
|
|
||||||
border: "1px solid #dd517f",
|
|
||||||
borderRadius: 4,
|
|
||||||
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.45)",
|
|
||||||
fontFamily: '"IBM Plex Mono", monospace',
|
|
||||||
fontSize: 13,
|
|
||||||
textShadow: "none",
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
query userStats {
|
|
||||||
allUsers {
|
|
||||||
id
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
profile {
|
|
||||||
orderSumTotal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
.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:
|
transition: background-color 0.1s ease-out, border 0.1s ease-out;
|
||||||
background-color 0.1s ease-out,
|
max-height: 82px;
|
||||||
border 0.1s ease-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.user:hover {
|
.user:hover {
|
||||||
@@ -19,19 +17,16 @@
|
|||||||
.user:hover,
|
.user:hover,
|
||||||
.active.user {
|
.active.user {
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
box-shadow:
|
box-shadow: 0 0 4px var(--background), 0 0 11px var(--background), 0 0 19px var(--background);
|
||||||
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 {
|
||||||
@@ -40,10 +35,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: "";
|
||||||
@@ -51,16 +46,16 @@
|
|||||||
|
|
||||||
.meta {
|
.meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
padding: 1rem;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 1rem;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
text-shadow: none;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance {
|
.balance {
|
||||||
color: var(--sun);
|
color: var(--sun)
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ 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";
|
||||||
|
|
||||||
type UserCardProps = {
|
type UserCardProps = {
|
||||||
@@ -14,7 +13,6 @@ type UserCardProps = {
|
|||||||
/** If user is null, then it's the special external user */
|
/** If user is null, then it's the special external user */
|
||||||
export const UserCard = ({ user, onUser, active = false }: UserCardProps): JSX.Element => {
|
export const UserCard = ({ user, onUser, active = false }: UserCardProps): JSX.Element => {
|
||||||
const classes = clsx(styles.user, { [styles.active]: active });
|
const classes = clsx(styles.user, { [styles.active]: active });
|
||||||
const userBalanceString = formatCurrency(Number(user?.profile?.balance));
|
|
||||||
return (
|
return (
|
||||||
<button className={classes} onClick={onUser} type="button" title={user?.firstName || "Ekstern"}>
|
<button className={classes} onClick={onUser} type="button" title={user?.firstName || "Ekstern"}>
|
||||||
<div className={styles.avatarWrap}>
|
<div className={styles.avatarWrap}>
|
||||||
@@ -29,7 +27,7 @@ export const UserCard = ({ user, onUser, active = false }: UserCardProps): JSX.E
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.meta}>
|
<div className={styles.meta}>
|
||||||
{user?.firstName || "Ekstern"}
|
{user?.firstName || "Ekstern"}
|
||||||
<span className={styles.balance}>{userBalanceString}</span>
|
<span className={styles.balance}>{user?.profile?.balance || ""}</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
.userDetail {
|
.userDetail {
|
||||||
display: grid;
|
display: grid;
|
||||||
min-width: 0;
|
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
grid-template-columns: minmax(0, 400px) minmax(0, 1fr);
|
grid-template-columns: 400px 1fr;
|
||||||
}
|
|
||||||
|
|
||||||
.chart {
|
|
||||||
width: 100%;
|
|
||||||
height: 360px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
|
||||||
.userDetail {
|
|
||||||
grid-template-columns: minmax(0, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart {
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { ResponsiveLine } from "@nivo/line";
|
import { Line } from "@nivo/line";
|
||||||
import React from "react";
|
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 { SchemaUser as User, UserDetailDocument, SchemaUserProfile as UserProfile } from "../../types";
|
import { User, UserDetailDocument, UserProfile } from "../../types";
|
||||||
import { formatMonth, formatTime } from "../../utils/time";
|
import { formatMonth, formatTime } from "../../utils/time";
|
||||||
import { chartTheme, chartTooltipStyle } from "../stats/chartTheme";
|
|
||||||
import styles from "./UserDetail.module.css";
|
import styles from "./UserDetail.module.css";
|
||||||
|
|
||||||
const UserDetail = () => {
|
const UserDetail = () => {
|
||||||
@@ -19,13 +18,15 @@ 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;
|
||||||
const series = (profile?.productOrderStats || []).map(({ productName: id, data }) => ({
|
// FIXME: types
|
||||||
id,
|
const series: any[] = [];
|
||||||
data: data.map((point, index, points) => ({
|
(profile?.productOrderStats || [])?.forEach(({ productName: id, data }) => {
|
||||||
x: point.x,
|
const lineData: any[] = [];
|
||||||
y: point.y + (points[index - 1]?.y || 0),
|
data.forEach((point, index) => {
|
||||||
})),
|
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>
|
||||||
@@ -36,13 +37,13 @@ const UserDetail = () => {
|
|||||||
<div>Stats</div>
|
<div>Stats</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Saldo: <strong>{Number(profile.balance).toLocaleString("no")} kr</strong>
|
Saldo: <strong>{profile.balance.toLocaleString("no")} kr</strong>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Siste kjøp: <strong>{formatTime(profile.lastPurchaseDate)}</strong>
|
Siste kjøp: <strong>{formatTime(profile.lastPurchaseDate)}</strong>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Kjøp totalt: <strong>{Number(profile.orderSumTotal).toLocaleString("no")} kr</strong>
|
Kjøp totalt: <strong>{profile.orderSumTotal.toLocaleString("no")} kr</strong>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,10 +62,10 @@ const UserDetail = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2>Kjøp over tid</h2>
|
<h2>Kjøp over tid</h2>
|
||||||
<div className={styles.chart}>
|
<Line
|
||||||
<ResponsiveLine
|
|
||||||
data={series}
|
data={series}
|
||||||
theme={chartTheme}
|
width={1000}
|
||||||
|
height={400}
|
||||||
margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
|
margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
|
||||||
xScale={{
|
xScale={{
|
||||||
type: "time",
|
type: "time",
|
||||||
@@ -84,26 +85,30 @@ const UserDetail = () => {
|
|||||||
axisBottom={{
|
axisBottom={{
|
||||||
format: "%b %Y",
|
format: "%b %Y",
|
||||||
legend: "Date",
|
legend: "Date",
|
||||||
legendPosition: "middle",
|
legendOffset: -12,
|
||||||
legendOffset: -20,
|
|
||||||
}}
|
}}
|
||||||
useMesh
|
useMesh
|
||||||
pointLabelYOffset={0}
|
pointLabelYOffset={0}
|
||||||
tooltip={({ point }) => (
|
tooltip={({ point }) => (
|
||||||
<div style={chartTooltipStyle}>
|
<div
|
||||||
|
style={{
|
||||||
|
background: "white",
|
||||||
|
padding: "9px 12px",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<strong>{point.data.xFormatted}</strong>
|
<strong>{point.data.xFormatted}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ background: point.seriesColor, width: 13, height: 13, display: "inline-block" }} />{" "}
|
<span style={{ background: point.serieColor, width: 13, height: 13, display: "inline-block" }} />{" "}
|
||||||
<strong>{String(point.data.yFormatted)}</strong> {point.seriesId}
|
<strong>{point.data.yFormatted}</strong> {point.serieId}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default UserDetail;
|
export default UserDetail;
|
||||||
+11
-41
@@ -14,41 +14,26 @@
|
|||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
#root {
|
|
||||||
display: flex;
|
|
||||||
min-height: 100vh;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
*,
|
*,
|
||||||
*:before,
|
*:before,
|
||||||
*:after {
|
*:after {
|
||||||
box-sizing: inherit;
|
box-sizing: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 641px) {
|
|
||||||
html {
|
|
||||||
scrollbar-gutter: stable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Body and interitable styles */
|
/* Body and interitable styles */
|
||||||
body {
|
body {
|
||||||
background: linear-gradient(rgba(0, 0, 0, 0.8) 60%, var(--background) 100%);
|
background: linear-gradient(rgba(0, 0, 0, 0.8) 60%, var(--background) 100%);
|
||||||
background-attachment: fixed;
|
|
||||||
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:
|
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor;
|
||||||
0 0 4px currentColor,
|
|
||||||
0 0 11px currentColor,
|
|
||||||
0 0 19px currentColor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Links */
|
/* Links */
|
||||||
@@ -78,12 +63,9 @@ h5 {
|
|||||||
a.btn-primary {
|
a.btn-primary {
|
||||||
padding: 0.4rem 1rem;
|
padding: 0.4rem 1rem;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 4px;
|
|
||||||
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
|
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
|
||||||
box-shadow:
|
border-radius: 4px;
|
||||||
0 0 4px var(--sun),
|
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
|
||||||
0 0 11px var(--sun),
|
|
||||||
0 0 19px var(--sun);
|
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,14 +73,11 @@ a.btn-primary {
|
|||||||
button {
|
button {
|
||||||
padding: 0.4rem 1rem;
|
padding: 0.4rem 1rem;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 4px;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
color: var(--border);
|
color: var(--border);
|
||||||
font-family: var(--font-family);
|
font-family: var(--font-family);
|
||||||
text-shadow:
|
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor;
|
||||||
0 0 4px currentColor,
|
|
||||||
0 0 11px currentColor,
|
|
||||||
0 0 19px currentColor;
|
|
||||||
}
|
}
|
||||||
button:hover {
|
button:hover {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
@@ -107,10 +86,7 @@ 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:
|
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
|
||||||
0 0 4px var(--sun),
|
|
||||||
0 0 11px var(--sun),
|
|
||||||
0 0 19px var(--sun);
|
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,15 +94,12 @@ button.primary {
|
|||||||
input {
|
input {
|
||||||
padding: 0.4rem 0.4rem;
|
padding: 0.4rem 0.4rem;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 4px;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
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:
|
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor;
|
||||||
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);
|
||||||
@@ -135,12 +108,9 @@ input:focus {
|
|||||||
input.btn-primary {
|
input.btn-primary {
|
||||||
padding: 0.4rem 1rem;
|
padding: 0.4rem 1rem;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 4px;
|
|
||||||
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
|
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
|
||||||
box-shadow:
|
border-radius: 4px;
|
||||||
0 0 4px var(--sun),
|
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
|
||||||
0 0 11px var(--sun),
|
|
||||||
0 0 19px var(--sun);
|
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
::placeholder {
|
::placeholder {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useCallback, useEffect, useRef } from "react";
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
|
|
||||||
export function usePrevious<T>(value: T) {
|
export function usePrevious(value: any) {
|
||||||
// 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<T | undefined>(undefined);
|
const ref = useRef();
|
||||||
|
|
||||||
// Store current value in ref
|
// Store current value in ref
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ ReactDOM.render(
|
|||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById("root"),
|
document.getElementById("root")
|
||||||
);
|
);
|
||||||
+223
-367
@@ -1,483 +1,339 @@
|
|||||||
/** 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: { input: string; output: string; }
|
ID: string;
|
||||||
String: { input: string; output: string; }
|
String: string;
|
||||||
Boolean: { input: boolean; output: boolean; }
|
Boolean: boolean;
|
||||||
Int: { input: number; output: number; }
|
Int: number;
|
||||||
Float: { input: number; output: number; }
|
Float: number;
|
||||||
/** Date with time (isoformat) */
|
/** Date with time (isoformat) */
|
||||||
DateTime: { input: string; output: string; }
|
DateTime: any;
|
||||||
/** Decimal (fixed-point) */
|
/** The GenericScalar scalar type represents a generic GraphQL scalar value that could be: List or Object. */
|
||||||
Decimal: { input: string; output: string; }
|
JSONScalar: any;
|
||||||
/** The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf). */
|
|
||||||
JSON: { input: unknown; output: Array<{ name: string; count: number }>; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemaAboveMaxSaldo = SchemaError & {
|
export type AboveMaxSaldo = Error & {
|
||||||
__typename?: 'AboveMaxSaldo';
|
__typename?: 'AboveMaxSaldo';
|
||||||
message: Scalars['String']['output'];
|
above: Scalars['Float'];
|
||||||
error: Scalars['Boolean']['output'];
|
error: Scalars['Boolean'];
|
||||||
maxSaldo: Scalars['Float']['output'];
|
maxSaldo: Scalars['Float'];
|
||||||
above: Scalars['Float']['output'];
|
message: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemaCreateDepositResponse = SchemaCreateDepositSuccess | SchemaAboveMaxSaldo | SchemaFormErrors;
|
export type CreateDepositResponse = AboveMaxSaldo | CreateDepositSuccess | FormErrors;
|
||||||
|
|
||||||
export type SchemaCreateDepositSuccess = {
|
export type CreateDepositSuccess = {
|
||||||
__typename?: 'CreateDepositSuccess';
|
__typename?: 'CreateDepositSuccess';
|
||||||
transaction: SchemaTransaction;
|
transaction: Transaction;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemaCreateInventoryTransactionResponse = SchemaCreateInventoryTransactionSuccess | SchemaFormErrors;
|
export type CreateOrderResponse = CreateOrderSuccess | FormErrors | InsufficientFunds;
|
||||||
|
|
||||||
export type SchemaCreateInventoryTransactionSuccess = {
|
export type CreateOrderSuccess = {
|
||||||
__typename?: 'CreateInventoryTransactionSuccess';
|
|
||||||
inventoryTransaction: SchemaInventoryTransaction;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaCreateOrderResponse = SchemaCreateOrderSuccess | SchemaInsufficientFunds | SchemaFormErrors;
|
|
||||||
|
|
||||||
export type SchemaCreateOrderSuccess = {
|
|
||||||
__typename?: 'CreateOrderSuccess';
|
__typename?: 'CreateOrderSuccess';
|
||||||
order: SchemaOrder;
|
order: Order;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemaError = {
|
export type Error = {
|
||||||
message: Scalars['String']['output'];
|
error: Scalars['Boolean'];
|
||||||
error: Scalars['Boolean']['output'];
|
message: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemaFieldError = SchemaError & {
|
export type FieldError = Error & {
|
||||||
__typename?: 'FieldError';
|
__typename?: 'FieldError';
|
||||||
message: Scalars['String']['output'];
|
error: Scalars['Boolean'];
|
||||||
error: Scalars['Boolean']['output'];
|
field: Scalars['String'];
|
||||||
field: Scalars['String']['output'];
|
message: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemaFormErrors = {
|
export type FormErrors = {
|
||||||
__typename?: 'FormErrors';
|
__typename?: 'FormErrors';
|
||||||
fields: Array<SchemaFieldError>;
|
error: Scalars['Boolean'];
|
||||||
message: Scalars['String']['output'];
|
fields: Array<FieldError>;
|
||||||
error: Scalars['Boolean']['output'];
|
message: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemaInsufficientFunds = SchemaError & {
|
export type InsufficientFunds = Error & {
|
||||||
__typename?: 'InsufficientFunds';
|
__typename?: 'InsufficientFunds';
|
||||||
message: Scalars['String']['output'];
|
amountLacking: Scalars['Float'];
|
||||||
error: Scalars['Boolean']['output'];
|
error: Scalars['Boolean'];
|
||||||
amountLacking: Scalars['Float']['output'];
|
message: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemaInventoryReport = {
|
export type Mutation = {
|
||||||
__typename?: 'InventoryReport';
|
|
||||||
products: Array<SchemaInventoryReportProduct>;
|
|
||||||
inventoryPurchases: Scalars['Decimal']['output'];
|
|
||||||
productSales: Scalars['Decimal']['output'];
|
|
||||||
deposits: Scalars['Decimal']['output'];
|
|
||||||
orderPurchases: Scalars['Decimal']['output'];
|
|
||||||
balanceAtStart: Scalars['Decimal']['output'];
|
|
||||||
balanceAtEnd: Scalars['Decimal']['output'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaInventoryReportProduct = {
|
|
||||||
__typename?: 'InventoryReportProduct';
|
|
||||||
productId: Scalars['ID']['output'];
|
|
||||||
productName: Scalars['String']['output'];
|
|
||||||
purchaseUnits: Scalars['Int']['output'];
|
|
||||||
purchaseValue: Scalars['Decimal']['output'];
|
|
||||||
salesUnits: Scalars['Int']['output'];
|
|
||||||
salesValue: Scalars['Decimal']['output'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaInventoryTransaction = {
|
|
||||||
__typename?: 'InventoryTransaction';
|
|
||||||
id: Scalars['ID']['output'];
|
|
||||||
user: SchemaUser;
|
|
||||||
product: SchemaProduct;
|
|
||||||
amount: Scalars['Int']['output'];
|
|
||||||
unitPrice: Scalars['Decimal']['output'];
|
|
||||||
comment: Scalars['String']['output'];
|
|
||||||
created: Scalars['DateTime']['output'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaInventoryTransactionInput = {
|
|
||||||
productId: Scalars['ID']['input'];
|
|
||||||
amount: Scalars['Int']['input'];
|
|
||||||
unitPrice: Scalars['Decimal']['input'];
|
|
||||||
comment?: Scalars['String']['input'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaInventoryTransactionOrdering =
|
|
||||||
{ created: SchemaOrdering; };
|
|
||||||
|
|
||||||
export type SchemaMutation = {
|
|
||||||
__typename?: 'Mutation';
|
__typename?: 'Mutation';
|
||||||
createInventoryTransaction: SchemaCreateInventoryTransactionResponse;
|
createDeposit: CreateDepositResponse;
|
||||||
createOrder: SchemaCreateOrderResponse;
|
createOrder: CreateOrderResponse;
|
||||||
createDeposit: SchemaCreateDepositResponse;
|
createUser: User;
|
||||||
createUser: SchemaUser;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type SchemaMutationCreateInventoryTransactionArgs = {
|
export type MutationCreateDepositArgs = {
|
||||||
data: SchemaInventoryTransactionInput;
|
amount: Scalars['Int'];
|
||||||
|
userId: Scalars['ID'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type SchemaMutationCreateOrderArgs = {
|
export type MutationCreateOrderArgs = {
|
||||||
customerId: Scalars['ID']['input'];
|
customerId: Scalars['ID'];
|
||||||
orderLines: Array<SchemaOrderLineInput>;
|
isExternal?: Scalars['Boolean'];
|
||||||
isExternal?: Scalars['Boolean']['input'];
|
orderLines: Array<OrderLineInput>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type SchemaMutationCreateDepositArgs = {
|
export type MutationCreateUserArgs = {
|
||||||
userId: Scalars['ID']['input'];
|
input: UserInput;
|
||||||
amount: Scalars['Int']['input'];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type OffsetPaginationInput = {
|
||||||
export type SchemaMutationCreateUserArgs = {
|
limit?: Scalars['Int'];
|
||||||
input: SchemaUserInput;
|
offset?: Scalars['Int'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemaOffsetPaginationInput = {
|
export type Order = {
|
||||||
offset?: Scalars['Int']['input'];
|
|
||||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaOrder = {
|
|
||||||
__typename?: 'Order';
|
__typename?: 'Order';
|
||||||
id: Scalars['ID']['output'];
|
created: Scalars['DateTime'];
|
||||||
customer?: Maybe<SchemaUser>;
|
customer?: Maybe<User>;
|
||||||
orderSum: Scalars['Decimal']['output'];
|
id: Scalars['ID'];
|
||||||
created: Scalars['DateTime']['output'];
|
isExternal: Scalars['Boolean'];
|
||||||
orderlines: Array<SchemaOrderLine>;
|
orderSum: Scalars['Float'];
|
||||||
isExternal: Scalars['Boolean']['output'];
|
orderlines?: Maybe<Array<OrderLine>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemaOrderLine = {
|
export type OrderLine = {
|
||||||
__typename?: 'OrderLine';
|
__typename?: 'OrderLine';
|
||||||
id: Scalars['ID']['output'];
|
amount: Scalars['Int'];
|
||||||
order: SchemaOrder;
|
id: Scalars['ID'];
|
||||||
product: SchemaProduct;
|
order: Order;
|
||||||
amount: Scalars['Int']['output'];
|
product: Product;
|
||||||
unitPrice: Scalars['Decimal']['output'];
|
unitPrice: Scalars['Float'];
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaOrderLineInput = {
|
|
||||||
productId: Scalars['ID']['input'];
|
|
||||||
amount: Scalars['Int']['input'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaOrderOrdering =
|
|
||||||
{ created: SchemaOrdering; };
|
|
||||||
|
|
||||||
export type SchemaOrderStats = {
|
|
||||||
__typename?: 'OrderStats';
|
|
||||||
yearly: Array<SchemaOrderStatsByTime>;
|
|
||||||
monthly: Array<SchemaOrderStatsByTime>;
|
|
||||||
daily: Array<SchemaOrderStatsByTime>;
|
|
||||||
hourly: Array<SchemaOrderStatsByTime>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaOrderStatsByTime = {
|
|
||||||
__typename?: 'OrderStatsByTime';
|
|
||||||
period: Scalars['Int']['output'];
|
|
||||||
count: Scalars['String']['output'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum SchemaOrdering {
|
|
||||||
Asc = 'ASC',
|
|
||||||
AscNullsFirst = 'ASC_NULLS_FIRST',
|
|
||||||
AscNullsLast = 'ASC_NULLS_LAST',
|
|
||||||
Desc = 'DESC',
|
|
||||||
DescNullsFirst = 'DESC_NULLS_FIRST',
|
|
||||||
DescNullsLast = 'DESC_NULLS_LAST'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SchemaPoint = {
|
|
||||||
__typename?: 'Point';
|
|
||||||
x: Scalars['String']['output'];
|
|
||||||
y: Scalars['Int']['output'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaProduct = {
|
|
||||||
__typename?: 'Product';
|
|
||||||
id: Scalars['ID']['output'];
|
|
||||||
name: Scalars['String']['output'];
|
|
||||||
imageUrl: Scalars['String']['output'];
|
|
||||||
salePriceInt: Scalars['Decimal']['output'];
|
|
||||||
salePriceExt: Scalars['Decimal']['output'];
|
|
||||||
active: Scalars['Boolean']['output'];
|
|
||||||
inventoryAmount: Scalars['Int']['output'];
|
|
||||||
updated: Scalars['DateTime']['output'];
|
|
||||||
wholesaleUnitPrice: Scalars['Decimal']['output'];
|
|
||||||
wholesaleValue: Scalars['Decimal']['output'];
|
|
||||||
userCounts: Scalars['JSON']['output'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaProductFilter = {
|
|
||||||
id?: InputMaybe<Scalars['ID']['input']>;
|
|
||||||
active?: InputMaybe<Scalars['Boolean']['input']>;
|
|
||||||
AND?: InputMaybe<SchemaProductFilter>;
|
|
||||||
OR?: InputMaybe<SchemaProductFilter>;
|
|
||||||
NOT?: InputMaybe<SchemaProductFilter>;
|
|
||||||
DISTINCT?: InputMaybe<Scalars['Boolean']['input']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaProductOrderStat = {
|
|
||||||
__typename?: 'ProductOrderStat';
|
|
||||||
productName: Scalars['String']['output'];
|
|
||||||
data: Array<SchemaPoint>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaQuery = {
|
|
||||||
__typename?: 'Query';
|
|
||||||
transactionList: Array<SchemaTransaction>;
|
|
||||||
inventoryTransactionList: Array<SchemaInventoryTransaction>;
|
|
||||||
inventoryReport: SchemaInventoryReport;
|
|
||||||
allProducts: Array<SchemaProduct>;
|
|
||||||
allOrders: Array<SchemaOrder>;
|
|
||||||
orderList: Array<SchemaOrder>;
|
|
||||||
orderStats: SchemaOrderStats;
|
|
||||||
productStats: Array<SchemaProductOrderStat>;
|
|
||||||
allUsers: Array<SchemaUser>;
|
|
||||||
user: SchemaUser;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export type SchemaQueryTransactionListArgs = {
|
|
||||||
ordering?: Array<SchemaTransactionOrdering>;
|
|
||||||
pagination?: InputMaybe<SchemaOffsetPaginationInput>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export type SchemaQueryInventoryTransactionListArgs = {
|
|
||||||
ordering?: Array<SchemaInventoryTransactionOrdering>;
|
|
||||||
pagination?: InputMaybe<SchemaOffsetPaginationInput>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export type SchemaQueryInventoryReportArgs = {
|
|
||||||
startTime: Scalars['DateTime']['input'];
|
|
||||||
endTime: Scalars['DateTime']['input'];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export type SchemaQueryAllProductsArgs = {
|
|
||||||
filters?: InputMaybe<SchemaProductFilter>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export type SchemaQueryOrderListArgs = {
|
|
||||||
ordering?: Array<SchemaOrderOrdering>;
|
|
||||||
pagination?: InputMaybe<SchemaOffsetPaginationInput>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export type SchemaQueryUserArgs = {
|
|
||||||
userId: Scalars['ID']['input'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaTopMonth = {
|
|
||||||
__typename?: 'TopMonth';
|
|
||||||
period: Scalars['String']['output'];
|
|
||||||
count: Scalars['Int']['output'];
|
|
||||||
sum: Scalars['Float']['output'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaTransaction = {
|
|
||||||
__typename?: 'Transaction';
|
|
||||||
id: Scalars['ID']['output'];
|
|
||||||
user: SchemaUser;
|
|
||||||
amount: Scalars['Decimal']['output'];
|
|
||||||
created: Scalars['DateTime']['output'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaTransactionOrdering =
|
|
||||||
{ created: SchemaOrdering; };
|
|
||||||
|
|
||||||
export type SchemaUser = {
|
|
||||||
__typename?: 'User';
|
|
||||||
id: Scalars['ID']['output'];
|
|
||||||
firstName: Scalars['String']['output'];
|
|
||||||
lastName: Scalars['String']['output'];
|
|
||||||
profile?: Maybe<SchemaUserProfile>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaUserInput = {
|
|
||||||
email?: InputMaybe<Scalars['String']['input']>;
|
|
||||||
firstName?: InputMaybe<Scalars['String']['input']>;
|
|
||||||
lastName?: InputMaybe<Scalars['String']['input']>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaUserProfile = {
|
|
||||||
__typename?: 'UserProfile';
|
|
||||||
id: Scalars['ID']['output'];
|
|
||||||
balance: Scalars['Decimal']['output'];
|
|
||||||
image: Scalars['String']['output'];
|
|
||||||
lastPurchaseDate: Scalars['DateTime']['output'];
|
|
||||||
orderSumTotal: Scalars['Decimal']['output'];
|
|
||||||
topMonths: Array<SchemaTopMonth>;
|
|
||||||
productOrderStats: Array<SchemaProductOrderStat>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type InventoryTransactionInput = {
|
|
||||||
productId: string | number;
|
|
||||||
amount: number;
|
|
||||||
unitPrice: string;
|
|
||||||
comment?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OrderLineInput = {
|
export type OrderLineInput = {
|
||||||
productId: string | number;
|
amount: Scalars['Int'];
|
||||||
amount: number;
|
productId: Scalars['ID'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderOrdering = {
|
||||||
|
created?: InputMaybe<Ordering>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderStats = {
|
||||||
|
__typename?: 'OrderStats';
|
||||||
|
daily: Array<OrderStatsByTime>;
|
||||||
|
hourly: Array<OrderStatsByTime>;
|
||||||
|
monthly: Array<OrderStatsByTime>;
|
||||||
|
yearly: Array<OrderStatsByTime>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderStatsByTime = {
|
||||||
|
__typename?: 'OrderStatsByTime';
|
||||||
|
count: Scalars['String'];
|
||||||
|
period: Scalars['Int'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum Ordering {
|
||||||
|
Asc = 'ASC',
|
||||||
|
Desc = 'DESC'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Point = {
|
||||||
|
__typename?: 'Point';
|
||||||
|
x: Scalars['String'];
|
||||||
|
y: Scalars['Int'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Product = {
|
||||||
|
__typename?: 'Product';
|
||||||
|
id: Scalars['ID'];
|
||||||
|
imageUrl: Scalars['String'];
|
||||||
|
name: Scalars['String'];
|
||||||
|
salePriceExt: Scalars['Float'];
|
||||||
|
salePriceInt: Scalars['Float'];
|
||||||
|
userCounts: Scalars['JSONScalar'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProductFilter = {
|
||||||
|
active?: InputMaybe<Scalars['Boolean']>;
|
||||||
|
id?: InputMaybe<Scalars['ID']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProductOrderStat = {
|
||||||
|
__typename?: 'ProductOrderStat';
|
||||||
|
data: Array<Point>;
|
||||||
|
productName: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Query = {
|
||||||
|
__typename?: 'Query';
|
||||||
|
allOrders: Array<Order>;
|
||||||
|
allProducts: Array<Product>;
|
||||||
|
allUsers: Array<User>;
|
||||||
|
orderList: Array<Order>;
|
||||||
|
orderStats: OrderStats;
|
||||||
|
productStats: Array<ProductOrderStat>;
|
||||||
|
transactionList: Array<Transaction>;
|
||||||
|
user: User;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type QueryAllProductsArgs = {
|
||||||
|
filters?: InputMaybe<ProductFilter>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type QueryOrderListArgs = {
|
||||||
|
order?: InputMaybe<OrderOrdering>;
|
||||||
|
pagination?: InputMaybe<OffsetPaginationInput>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type QueryTransactionListArgs = {
|
||||||
|
order?: InputMaybe<TransactionOrdering>;
|
||||||
|
pagination?: InputMaybe<OffsetPaginationInput>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type QueryUserArgs = {
|
||||||
|
userId: Scalars['ID'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TopMonth = {
|
||||||
|
__typename?: 'TopMonth';
|
||||||
|
count: Scalars['Int'];
|
||||||
|
period: Scalars['String'];
|
||||||
|
sum: Scalars['Int'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Transaction = {
|
||||||
|
__typename?: 'Transaction';
|
||||||
|
amount: Scalars['Float'];
|
||||||
|
created: Scalars['DateTime'];
|
||||||
|
id: Scalars['ID'];
|
||||||
|
user: User;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TransactionOrdering = {
|
||||||
|
created?: InputMaybe<Ordering>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
__typename?: 'User';
|
||||||
|
firstName: Scalars['String'];
|
||||||
|
id: Scalars['ID'];
|
||||||
|
lastName: Scalars['String'];
|
||||||
|
profile?: Maybe<UserProfile>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserInput = {
|
||||||
|
email?: InputMaybe<Scalars['String']>;
|
||||||
|
firstName?: InputMaybe<Scalars['String']>;
|
||||||
|
lastName?: InputMaybe<Scalars['String']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserProfile = {
|
||||||
|
__typename?: 'UserProfile';
|
||||||
|
balance: Scalars['Float'];
|
||||||
|
id: Scalars['ID'];
|
||||||
|
image: Scalars['String'];
|
||||||
|
lastPurchaseDate: Scalars['DateTime'];
|
||||||
|
orderSumTotal: Scalars['Float'];
|
||||||
|
productOrderStats: Array<ProductOrderStat>;
|
||||||
|
topMonths: Array<TopMonth>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateUserMutationVariables = Exact<{
|
export type CreateUserMutationVariables = Exact<{
|
||||||
firstName: string;
|
firstName: Scalars['String'];
|
||||||
lastName: string;
|
lastName: Scalars['String'];
|
||||||
email: string;
|
email: Scalars['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: string | number;
|
userId: Scalars['ID'];
|
||||||
amount: number;
|
amount: Scalars['Int'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type CreateDepositMutation = { __typename: 'Mutation', createDeposit:
|
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 }> } };
|
||||||
| { __typename: 'CreateDepositSuccess', transaction: { __typename: 'Transaction', id: string, amount: string, user: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: string } | 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 CreateInventoryTransactionMutationVariables = Exact<{
|
|
||||||
data: InventoryTransactionInput;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
|
|
||||||
export type CreateInventoryTransactionMutation = { __typename: 'Mutation', createInventoryTransaction:
|
|
||||||
| { __typename: 'CreateInventoryTransactionSuccess', inventoryTransaction: { __typename: 'InventoryTransaction', id: string, product: { __typename: 'Product', name: string, inventoryAmount: number } } }
|
|
||||||
| { __typename: 'FormErrors', message: string, fields: Array<{ __typename: 'FieldError', field: string, message: string }> }
|
|
||||||
};
|
|
||||||
|
|
||||||
export type InventoryPageQueryVariables = Exact<{
|
|
||||||
limit?: number | null | undefined;
|
|
||||||
offset?: number | null | undefined;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
|
|
||||||
export type InventoryPageQuery = { __typename: 'Query', allProducts: Array<{ __typename: 'Product', id: string, name: string, active: boolean, inventoryAmount: number, updated: string, wholesaleUnitPrice: string, wholesaleValue: string }>, inventoryTransactionList: Array<{ __typename: 'InventoryTransaction', id: string, created: string, amount: number, unitPrice: string, comment: string, product: { __typename: 'Product', name: string }, user: { __typename: 'User', firstName: string, lastName: string } }> };
|
|
||||||
|
|
||||||
export type InventoryProductsQueryVariables = Exact<{ [key: string]: never; }>;
|
|
||||||
|
|
||||||
|
|
||||||
export type InventoryProductsQuery = { __typename: 'Query', allProducts: Array<{ __typename: 'Product', id: string, name: string, inventoryAmount: number }> };
|
|
||||||
|
|
||||||
export type InventoryReportQueryVariables = Exact<{
|
|
||||||
startTime: string;
|
|
||||||
endTime: string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
|
|
||||||
export type InventoryReportQuery = { __typename: 'Query', inventoryReport: { __typename: 'InventoryReport', inventoryPurchases: string, productSales: string, deposits: string, orderPurchases: string, balanceAtStart: string, balanceAtEnd: string, products: Array<{ __typename: 'InventoryReportProduct', productId: string, productName: string, purchaseUnits: number, purchaseValue: string, salesUnits: number, salesValue: string }> } };
|
|
||||||
|
|
||||||
export type OrderListQueryVariables = Exact<{
|
export type OrderListQueryVariables = Exact<{
|
||||||
limit?: number | null | undefined;
|
limit?: InputMaybe<Scalars['Int']>;
|
||||||
offset?: number | null | undefined;
|
offset?: InputMaybe<Scalars['Int']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type OrderListQuery = { __typename: 'Query', orderList: Array<{ __typename: 'Order', id: string, isExternal: boolean, created: string, orderSum: string, customer: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: string } | null } | null, orderlines: Array<{ __typename: 'OrderLine', id: string, amount: number, unitPrice: string, product: { __typename: 'Product', id: string, name: string, salePriceExt: string, salePriceInt: string } }> }> };
|
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 TransactionListQueryVariables = Exact<{
|
export type TransactionListQueryVariables = Exact<{
|
||||||
limit?: number | null | undefined;
|
limit?: InputMaybe<Scalars['Int']>;
|
||||||
offset?: number | null | undefined;
|
offset?: InputMaybe<Scalars['Int']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type TransactionListQuery = { __typename: 'Query', transactionList: Array<{ __typename: 'Transaction', id: string, created: string, amount: string, user: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: string } | null } }> };
|
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 AllProductsQueryVariables = Exact<{
|
export type AllProductsQueryVariables = Exact<{
|
||||||
active: boolean;
|
active: Scalars['Boolean'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type AllProductsQuery = { __typename: 'Query', allProducts: Array<{ __typename: 'Product', id: string, name: string, imageUrl: string, salePriceExt: string, salePriceInt: string }> };
|
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: string, image: string, lastPurchaseDate: string } | 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: any } | null }> };
|
||||||
|
|
||||||
export type CreateOrderMutationVariables = Exact<{
|
export type CreateOrderMutationVariables = Exact<{
|
||||||
customerId: string | number;
|
customerId: Scalars['ID'];
|
||||||
orderLines: Array<OrderLineInput> | OrderLineInput;
|
orderLines: Array<OrderLineInput> | OrderLineInput;
|
||||||
isExternal: boolean;
|
isExternal: Scalars['Boolean'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type CreateOrderMutation = { __typename: 'Mutation', createOrder:
|
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 } };
|
||||||
| { __typename: 'CreateOrderSuccess', order: { __typename: 'Order', id: string, isExternal: boolean, orderSum: string, customer: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: string } | null } | null, orderlines: Array<{ __typename: 'OrderLine', id: string, amount: number, unitPrice: string, product: { __typename: 'Product', id: string, name: string, salePriceExt: string, salePriceInt: string } }> } }
|
|
||||||
| { __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?: boolean | null | undefined;
|
active?: InputMaybe<Scalars['Boolean']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type AllProductStatsQuery = { __typename: 'Query', allProducts: Array<{ __typename: 'Product', id: string, name: string, userCounts: Array<{ name: string; count: number }> }> };
|
export type AllProductStatsQuery = { __typename?: 'Query', allProducts: Array<{ __typename?: 'Product', id: string, name: string, userCounts: any }> };
|
||||||
|
|
||||||
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 UserStatsQueryVariables = Exact<{ [key: string]: never; }>;
|
|
||||||
|
|
||||||
|
|
||||||
export type UserStatsQuery = { __typename: 'Query', allUsers: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', orderSumTotal: string } | null }> };
|
|
||||||
|
|
||||||
export type UserDetailQueryVariables = Exact<{
|
export type UserDetailQueryVariables = Exact<{
|
||||||
userId: string | number;
|
userId: Scalars['ID'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type UserDetailQuery = { __typename: 'Query', user: { __typename: 'User', id: string, firstName: string, lastName: string, profile: { __typename: 'UserProfile', id: string, balance: string, image: string, lastPurchaseDate: string, orderSumTotal: string, 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: 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 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>;
|
||||||
export const CreateDepositDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateDeposit"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"amount"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createDeposit"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}},{"kind":"Argument","name":{"kind":"Name","value":"amount"},"value":{"kind":"Variable","name":{"kind":"Name","value":"amount"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CreateDepositSuccess"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"transaction"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AboveMaxSaldo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"maxSaldo"}},{"kind":"Field","name":{"kind":"Name","value":"above"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FormErrors"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CreateDepositMutation, CreateDepositMutationVariables>;
|
export const CreateDepositDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateDeposit"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"amount"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createDeposit"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}},{"kind":"Argument","name":{"kind":"Name","value":"amount"},"value":{"kind":"Variable","name":{"kind":"Name","value":"amount"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CreateDepositSuccess"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"transaction"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AboveMaxSaldo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"maxSaldo"}},{"kind":"Field","name":{"kind":"Name","value":"above"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FormErrors"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CreateDepositMutation, CreateDepositMutationVariables>;
|
||||||
export const CreateInventoryTransactionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"createInventoryTransaction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"InventoryTransactionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createInventoryTransaction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CreateInventoryTransactionSuccess"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"inventoryTransaction"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"inventoryAmount"}}]}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FormErrors"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CreateInventoryTransactionMutation, CreateInventoryTransactionMutationVariables>;
|
export const OrderListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"orderList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orderList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"order"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"created"},"value":{"kind":"EnumValue","value":"DESC"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isExternal"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"orderSum"}},{"kind":"Field","name":{"kind":"Name","value":"customer"},"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderlines"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"unitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}}]}}]}}]} as unknown as DocumentNode<OrderListQuery, OrderListQueryVariables>;
|
||||||
export const InventoryPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"inventoryPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"20"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"inventoryAmount"}},{"kind":"Field","name":{"kind":"Name","value":"updated"}},{"kind":"Field","name":{"kind":"Name","value":"wholesaleUnitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"wholesaleValue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inventoryTransactionList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"ordering"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"created"},"value":{"kind":"EnumValue","value":"DESC"}}]}]}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"unitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"comment"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}}]}}]}}]} as unknown as DocumentNode<InventoryPageQuery, InventoryPageQueryVariables>;
|
export const TransactionListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"transactionList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"transactionList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"order"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"created"},"value":{"kind":"EnumValue","value":"DESC"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}}]}}]}}]} as unknown as DocumentNode<TransactionListQuery, TransactionListQueryVariables>;
|
||||||
export const InventoryProductsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"inventoryProducts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"BooleanValue","value":true}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"inventoryAmount"}}]}}]}}]} as unknown as DocumentNode<InventoryProductsQuery, InventoryProductsQueryVariables>;
|
|
||||||
export const InventoryReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"inventoryReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"startTime"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DateTime"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"endTime"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DateTime"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"inventoryReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"startTime"},"value":{"kind":"Variable","name":{"kind":"Name","value":"startTime"}}},{"kind":"Argument","name":{"kind":"Name","value":"endTime"},"value":{"kind":"Variable","name":{"kind":"Name","value":"endTime"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"products"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productId"}},{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"purchaseUnits"}},{"kind":"Field","name":{"kind":"Name","value":"purchaseValue"}},{"kind":"Field","name":{"kind":"Name","value":"salesUnits"}},{"kind":"Field","name":{"kind":"Name","value":"salesValue"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inventoryPurchases"}},{"kind":"Field","name":{"kind":"Name","value":"productSales"}},{"kind":"Field","name":{"kind":"Name","value":"deposits"}},{"kind":"Field","name":{"kind":"Name","value":"orderPurchases"}},{"kind":"Field","name":{"kind":"Name","value":"balanceAtStart"}},{"kind":"Field","name":{"kind":"Name","value":"balanceAtEnd"}}]}}]}}]} as unknown as DocumentNode<InventoryReportQuery, InventoryReportQueryVariables>;
|
|
||||||
export const OrderListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"orderList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orderList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"ordering"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"created"},"value":{"kind":"EnumValue","value":"DESC"}}]}]}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isExternal"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"orderSum"}},{"kind":"Field","name":{"kind":"Name","value":"customer"},"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderlines"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"unitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}}]}}]}}]} as unknown as DocumentNode<OrderListQuery, OrderListQueryVariables>;
|
|
||||||
export const TransactionListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"transactionList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"10"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"transactionList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"ordering"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"created"},"value":{"kind":"EnumValue","value":"DESC"}}]}]}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"created"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}}]}}]}}]} as unknown as DocumentNode<TransactionListQuery, TransactionListQueryVariables>;
|
|
||||||
export const AllProductsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allProducts"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"active"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"Variable","name":{"kind":"Name","value":"active"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"imageUrl"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}}]} as unknown as DocumentNode<AllProductsQuery, AllProductsQueryVariables>;
|
export const AllProductsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allProducts"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"active"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"Variable","name":{"kind":"Name","value":"active"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"imageUrl"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}}]} as unknown as DocumentNode<AllProductsQuery, AllProductsQueryVariables>;
|
||||||
export const AllUsersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allUsers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allUsers"},"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"lastPurchaseDate"}}]}}]}}]}}]} as unknown as DocumentNode<AllUsersQuery, AllUsersQueryVariables>;
|
export const AllUsersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allUsers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allUsers"},"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"lastPurchaseDate"}}]}}]}}]}}]} as unknown as DocumentNode<AllUsersQuery, AllUsersQueryVariables>;
|
||||||
export const CreateOrderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOrder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"customerId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderLines"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"OrderLineInput"}}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isExternal"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOrder"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"customerId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"customerId"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderLines"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderLines"}}},{"kind":"Argument","name":{"kind":"Name","value":"isExternal"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isExternal"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOrderSuccess"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isExternal"}},{"kind":"Field","name":{"kind":"Name","value":"customer"},"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderlines"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"unitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderSum"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"InsufficientFunds"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"amountLacking"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FormErrors"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CreateOrderMutation, CreateOrderMutationVariables>;
|
export const CreateOrderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOrder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"customerId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderLines"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"OrderLineInput"}}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isExternal"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOrder"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"customerId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"customerId"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderLines"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderLines"}}},{"kind":"Argument","name":{"kind":"Name","value":"isExternal"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isExternal"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOrderSuccess"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isExternal"}},{"kind":"Field","name":{"kind":"Name","value":"customer"},"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderlines"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"unitPrice"}},{"kind":"Field","name":{"kind":"Name","value":"product"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceExt"}},{"kind":"Field","name":{"kind":"Name","value":"salePriceInt"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderSum"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"InsufficientFunds"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"amountLacking"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FormErrors"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"fields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CreateOrderMutation, CreateOrderMutationVariables>;
|
||||||
export const AllProductStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allProductStats"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"active"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"Variable","name":{"kind":"Name","value":"active"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userCounts"}}]}}]}}]} as unknown as DocumentNode<AllProductStatsQuery, AllProductStatsQueryVariables>;
|
export const AllProductStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"allProductStats"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"active"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allProducts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"Variable","name":{"kind":"Name","value":"active"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userCounts"}}]}}]}}]} as unknown as DocumentNode<AllProductStatsQuery, AllProductStatsQueryVariables>;
|
||||||
export const OrderStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"orderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"yearly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"monthly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"daily"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"hourly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]} as unknown as DocumentNode<OrderStatsQuery, OrderStatsQueryVariables>;
|
export const OrderStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"orderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"orderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"yearly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"monthly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"daily"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"hourly"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]} as unknown as DocumentNode<OrderStatsQuery, OrderStatsQueryVariables>;
|
||||||
export const ProductStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"productStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}}]}}]} as unknown as DocumentNode<ProductStatsQuery, ProductStatsQueryVariables>;
|
export const ProductStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"productStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}}]}}]} as unknown as DocumentNode<ProductStatsQuery, ProductStatsQueryVariables>;
|
||||||
export const UserStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"userStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allUsers"},"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":"orderSumTotal"}}]}}]}}]}}]} as unknown as DocumentNode<UserStatsQuery, UserStatsQueryVariables>;
|
|
||||||
export const UserDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"userDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}],"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"lastPurchaseDate"}},{"kind":"Field","name":{"kind":"Name","value":"topMonths"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"sum"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"productOrderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderSumTotal"}}]}}]}}]}}]} as unknown as DocumentNode<UserDetailQuery, UserDetailQueryVariables>;
|
export const UserDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"userDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userId"}}}],"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"}},{"kind":"Field","name":{"kind":"Name","value":"balance"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"lastPurchaseDate"}},{"kind":"Field","name":{"kind":"Name","value":"topMonths"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"period"}},{"kind":"Field","name":{"kind":"Name","value":"sum"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"productOrderStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"orderSumTotal"}}]}}]}}]}}]} as unknown as DocumentNode<UserDetailQuery, UserDetailQueryVariables>;
|
||||||
Loaded 100 of 109 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user