Compare commits
2 Commits
e99e042eea
...
be5dafe539
| Author | SHA1 | Date | |
|---|---|---|---|
|
be5dafe539
|
|||
|
6233380682
|
@@ -3,4 +3,4 @@ ROOT_DIR=./data
|
||||
APP_PORT=4321
|
||||
IMAGE=git.atri.dad/atash/chronus:latest
|
||||
JWT_SECRET=some-secret
|
||||
ORIGIN=https://chronus.example.com
|
||||
ORIGIN=https://chronus.example.com
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"bcryptjs": "^3.0.3",
|
||||
"chart.js": "^4.5.1",
|
||||
"daisyui": "^5.5.18",
|
||||
"dotenv": "^17.3.0",
|
||||
"dotenv": "^17.3.1",
|
||||
"drizzle-orm": "0.45.1",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"nanoid": "^5.1.6",
|
||||
@@ -34,7 +34,6 @@
|
||||
"vue-chartjs": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@catppuccin/daisyui": "^2.1.1",
|
||||
"@react-pdf/types": "^2.9.2",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"drizzle-kit": "0.31.9"
|
||||
|
||||
175
pnpm-lock.yaml
generated
@@ -45,8 +45,8 @@ importers:
|
||||
specifier: ^5.5.18
|
||||
version: 5.5.18
|
||||
dotenv:
|
||||
specifier: ^17.3.0
|
||||
version: 17.3.0
|
||||
specifier: ^17.3.1
|
||||
version: 17.3.1
|
||||
drizzle-orm:
|
||||
specifier: 0.45.1
|
||||
version: 0.45.1(@libsql/client@0.17.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.6.0)
|
||||
@@ -69,9 +69,6 @@ importers:
|
||||
specifier: ^5.3.3
|
||||
version: 5.3.3(chart.js@4.5.1)(vue@3.5.28(typescript@5.9.3))
|
||||
devDependencies:
|
||||
'@catppuccin/daisyui':
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1(tailwindcss@4.1.18)
|
||||
'@react-pdf/types':
|
||||
specifier: ^2.9.2
|
||||
version: 2.9.2
|
||||
@@ -285,14 +282,6 @@ packages:
|
||||
resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@catppuccin/daisyui@2.1.1':
|
||||
resolution: {integrity: sha512-PrZttjj8kwfDBJ34sR+DN25Xtjvxx4T5p8uu/iiGYZR8UOsNwzMlO/alYDBwwTOLzP1NKLNRax09kCT39+QM+A==}
|
||||
peerDependencies:
|
||||
tailwindcss: ^4.0.17
|
||||
|
||||
'@catppuccin/palette@1.7.1':
|
||||
resolution: {integrity: sha512-aRc1tbzrevOTV7nFTT9SRdF26w/MIwT4Jwt4fDMc9itRZUDXCuEDBLyz4TQMlqO9ZP8mf5Hu4Jr6D03NLFc6Gw==}
|
||||
|
||||
'@ceereals/vue-pdf@0.2.1':
|
||||
resolution: {integrity: sha512-E7Y2GyHTYEmZ2U5ZlVuJrOWdHhco49ZTdKVOo/wcOhlfNFG+W5pAZ6rOcaua+owspC4BgGzAxlmqj/jdEM9ehA==}
|
||||
peerDependencies:
|
||||
@@ -810,105 +799,89 @@ packages:
|
||||
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.4':
|
||||
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-riscv64@1.2.4':
|
||||
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.2.4':
|
||||
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.34.5':
|
||||
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-ppc64@0.34.5':
|
||||
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-riscv64@0.34.5':
|
||||
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.5':
|
||||
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.34.5':
|
||||
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
|
||||
@@ -1021,35 +994,35 @@ packages:
|
||||
'@peculiar/asn1-android@2.6.0':
|
||||
resolution: {integrity: sha512-cBRCKtYPF7vJGN76/yG8VbxRcHLPF3HnkoHhKOZeHpoVtbMYfY9ROKtH3DtYUY9m8uI1Mh47PRhHf2hSK3xcSQ==}
|
||||
|
||||
'@peculiar/asn1-cms@2.6.0':
|
||||
resolution: {integrity: sha512-2uZqP+ggSncESeUF/9Su8rWqGclEfEiz1SyU02WX5fUONFfkjzS2Z/F1Li0ofSmf4JqYXIOdCAZqIXAIBAT1OA==}
|
||||
'@peculiar/asn1-cms@2.6.1':
|
||||
resolution: {integrity: sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==}
|
||||
|
||||
'@peculiar/asn1-csr@2.6.0':
|
||||
resolution: {integrity: sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ==}
|
||||
'@peculiar/asn1-csr@2.6.1':
|
||||
resolution: {integrity: sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==}
|
||||
|
||||
'@peculiar/asn1-ecc@2.6.0':
|
||||
resolution: {integrity: sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==}
|
||||
'@peculiar/asn1-ecc@2.6.1':
|
||||
resolution: {integrity: sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==}
|
||||
|
||||
'@peculiar/asn1-pfx@2.6.0':
|
||||
resolution: {integrity: sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ==}
|
||||
'@peculiar/asn1-pfx@2.6.1':
|
||||
resolution: {integrity: sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==}
|
||||
|
||||
'@peculiar/asn1-pkcs8@2.6.0':
|
||||
resolution: {integrity: sha512-KyQ4D8G/NrS7Fw3XCJrngxmjwO/3htnA0lL9gDICvEQ+GJ+EPFqldcJQTwPIdvx98Tua+WjkdKHSC0/Km7T+lA==}
|
||||
'@peculiar/asn1-pkcs8@2.6.1':
|
||||
resolution: {integrity: sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==}
|
||||
|
||||
'@peculiar/asn1-pkcs9@2.6.0':
|
||||
resolution: {integrity: sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw==}
|
||||
'@peculiar/asn1-pkcs9@2.6.1':
|
||||
resolution: {integrity: sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==}
|
||||
|
||||
'@peculiar/asn1-rsa@2.6.0':
|
||||
resolution: {integrity: sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==}
|
||||
'@peculiar/asn1-rsa@2.6.1':
|
||||
resolution: {integrity: sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==}
|
||||
|
||||
'@peculiar/asn1-schema@2.6.0':
|
||||
resolution: {integrity: sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==}
|
||||
|
||||
'@peculiar/asn1-x509-attr@2.6.0':
|
||||
resolution: {integrity: sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA==}
|
||||
'@peculiar/asn1-x509-attr@2.6.1':
|
||||
resolution: {integrity: sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==}
|
||||
|
||||
'@peculiar/asn1-x509@2.6.0':
|
||||
resolution: {integrity: sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==}
|
||||
'@peculiar/asn1-x509@2.6.1':
|
||||
resolution: {integrity: sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==}
|
||||
|
||||
'@peculiar/x509@1.14.3':
|
||||
resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==}
|
||||
@@ -1140,79 +1113,66 @@ packages:
|
||||
resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.57.1':
|
||||
resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.57.1':
|
||||
resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.57.1':
|
||||
resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.57.1':
|
||||
resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.57.1':
|
||||
resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.57.1':
|
||||
resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.57.1':
|
||||
resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.57.1':
|
||||
resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==}
|
||||
@@ -1313,28 +1273,24 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
|
||||
resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
|
||||
resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
|
||||
resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
|
||||
resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
|
||||
@@ -1926,8 +1882,8 @@ packages:
|
||||
domutils@3.2.2:
|
||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||
|
||||
dotenv@17.3.0:
|
||||
resolution: {integrity: sha512-i3z5dx/8F45f+Dj0B/qG8oKip9luzyHz6dfJMOKG7zQW/12tT7CrIjs/0J10uNK/Z5O7O0UtfEmx6yFKRQCl4g==}
|
||||
dotenv@17.3.1:
|
||||
resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
drizzle-kit@0.31.9:
|
||||
@@ -2492,28 +2448,24 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
||||
@@ -3295,8 +3247,8 @@ packages:
|
||||
unified@11.0.5:
|
||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||
|
||||
unifont@0.7.3:
|
||||
resolution: {integrity: sha512-b0GtQzKCyuSHGsfj5vyN8st7muZ6VCI4XD4vFlr7Uy1rlWVYxC3npnfk8MyreHxJYrz1ooLDqDzFe9XqQTlAhA==}
|
||||
unifont@0.7.4:
|
||||
resolution: {integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==}
|
||||
|
||||
unist-util-find-after@5.0.0:
|
||||
resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
|
||||
@@ -4031,13 +3983,6 @@ snapshots:
|
||||
dependencies:
|
||||
fontkitten: 1.0.2
|
||||
|
||||
'@catppuccin/daisyui@2.1.1(tailwindcss@4.1.18)':
|
||||
dependencies:
|
||||
'@catppuccin/palette': 1.7.1
|
||||
tailwindcss: 4.1.18
|
||||
|
||||
'@catppuccin/palette@1.7.1': {}
|
||||
|
||||
'@ceereals/vue-pdf@0.2.1(vue@3.5.28(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@react-pdf/fns': 3.1.2
|
||||
@@ -4510,59 +4455,59 @@ snapshots:
|
||||
asn1js: 3.0.7
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-cms@2.6.0':
|
||||
'@peculiar/asn1-cms@2.6.1':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.0
|
||||
'@peculiar/asn1-x509-attr': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.1
|
||||
'@peculiar/asn1-x509-attr': 2.6.1
|
||||
asn1js: 3.0.7
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-csr@2.6.0':
|
||||
'@peculiar/asn1-csr@2.6.1':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.1
|
||||
asn1js: 3.0.7
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-ecc@2.6.0':
|
||||
'@peculiar/asn1-ecc@2.6.1':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.1
|
||||
asn1js: 3.0.7
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-pfx@2.6.0':
|
||||
'@peculiar/asn1-pfx@2.6.1':
|
||||
dependencies:
|
||||
'@peculiar/asn1-cms': 2.6.0
|
||||
'@peculiar/asn1-pkcs8': 2.6.0
|
||||
'@peculiar/asn1-rsa': 2.6.0
|
||||
'@peculiar/asn1-cms': 2.6.1
|
||||
'@peculiar/asn1-pkcs8': 2.6.1
|
||||
'@peculiar/asn1-rsa': 2.6.1
|
||||
'@peculiar/asn1-schema': 2.6.0
|
||||
asn1js: 3.0.7
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-pkcs8@2.6.0':
|
||||
'@peculiar/asn1-pkcs8@2.6.1':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.1
|
||||
asn1js: 3.0.7
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-pkcs9@2.6.0':
|
||||
'@peculiar/asn1-pkcs9@2.6.1':
|
||||
dependencies:
|
||||
'@peculiar/asn1-cms': 2.6.0
|
||||
'@peculiar/asn1-pfx': 2.6.0
|
||||
'@peculiar/asn1-pkcs8': 2.6.0
|
||||
'@peculiar/asn1-cms': 2.6.1
|
||||
'@peculiar/asn1-pfx': 2.6.1
|
||||
'@peculiar/asn1-pkcs8': 2.6.1
|
||||
'@peculiar/asn1-schema': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.0
|
||||
'@peculiar/asn1-x509-attr': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.1
|
||||
'@peculiar/asn1-x509-attr': 2.6.1
|
||||
asn1js: 3.0.7
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-rsa@2.6.0':
|
||||
'@peculiar/asn1-rsa@2.6.1':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.1
|
||||
asn1js: 3.0.7
|
||||
tslib: 2.8.1
|
||||
|
||||
@@ -4572,14 +4517,14 @@ snapshots:
|
||||
pvtsutils: 1.3.6
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-x509-attr@2.6.0':
|
||||
'@peculiar/asn1-x509-attr@2.6.1':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.1
|
||||
asn1js: 3.0.7
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-x509@2.6.0':
|
||||
'@peculiar/asn1-x509@2.6.1':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.6.0
|
||||
asn1js: 3.0.7
|
||||
@@ -4588,13 +4533,13 @@ snapshots:
|
||||
|
||||
'@peculiar/x509@1.14.3':
|
||||
dependencies:
|
||||
'@peculiar/asn1-cms': 2.6.0
|
||||
'@peculiar/asn1-csr': 2.6.0
|
||||
'@peculiar/asn1-ecc': 2.6.0
|
||||
'@peculiar/asn1-pkcs9': 2.6.0
|
||||
'@peculiar/asn1-rsa': 2.6.0
|
||||
'@peculiar/asn1-cms': 2.6.1
|
||||
'@peculiar/asn1-csr': 2.6.1
|
||||
'@peculiar/asn1-ecc': 2.6.1
|
||||
'@peculiar/asn1-pkcs9': 2.6.1
|
||||
'@peculiar/asn1-rsa': 2.6.1
|
||||
'@peculiar/asn1-schema': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.1
|
||||
pvtsutils: 1.3.6
|
||||
reflect-metadata: 0.2.2
|
||||
tslib: 2.8.1
|
||||
@@ -4807,10 +4752,10 @@ snapshots:
|
||||
'@hexagon/base64': 1.1.28
|
||||
'@levischuck/tiny-cbor': 0.2.11
|
||||
'@peculiar/asn1-android': 2.6.0
|
||||
'@peculiar/asn1-ecc': 2.6.0
|
||||
'@peculiar/asn1-rsa': 2.6.0
|
||||
'@peculiar/asn1-ecc': 2.6.1
|
||||
'@peculiar/asn1-rsa': 2.6.1
|
||||
'@peculiar/asn1-schema': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.0
|
||||
'@peculiar/asn1-x509': 2.6.1
|
||||
'@peculiar/x509': 1.14.3
|
||||
|
||||
'@swc/helpers@0.5.18':
|
||||
@@ -5250,7 +5195,7 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
tsconfck: 3.1.6(typescript@5.9.3)
|
||||
ultrahtml: 1.6.0
|
||||
unifont: 0.7.3
|
||||
unifont: 0.7.4
|
||||
unist-util-visit: 5.1.0
|
||||
unstorage: 1.17.4
|
||||
vfile: 6.0.3
|
||||
@@ -5603,7 +5548,7 @@ snapshots:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
|
||||
dotenv@17.3.0: {}
|
||||
dotenv@17.3.1: {}
|
||||
|
||||
drizzle-kit@0.31.9:
|
||||
dependencies:
|
||||
@@ -7245,7 +7190,7 @@ snapshots:
|
||||
trough: 2.2.0
|
||||
vfile: 6.0.3
|
||||
|
||||
unifont@0.7.3:
|
||||
unifont@0.7.4:
|
||||
dependencies:
|
||||
css-tree: 3.1.0
|
||||
ofetch: 1.5.1
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 732 B After Width: | Height: | Size: 744 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
BIN
public/logo.webp
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 530 KiB |
@@ -9,7 +9,7 @@ const initial = name ? name.charAt(0).toUpperCase() : '?';
|
||||
---
|
||||
|
||||
<div class:list={["avatar placeholder", className]}>
|
||||
<div class="bg-primary/15 text-primary w-9 h-9 rounded-full flex items-center justify-center">
|
||||
<div class="bg-base-300 text-primary w-9 h-9 rounded-full flex items-center justify-center">
|
||||
<span class="text-sm font-semibold">{initial}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -148,7 +148,7 @@ function clearForm() {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="card bg-base-200/50 backdrop-blur-sm shadow-lg border border-base-300/50 hover:border-base-300 transition-all duration-200"
|
||||
class="card bg-base-200 backdrop-blur-sm shadow-lg border border-base-content/20 hover:border-base-content/20 transition-all duration-200"
|
||||
>
|
||||
<div class="card-body gap-6">
|
||||
<div class="flex justify-between items-center">
|
||||
@@ -183,7 +183,7 @@ function clearForm() {
|
||||
<select
|
||||
id="manual-client"
|
||||
v-model="selectedClientId"
|
||||
class="select select-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
|
||||
class="select select-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
<option value="">Select a client...</option>
|
||||
@@ -203,7 +203,7 @@ function clearForm() {
|
||||
id="manual-start-date"
|
||||
v-model="startDate"
|
||||
type="date"
|
||||
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
|
||||
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
|
||||
:disabled="isSubmitting"
|
||||
/>
|
||||
</div>
|
||||
@@ -216,7 +216,7 @@ function clearForm() {
|
||||
id="manual-start-time"
|
||||
v-model="startTime"
|
||||
type="time"
|
||||
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
|
||||
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
|
||||
:disabled="isSubmitting"
|
||||
/>
|
||||
</div>
|
||||
@@ -232,7 +232,7 @@ function clearForm() {
|
||||
id="manual-end-date"
|
||||
v-model="endDate"
|
||||
type="date"
|
||||
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
|
||||
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
|
||||
:disabled="isSubmitting"
|
||||
/>
|
||||
</div>
|
||||
@@ -245,7 +245,7 @@ function clearForm() {
|
||||
id="manual-end-time"
|
||||
v-model="endTime"
|
||||
type="time"
|
||||
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
|
||||
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
|
||||
:disabled="isSubmitting"
|
||||
/>
|
||||
</div>
|
||||
@@ -261,7 +261,7 @@ function clearForm() {
|
||||
v-model="description"
|
||||
type="text"
|
||||
placeholder="What did you work on?"
|
||||
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
|
||||
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
|
||||
:disabled="isSubmitting"
|
||||
/>
|
||||
</div>
|
||||
@@ -278,7 +278,7 @@ function clearForm() {
|
||||
'badge badge-lg cursor-pointer transition-all hover:scale-105',
|
||||
selectedTagId === tag.id
|
||||
? 'badge-primary shadow-lg shadow-primary/20'
|
||||
: 'badge-outline hover:bg-base-300/50',
|
||||
: 'badge-outline hover:bg-base-300',
|
||||
]"
|
||||
:disabled="isSubmitting"
|
||||
type="button"
|
||||
|
||||
@@ -18,12 +18,12 @@ const { title, value, description, icon, color = 'text-primary', valueClass } =
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs font-medium uppercase tracking-wider text-base-content/60">{title}</span>
|
||||
{icon && (
|
||||
<div class:list={[color, "opacity-40"]}>
|
||||
<div class:list={[color, "opacity-70"]}>
|
||||
<Icon name={icon} class="w-5 h-5" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div class:list={["text-2xl font-bold", color, valueClass]}>{value}</div>
|
||||
{description && <div class="text-xs text-base-content/50">{description}</div>}
|
||||
{description && <div class="text-xs text-base-content/60">{description}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,34 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Icon from './Icon.vue';
|
||||
import { ref, onMounted } from "vue";
|
||||
import Icon from "./Icon.vue";
|
||||
|
||||
const theme = ref('macchiato');
|
||||
const theme = ref("sunset");
|
||||
|
||||
onMounted(() => {
|
||||
const stored = localStorage.getItem('theme');
|
||||
const stored = localStorage.getItem("theme");
|
||||
if (stored) {
|
||||
theme.value = stored;
|
||||
document.documentElement.setAttribute('data-theme', stored);
|
||||
document.documentElement.setAttribute("data-theme", stored);
|
||||
}
|
||||
});
|
||||
|
||||
function toggleTheme() {
|
||||
const newTheme = theme.value === 'macchiato' ? 'latte' : 'macchiato';
|
||||
const newTheme = theme.value === "sunset" ? "winter" : "sunset";
|
||||
theme.value = newTheme;
|
||||
document.documentElement.setAttribute('data-theme', newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
document.documentElement.setAttribute("data-theme", newTheme);
|
||||
localStorage.setItem("theme", newTheme);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
@click="toggleTheme"
|
||||
<button
|
||||
@click="toggleTheme"
|
||||
class="btn btn-ghost btn-circle"
|
||||
aria-label="Toggle Theme"
|
||||
>
|
||||
<Icon
|
||||
:name="theme === 'macchiato' ? 'moon' : 'sun'"
|
||||
class="w-5 h-5"
|
||||
/>
|
||||
<Icon :name="theme === 'sunset' ? 'moon' : 'sun'" class="w-5 h-5" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -118,7 +118,7 @@ async function stopTimer() {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="card bg-base-200/50 backdrop-blur-sm shadow-lg border border-base-300/50 mb-6 hover:border-base-300 transition-all duration-200"
|
||||
class="card bg-base-200 backdrop-blur-sm shadow-lg border border-base-content/20 mb-6 hover:border-base-content/20 transition-all duration-200"
|
||||
>
|
||||
<div class="card-body gap-6">
|
||||
<!-- Client Row -->
|
||||
@@ -129,7 +129,7 @@ async function stopTimer() {
|
||||
<select
|
||||
id="timer-client"
|
||||
v-model="selectedClientId"
|
||||
class="select select-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
|
||||
class="select select-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
|
||||
:disabled="isRunning"
|
||||
>
|
||||
<option value="">Select a client...</option>
|
||||
@@ -149,7 +149,7 @@ async function stopTimer() {
|
||||
v-model="description"
|
||||
type="text"
|
||||
placeholder="What are you working on?"
|
||||
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
|
||||
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
|
||||
:disabled="isRunning"
|
||||
/>
|
||||
</div>
|
||||
@@ -166,7 +166,7 @@ async function stopTimer() {
|
||||
'badge badge-lg cursor-pointer transition-all hover:scale-105',
|
||||
selectedTagId === tag.id
|
||||
? 'badge-primary shadow-lg shadow-primary/20'
|
||||
: 'badge-outline hover:bg-base-300/50',
|
||||
: 'badge-outline hover:bg-base-300',
|
||||
]"
|
||||
:disabled="isRunning"
|
||||
type="button"
|
||||
|
||||
@@ -107,7 +107,7 @@ function closeShowTokenModal() {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
|
||||
<div class="card bg-base-100 shadow-xl border border-base-content/20 mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="card-title text-lg sm:text-xl">
|
||||
|
||||
@@ -94,7 +94,7 @@ async function deletePasskey(id: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
|
||||
<div class="card bg-base-100 shadow-xl border border-base-content/20 mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="card-title text-lg sm:text-xl">
|
||||
|
||||
@@ -86,7 +86,7 @@ async function changePassword() {
|
||||
<span>{{ message.text }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
|
||||
<div class="card bg-base-100 shadow-xl border border-base-content/20 mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title mb-6 text-lg sm:text-xl">
|
||||
<Icon name="key" class="w-5 h-5 sm:w-6 sm:h-6" />
|
||||
|
||||
@@ -71,7 +71,7 @@ async function updateProfile() {
|
||||
<span>{{ message.text }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
|
||||
<div class="card bg-base-100 shadow-xl border border-base-content/20 mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title mb-6 text-lg sm:text-xl">
|
||||
<Icon name="user-circle" class="w-5 h-5 sm:w-6 sm:h-6" />
|
||||
|
||||
@@ -28,7 +28,6 @@ const userMemberships = await db.select({
|
||||
.all();
|
||||
|
||||
const currentTeamId = Astro.cookies.get('currentTeamId')?.value || userMemberships[0]?.organization.id;
|
||||
const currentTeam = userMemberships.find(m => m.organization.id === currentTeamId);
|
||||
|
||||
const navItems = [
|
||||
{ href: '/dashboard', label: 'Dashboard', icon: 'home', exact: true },
|
||||
@@ -54,8 +53,8 @@ function isActive(item: { href: string; exact?: boolean }) {
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
<script>
|
||||
const theme = localStorage.getItem('theme') || 'macchiato';
|
||||
<script is:inline>
|
||||
const theme = localStorage.getItem('theme') || 'sunset';
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
</script>
|
||||
</head>
|
||||
@@ -64,7 +63,7 @@ function isActive(item: { href: string; exact?: boolean }) {
|
||||
<input id="my-drawer-2" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content flex flex-col h-full overflow-auto">
|
||||
<!-- Mobile Navbar -->
|
||||
<div class="navbar bg-base-100 sticky top-0 z-50 lg:hidden border-b border-base-200">
|
||||
<div class="navbar bg-base-100 sticky top-0 z-50 lg:hidden border-b border-base-content/20">
|
||||
<div class="flex-none">
|
||||
<label for="my-drawer-2" aria-label="open sidebar" class="btn btn-square btn-ghost btn-sm">
|
||||
<Icon name="bars-3" class="w-5 h-5" />
|
||||
@@ -87,7 +86,7 @@ function isActive(item: { href: string; exact?: boolean }) {
|
||||
|
||||
<div class="drawer-side z-50">
|
||||
<label for="my-drawer-2" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<aside class="bg-base-200 min-h-full w-72 flex flex-col border-r border-base-300/40">
|
||||
<aside class="bg-base-200 min-h-full w-72 flex flex-col border-r border-base-content/20">
|
||||
<!-- Logo -->
|
||||
<div class="px-5 pt-5 pb-3">
|
||||
<a href="/dashboard" class="flex items-center gap-2.5 group">
|
||||
@@ -100,7 +99,7 @@ function isActive(item: { href: string; exact?: boolean }) {
|
||||
{userMemberships.length > 0 && (
|
||||
<div class="px-4 pb-2">
|
||||
<select
|
||||
class="select select-sm w-full bg-base-300/40 border-base-300/60 focus:border-primary/50 focus:outline-none text-sm font-medium"
|
||||
class="select select-sm w-full bg-base-300 border-base-content/20 focus:border-primary focus:outline-none text-sm font-medium"
|
||||
id="team-switcher"
|
||||
aria-label="Switch team"
|
||||
>
|
||||
@@ -135,8 +134,8 @@ function isActive(item: { href: string; exact?: boolean }) {
|
||||
<a href={item.href} class:list={[
|
||||
"rounded-lg gap-3 px-3 py-2.5 font-medium text-sm",
|
||||
isActive(item)
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-base-content/70 hover:text-base-content hover:bg-base-300/50"
|
||||
? "bg-primary text-primary-content"
|
||||
: "text-base-content/70 hover:text-base-content hover:bg-base-300"
|
||||
]}>
|
||||
<Icon name={item.icon} class="w-[18px] h-[18px]" />
|
||||
{item.label}
|
||||
@@ -153,8 +152,8 @@ function isActive(item: { href: string; exact?: boolean }) {
|
||||
<a href="/admin" class:list={[
|
||||
"rounded-lg gap-3 px-3 py-2.5 font-medium text-sm",
|
||||
Astro.url.pathname.startsWith("/admin")
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-base-content/70 hover:text-base-content hover:bg-base-300/50"
|
||||
? "bg-primary text-primary-content"
|
||||
: "text-base-content/70 hover:text-base-content hover:bg-base-300"
|
||||
]}>
|
||||
<Icon name="cog-6-tooth" class="w-[18px] h-[18px]" />
|
||||
Site Admin
|
||||
@@ -166,25 +165,25 @@ function isActive(item: { href: string; exact?: boolean }) {
|
||||
</nav>
|
||||
|
||||
<!-- Bottom Section -->
|
||||
<div class="mt-auto border-t border-base-300/40">
|
||||
<div class="mt-auto border-t border-base-content/20">
|
||||
<div class="p-3">
|
||||
<a href="/dashboard/settings" class="flex items-center gap-3 rounded-lg p-2.5 hover:bg-base-300/40 group">
|
||||
<a href="/dashboard/settings" class="flex items-center gap-3 rounded-lg p-2.5 hover:bg-base-300 group">
|
||||
<Avatar name={user.name} />
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-medium text-sm truncate">{user.name}</div>
|
||||
<div class="text-xs text-base-content/50 truncate">{user.email}</div>
|
||||
<div class="text-xs text-base-content/60 truncate">{user.email}</div>
|
||||
</div>
|
||||
<Icon name="chevron-right" class="w-4 h-4 text-base-content/30 group-hover:text-base-content/50" />
|
||||
<Icon name="chevron-right" class="w-4 h-4 text-base-content/50 group-hover:text-base-content/70" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between px-5 pb-2">
|
||||
<span class="text-xs text-base-content/40 font-medium">Theme</span>
|
||||
<span class="text-xs text-base-content/60 font-medium">Theme</span>
|
||||
<ThemeToggle client:load />
|
||||
</div>
|
||||
|
||||
<div class="px-3 pb-3">
|
||||
<button id="logout-btn" type="button" class="btn btn-ghost btn-sm btn-block justify-start gap-2 text-base-content/60 hover:text-error hover:bg-error/10 font-medium">
|
||||
<button id="logout-btn" type="button" class="btn btn-ghost btn-sm btn-block justify-start gap-2 text-base-content/60 hover:text-error hover:bg-base-300 font-medium">
|
||||
<Icon name="arrow-right-on-rectangle" class="w-[18px] h-[18px]" />
|
||||
Logout
|
||||
</button>
|
||||
|
||||
@@ -17,8 +17,8 @@ const { title } = Astro.props;
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
<script>
|
||||
const theme = localStorage.getItem('theme') || 'macchiato';
|
||||
<script is:inline>
|
||||
const theme = localStorage.getItem('theme') || 'sunset';
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@@ -32,7 +32,7 @@ const allUsers = await db.select().from(users).all();
|
||||
title="Total Users"
|
||||
value={String(allUsers.length)}
|
||||
description="Registered accounts"
|
||||
icon="heroicons:users"
|
||||
icon="users"
|
||||
color="text-primary"
|
||||
/>
|
||||
</div>
|
||||
@@ -63,7 +63,7 @@ const allUsers = await db.select().from(users).all();
|
||||
<!-- Users List -->
|
||||
<div class="card card-border bg-base-100">
|
||||
<div class="card-body p-0">
|
||||
<div class="px-4 py-3 border-b border-base-200">
|
||||
<div class="px-4 py-3 border-b border-base-content/20">
|
||||
<h2 class="text-sm font-semibold">All Users</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
@@ -93,7 +93,7 @@ const allUsers = await db.select().from(users).all();
|
||||
<span class="badge badge-xs badge-ghost">No</span>
|
||||
)}
|
||||
</td>
|
||||
<td class="text-base-content/40">{u.createdAt?.toLocaleDateString() ?? 'N/A'}</td>
|
||||
<td class="text-base-content/60">{u.createdAt?.toLocaleDateString() ?? 'N/A'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -31,7 +31,7 @@ const allClients = await db.select()
|
||||
<div class="card-body p-4 gap-1">
|
||||
<h2 class="font-semibold">{client.name}</h2>
|
||||
{client.email && <p class="text-sm text-base-content/60">{client.email}</p>}
|
||||
<p class="text-xs text-base-content/40">Created {client.createdAt?.toLocaleDateString() ?? 'N/A'}</p>
|
||||
<p class="text-xs text-base-content/60">Created {client.createdAt?.toLocaleDateString() ?? 'N/A'}</p>
|
||||
<div class="card-actions justify-end mt-3">
|
||||
<a href={`/dashboard/clients/${client.id}`} class="btn btn-xs btn-ghost">View</a>
|
||||
<a href={`/dashboard/clients/${client.id}/edit`} class="btn btn-xs btn-primary">Edit</a>
|
||||
@@ -43,7 +43,7 @@ const allClients = await db.select()
|
||||
|
||||
{allClients.length === 0 && (
|
||||
<div class="flex flex-col items-center justify-center py-12 text-center">
|
||||
<p class="text-base-content/50 text-sm mb-4">No clients yet</p>
|
||||
<p class="text-base-content/60 text-sm mb-4">No clients yet</p>
|
||||
<a href="/dashboard/clients/new" class="btn btn-primary btn-sm">Add Your First Client</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -75,7 +75,7 @@ if (!client) return Astro.redirect('/dashboard/clients');
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<div class="divider text-xs text-base-content/40">Address Details</div>
|
||||
<div class="divider text-xs text-base-content/60">Address Details</div>
|
||||
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-xs">Street Address (optional)</legend>
|
||||
|
||||
@@ -149,7 +149,7 @@ const totalEntriesCount = totalEntriesResult?.count || 0;
|
||||
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<div class="text-xs text-base-content/40">Created</div>
|
||||
<div class="text-xs text-base-content/60">Created</div>
|
||||
<div class="text-sm">{client.createdAt?.toLocaleDateString() ?? 'N/A'}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,7 +160,7 @@ const totalEntriesCount = totalEntriesResult?.count || 0;
|
||||
<!-- Recent Activity -->
|
||||
<div class="card card-border bg-base-100">
|
||||
<div class="card-body p-0">
|
||||
<div class="px-4 py-3 border-b border-base-200">
|
||||
<div class="px-4 py-3 border-b border-base-content/20">
|
||||
<h2 class="text-sm font-semibold">Recent Activity</h2>
|
||||
</div>
|
||||
|
||||
@@ -191,7 +191,7 @@ const totalEntriesCount = totalEntriesResult?.count || 0;
|
||||
) : '-'}
|
||||
</td>
|
||||
<td class="text-base-content/60">{entryUser?.name || 'Unknown'}</td>
|
||||
<td class="text-base-content/40">{entry.startTime.toLocaleDateString()}</td>
|
||||
<td class="text-base-content/60">{entry.startTime.toLocaleDateString()}</td>
|
||||
<td class="font-mono">{formatTimeRange(entry.startTime, entry.endTime)}</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -199,13 +199,13 @@ const totalEntriesCount = totalEntriesResult?.count || 0;
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div class="text-center py-8 text-base-content/40 text-sm">
|
||||
<div class="text-center py-8 text-base-content/60 text-sm">
|
||||
No time entries recorded for this client yet.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{recentEntries.length > 0 && (
|
||||
<div class="flex justify-center py-3 border-t border-base-200">
|
||||
<div class="flex justify-center py-3 border-t border-base-content/20">
|
||||
<a href={`/dashboard/tracker?client=${client.id}`} class="btn btn-ghost btn-xs">
|
||||
View All Entries
|
||||
</a>
|
||||
|
||||
@@ -45,7 +45,7 @@ if (!user) return Astro.redirect('/login');
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<div class="divider text-xs text-base-content/40">Address Details</div>
|
||||
<div class="divider text-xs text-base-content/60">Address Details</div>
|
||||
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-xs">Street Address (optional)</legend>
|
||||
|
||||
@@ -201,9 +201,9 @@ const hasMembership = userOrgs.length > 0;
|
||||
{stats.recentEntries.length > 0 ? (
|
||||
<ul class="space-y-2 mt-3">
|
||||
{stats.recentEntries.map(({ entry, client, tag }) => (
|
||||
<ColorDot client:load as="li" color={tag?.color || 'oklch(var(--p))'} borderColor class="p-2.5 rounded-lg bg-base-200/50 border-l-3 hover:bg-base-200 transition-colors">
|
||||
<ColorDot client:load as="li" color={tag?.color || 'oklch(var(--p))'} borderColor class="p-2.5 rounded-lg bg-base-200 border-l-3 hover:bg-base-300 transition-colors">
|
||||
<div class="font-medium text-sm">{client.name}</div>
|
||||
<div class="text-xs text-base-content/50 mt-0.5 flex flex-wrap gap-2 items-center">
|
||||
<div class="text-xs text-base-content/60 mt-0.5 flex flex-wrap gap-2 items-center">
|
||||
<span class="flex gap-1 flex-wrap">
|
||||
{tag ? (
|
||||
<span class="badge badge-xs badge-outline">{tag.name}</span>
|
||||
@@ -216,8 +216,8 @@ const hasMembership = userOrgs.length > 0;
|
||||
</ul>
|
||||
) : (
|
||||
<div class="flex flex-col items-center justify-center py-6 text-center mt-3">
|
||||
<Icon name="clock" class="w-10 h-10 text-base-content/15 mb-2" />
|
||||
<p class="text-base-content/40 text-sm">No recent time entries</p>
|
||||
<Icon name="clock" class="w-10 h-10 text-base-content/30 mb-2" />
|
||||
<p class="text-base-content/60 text-sm">No recent time entries</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -113,10 +113,10 @@ const isDraft = invoice.status === 'draft';
|
||||
</form>
|
||||
)}
|
||||
<div class="dropdown dropdown-end">
|
||||
<div role="button" tabindex="0" class="btn btn-square btn-ghost btn-sm border border-base-200">
|
||||
<div role="button" tabindex="0" class="btn btn-square btn-ghost btn-sm border border-base-content/20">
|
||||
<Icon name="ellipsis-horizontal" class="w-4 h-4" />
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content z-1 menu p-2 bg-base-100 rounded-box w-52 border border-base-200">
|
||||
<ul tabindex="0" class="dropdown-content z-1 menu p-2 bg-base-100 rounded-box w-52 border border-base-content/20">
|
||||
<li>
|
||||
<a href={`/dashboard/invoices/${invoice.id}/edit`}>
|
||||
<Icon name="pencil-square" class="w-4 h-4" />
|
||||
@@ -174,7 +174,7 @@ const isDraft = invoice.status === 'draft';
|
||||
)}
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-4xl font-light text-base-content/30 uppercase tracking-widest mb-4">
|
||||
<div class="text-4xl font-light text-base-content/50 uppercase tracking-widest mb-4">
|
||||
{invoice.type}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-1 text-sm">
|
||||
@@ -190,7 +190,7 @@ const isDraft = invoice.status === 'draft';
|
||||
|
||||
<!-- Bill To -->
|
||||
<div class="mb-12">
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-base-content/40 mb-2">Bill To</div>
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-base-content/60 mb-2">Bill To</div>
|
||||
{client ? (
|
||||
<div>
|
||||
<div class="font-bold text-lg">{client.name}</div>
|
||||
@@ -209,7 +209,7 @@ const isDraft = invoice.status === 'draft';
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div class="italic text-base-content/40">Client deleted</div>
|
||||
<div class="italic text-base-content/60">Client deleted</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -218,7 +218,7 @@ const isDraft = invoice.status === 'draft';
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full min-w-150">
|
||||
<thead>
|
||||
<tr class="border-b-2 border-base-200 text-left text-xs font-bold uppercase tracking-wider text-base-content/40">
|
||||
<tr class="border-b-2 border-base-content/20 text-left text-xs font-bold uppercase tracking-wider text-base-content/60">
|
||||
<th class="py-3">Description</th>
|
||||
<th class="py-3 text-right w-24">Qty</th>
|
||||
<th class="py-3 text-right w-32">Price</th>
|
||||
@@ -226,7 +226,7 @@ const isDraft = invoice.status === 'draft';
|
||||
{isDraft && <th class="py-3 w-10"></th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-base-200">
|
||||
<tbody class="divide-y divide-base-content/20">
|
||||
{items.map(item => (
|
||||
<tr>
|
||||
<td class="py-4">{item.description}</td>
|
||||
@@ -237,7 +237,7 @@ const isDraft = invoice.status === 'draft';
|
||||
<td class="py-4 text-right">
|
||||
<form method="POST" action={`/api/invoices/${invoice.id}/items/delete`}>
|
||||
<input type="hidden" name="itemId" value={item.id} />
|
||||
<button type="submit" class="btn btn-ghost btn-xs btn-square text-error opacity-50 hover:opacity-100">
|
||||
<button type="submit" class="btn btn-ghost btn-xs btn-square text-error opacity-70 hover:opacity-100">
|
||||
<Icon name="trash" class="w-4 h-4" />
|
||||
</button>
|
||||
</form>
|
||||
@@ -247,7 +247,7 @@ const isDraft = invoice.status === 'draft';
|
||||
))}
|
||||
{items.length === 0 && (
|
||||
<tr>
|
||||
<td colspan={isDraft ? 5 : 4} class="py-8 text-center text-base-content/40 italic">
|
||||
<td colspan={isDraft ? 5 : 4} class="py-8 text-center text-base-content/60 italic">
|
||||
No items added yet.
|
||||
</td>
|
||||
</tr>
|
||||
@@ -266,7 +266,7 @@ const isDraft = invoice.status === 'draft';
|
||||
</ModalButton>
|
||||
</div>
|
||||
|
||||
<form method="POST" action={`/api/invoices/${invoice.id}/items/add`} class="bg-base-200/50 p-4 rounded-lg mb-8 border border-base-200">
|
||||
<form method="POST" action={`/api/invoices/${invoice.id}/items/add`} class="bg-base-200 p-4 rounded-lg mb-8 border border-base-content/20">
|
||||
<h4 class="text-xs font-semibold mb-3">Add Item</h4>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-12 gap-3 items-end">
|
||||
<div class="sm:col-span-6">
|
||||
@@ -329,8 +329,8 @@ const isDraft = invoice.status === 'draft';
|
||||
|
||||
<!-- Notes -->
|
||||
{invoice.notes && (
|
||||
<div class="mt-12 pt-8 border-t border-base-200">
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-base-content/40 mb-2">Notes</div>
|
||||
<div class="mt-12 pt-8 border-t border-base-content/20">
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-base-content/60 mb-2">Notes</div>
|
||||
<div class="text-sm whitespace-pre-wrap opacity-80">{invoice.notes}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -211,8 +211,8 @@ const getStatusColor = (status: string) => {
|
||||
|
||||
<div class="card card-border bg-base-100">
|
||||
<div class="card-body p-0">
|
||||
<div class="px-4 py-3 border-b border-base-200">
|
||||
<p class="text-xs text-base-content/50">
|
||||
<div class="px-4 py-3 border-b border-base-content/20">
|
||||
<p class="text-xs text-base-content/60">
|
||||
Showing <span class="font-semibold text-base-content">{allInvoices.length}</span>
|
||||
{allInvoices.length === 1 ? 'result' : 'results'}
|
||||
{selectedYear === 'current' ? ` for ${currentYear} (year to date)` : ` for ${selectedYear}`}
|
||||
@@ -235,7 +235,7 @@ const getStatusColor = (status: string) => {
|
||||
<tbody>
|
||||
{allInvoices.length === 0 ? (
|
||||
<tr>
|
||||
<td colspan="8" class="text-center py-8 text-base-content/50 text-sm">
|
||||
<td colspan="8" class="text-center py-8 text-base-content/60 text-sm">
|
||||
No invoices or quotes found. Create one to get started.
|
||||
</td>
|
||||
</tr>
|
||||
@@ -251,7 +251,7 @@ const getStatusColor = (status: string) => {
|
||||
{client ? (
|
||||
<div class="font-medium">{client.name}</div>
|
||||
) : (
|
||||
<span class="text-base-content/40 italic">Deleted Client</span>
|
||||
<span class="text-base-content/60 italic">Deleted Client</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{invoice.issueDate.toLocaleDateString()}</td>
|
||||
@@ -272,7 +272,7 @@ const getStatusColor = (status: string) => {
|
||||
<div role="button" tabindex="0" class="btn btn-ghost btn-xs btn-square">
|
||||
<Icon name="ellipsis-vertical" class="w-4 h-4" />
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content menu p-2 bg-base-100 rounded-box w-52 border border-base-200 z-100">
|
||||
<ul tabindex="0" class="dropdown-content menu p-2 bg-base-100 rounded-box w-52 border border-base-content/20 z-100">
|
||||
<li>
|
||||
<a href={`/dashboard/invoices/${invoice.id}`}>
|
||||
<Icon name="eye" class="w-4 h-4" />
|
||||
@@ -306,7 +306,7 @@ const getStatusColor = (status: string) => {
|
||||
<li>
|
||||
<ConfirmForm client:load message="Are you sure? This action cannot be undone." action="/api/invoices/delete" class="w-full">
|
||||
<input type="hidden" name="id" value={invoice.id} />
|
||||
<button type="submit" class="w-full justify-start text-error hover:bg-error/10">
|
||||
<button type="submit" class="w-full justify-start text-error hover:bg-base-300">
|
||||
<Icon name="trash" class="w-4 h-4" />
|
||||
Delete
|
||||
</button>
|
||||
|
||||
@@ -104,11 +104,11 @@ const defaultDueDate = nextMonth.toISOString().split('T')[0];
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-xs">Document Type</legend>
|
||||
<div class="flex gap-3">
|
||||
<label class="label cursor-pointer justify-start gap-2 border border-base-200 rounded-lg p-3 flex-1 hover:border-primary has-checked:border-primary has-checked:bg-primary/5 transition-all font-medium text-sm" for="document-type-invoice">
|
||||
<label class="label cursor-pointer justify-start gap-2 border border-base-content/20 rounded-lg p-3 flex-1 hover:border-primary has-checked:border-primary has-checked:bg-base-200 transition-all font-medium text-sm" for="document-type-invoice">
|
||||
<input type="radio" id="document-type-invoice" name="type" value="invoice" class="radio radio-primary radio-sm" checked />
|
||||
Invoice
|
||||
</label>
|
||||
<label class="label cursor-pointer justify-start gap-2 border border-base-200 rounded-lg p-3 flex-1 hover:border-primary has-checked:border-primary has-checked:bg-primary/5 transition-all font-medium text-sm" for="document-type-quote">
|
||||
<label class="label cursor-pointer justify-start gap-2 border border-base-content/20 rounded-lg p-3 flex-1 hover:border-primary has-checked:border-primary has-checked:bg-base-200 transition-all font-medium text-sm" for="document-type-quote">
|
||||
<input type="radio" id="document-type-quote" name="type" value="quote" class="radio radio-primary radio-sm" />
|
||||
Quote / Estimate
|
||||
</label>
|
||||
|
||||
@@ -394,11 +394,11 @@ function getTimeRangeLabel(range: string) {
|
||||
<div class="stat-title text-xs">Total Invoices</div>
|
||||
<div class="stat-value text-2xl">{invoiceStats.total}</div>
|
||||
</div>
|
||||
<div class="stat bg-success/10 rounded-lg">
|
||||
<div class="stat bg-base-200 rounded-lg">
|
||||
<div class="stat-title text-xs">Paid</div>
|
||||
<div class="stat-value text-2xl text-success">{invoiceStats.paid}</div>
|
||||
</div>
|
||||
<div class="stat bg-info/10 rounded-lg">
|
||||
<div class="stat bg-base-200 rounded-lg">
|
||||
<div class="stat-title text-xs">Sent</div>
|
||||
<div class="stat-value text-2xl text-info">{invoiceStats.sent}</div>
|
||||
</div>
|
||||
@@ -430,15 +430,15 @@ function getTimeRangeLabel(range: string) {
|
||||
<div class="stat-title text-xs">Total Quotes</div>
|
||||
<div class="stat-value text-2xl">{quoteStats.total}</div>
|
||||
</div>
|
||||
<div class="stat bg-success/10 rounded-lg">
|
||||
<div class="stat bg-base-200 rounded-lg">
|
||||
<div class="stat-title text-xs">Accepted</div>
|
||||
<div class="stat-value text-2xl text-success">{quoteStats.accepted}</div>
|
||||
</div>
|
||||
<div class="stat bg-info/10 rounded-lg">
|
||||
<div class="stat bg-base-200 rounded-lg">
|
||||
<div class="stat-title text-xs">Pending</div>
|
||||
<div class="stat-value text-2xl text-info">{quoteStats.sent}</div>
|
||||
</div>
|
||||
<div class="stat bg-error/10 rounded-lg">
|
||||
<div class="stat bg-base-200 rounded-lg">
|
||||
<div class="stat-title text-xs">Declined</div>
|
||||
<div class="stat-value text-2xl text-error">{quoteStats.declined}</div>
|
||||
</div>
|
||||
@@ -591,7 +591,7 @@ function getTimeRangeLabel(range: string) {
|
||||
<td>
|
||||
<div>
|
||||
<div class="font-medium">{stat.member.name}</div>
|
||||
<div class="text-xs text-base-content/40">{stat.member.email}</div>
|
||||
<div class="text-xs text-base-content/60">{stat.member.email}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="font-mono text-sm">{formatDuration(stat.totalTime)}</td>
|
||||
@@ -738,7 +738,7 @@ function getTimeRangeLabel(range: string) {
|
||||
<tr>
|
||||
<td class="whitespace-nowrap">
|
||||
{e.entry.startTime.toLocaleDateString()}<br/>
|
||||
<span class="text-xs text-base-content/40">
|
||||
<span class="text-xs text-base-content/60">
|
||||
{e.entry.startTime.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
|
||||
</span>
|
||||
</td>
|
||||
@@ -753,7 +753,7 @@ function getTimeRangeLabel(range: string) {
|
||||
<span>{e.tag.name}</span>
|
||||
</div>
|
||||
) : (
|
||||
<span class="text-base-content/30">-</span>
|
||||
<span class="text-base-content/60">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td class="text-base-content/60">{e.entry.description || '-'}</td>
|
||||
@@ -770,9 +770,9 @@ function getTimeRangeLabel(range: string) {
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex flex-col items-center justify-center py-10 text-center">
|
||||
<Icon name="inbox" class="w-12 h-12 text-base-content/15 mb-3" />
|
||||
<Icon name="inbox" class="w-12 h-12 text-base-content/30 mb-3" />
|
||||
<h3 class="text-base font-semibold mb-1">No time entries found</h3>
|
||||
<p class="text-base-content/50 text-sm mb-4">Try adjusting your filters or select a different time range.</p>
|
||||
<p class="text-base-content/60 text-sm mb-4">Try adjusting your filters or select a different time range.</p>
|
||||
<a href="/dashboard/tracker" class="btn btn-primary btn-sm">
|
||||
<Icon name="play" class="w-4 h-4" />
|
||||
Start Tracking Time
|
||||
|
||||
@@ -77,11 +77,11 @@ const userPasskeys = await db.select()
|
||||
</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between py-3 border-b border-base-200 gap-2 sm:gap-0">
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between py-3 border-b border-base-content/20 gap-2 sm:gap-0">
|
||||
<span class="text-base-content/60 text-sm">Account ID</span>
|
||||
<span class="font-mono text-xs break-all">{user.id}</span>
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between py-3 border-b border-base-200 gap-2 sm:gap-0">
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between py-3 border-b border-base-content/20 gap-2 sm:gap-0">
|
||||
<span class="text-base-content/60 text-sm">Email</span>
|
||||
<span class="text-sm break-all">{user.email}</span>
|
||||
</div>
|
||||
|
||||
@@ -82,7 +82,7 @@ const isAdmin = currentUserMember?.member.role === 'owner' || currentUserMember?
|
||||
{member.role}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-base-content/40">{member.joinedAt?.toLocaleDateString() ?? 'N/A'}</td>
|
||||
<td class="text-base-content/60">{member.joinedAt?.toLocaleDateString() ?? 'N/A'}</td>
|
||||
{isAdmin && (
|
||||
<td>
|
||||
{teamUser.id !== user.id && member.role !== 'owner' && (
|
||||
@@ -90,7 +90,7 @@ const isAdmin = currentUserMember?.member.role === 'owner' || currentUserMember?
|
||||
<div role="button" tabindex="0" class="btn btn-ghost btn-xs btn-square">
|
||||
<Icon name="ellipsis-vertical" class="w-4 h-4" />
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content z-1 menu p-2 bg-base-100 rounded-box w-52 border border-base-200">
|
||||
<ul tabindex="0" class="dropdown-content z-1 menu p-2 bg-base-100 rounded-box w-52 border border-base-content/20">
|
||||
<li>
|
||||
<form method="POST" action={`/api/team/change-role`}>
|
||||
<input type="hidden" name="userId" value={teamUser.id} />
|
||||
|
||||
@@ -56,7 +56,7 @@ if (!isAdmin) return Astro.redirect('/dashboard/team');
|
||||
<option value="member">Member</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
<p class="text-xs text-base-content/40 mt-1">Members can track time. Admins can manage team and clients.</p>
|
||||
<p class="text-xs text-base-content/60 mt-1">Members can track time. Admins can manage team and clients.</p>
|
||||
</fieldset>
|
||||
|
||||
<div class="flex justify-end gap-2 mt-4">
|
||||
|
||||
@@ -73,7 +73,7 @@ const successType = url.searchParams.get('success');
|
||||
<legend class="fieldset-legend text-xs">Team Logo</legend>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="avatar placeholder">
|
||||
<div class="bg-base-200 text-neutral-content rounded-xl w-20 border border-base-200 flex items-center justify-center overflow-hidden">
|
||||
<div class="bg-base-200 text-neutral-content rounded-xl w-20 border border-base-content/20 flex items-center justify-center overflow-hidden">
|
||||
{organization.logoUrl ? (
|
||||
<img
|
||||
src={organization.logoUrl}
|
||||
@@ -83,7 +83,7 @@ const successType = url.searchParams.get('success');
|
||||
) : (
|
||||
<Icon
|
||||
name="photo"
|
||||
class="w-6 h-6 opacity-40 text-base-content"
|
||||
class="w-6 h-6 opacity-70 text-base-content"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -95,7 +95,7 @@ const successType = url.searchParams.get('success');
|
||||
accept="image/png, image/jpeg"
|
||||
class="file-input file-input-bordered file-input-sm w-full max-w-xs"
|
||||
/>
|
||||
<div class="text-xs text-base-content/40 mt-1">
|
||||
<div class="text-xs text-base-content/60 mt-1">
|
||||
Upload a company logo (PNG, JPG). Will be displayed on invoices and quotes.
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,10 +113,10 @@ const successType = url.searchParams.get('success');
|
||||
class="input w-full"
|
||||
required
|
||||
/>
|
||||
<p class="text-xs text-base-content/40 mt-1">This name is visible to all team members</p>
|
||||
<p class="text-xs text-base-content/60 mt-1">This name is visible to all team members</p>
|
||||
</fieldset>
|
||||
|
||||
<div class="divider text-xs text-base-content/40 my-2">Address Information</div>
|
||||
<div class="divider text-xs text-base-content/60 my-2">Address Information</div>
|
||||
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-xs">Street Address</legend>
|
||||
@@ -182,7 +182,7 @@ const successType = url.searchParams.get('success');
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="divider text-xs text-base-content/40 my-2">Defaults</div>
|
||||
<div class="divider text-xs text-base-content/60 my-2">Defaults</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<fieldset class="fieldset">
|
||||
@@ -216,7 +216,7 @@ const successType = url.searchParams.get('success');
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row justify-between items-center gap-3 mt-4">
|
||||
<span class="text-xs text-base-content/40 text-center sm:text-left">
|
||||
<span class="text-xs text-base-content/60 text-center sm:text-left">
|
||||
Address information appears on invoices and quotes
|
||||
</span>
|
||||
|
||||
@@ -280,7 +280,7 @@ const successType = url.searchParams.get('success');
|
||||
{tag.rate ? (
|
||||
<span class="font-mono text-sm">{new Intl.NumberFormat('en-US', { style: 'currency', currency: organization.defaultCurrency || 'USD' }).format(tag.rate / 100)}</span>
|
||||
) : (
|
||||
<span class="text-base-content/40 text-xs italic">No rate</span>
|
||||
<span class="text-base-content/60 text-xs italic">No rate</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
@@ -315,7 +315,7 @@ const successType = url.searchParams.get('success');
|
||||
<fieldset class="fieldset mb-4">
|
||||
<legend class="fieldset-legend text-xs">Hourly Rate (cents)</legend>
|
||||
<input type="number" name="rate" value={tag.rate || 0} min="0" class="input w-full" />
|
||||
<p class="text-xs text-base-content/40 mt-1">Enter rate in cents (e.g. 5000 = $50.00)</p>
|
||||
<p class="text-xs text-base-content/60 mt-1">Enter rate in cents (e.g. 5000 = $50.00)</p>
|
||||
</fieldset>
|
||||
<div class="modal-action">
|
||||
<ModalButton client:load modalId={`edit_tag_modal_${tag.id}`} action="close" class="btn btn-sm">Cancel</ModalButton>
|
||||
@@ -353,7 +353,7 @@ const successType = url.searchParams.get('success');
|
||||
<fieldset class="fieldset mb-4">
|
||||
<legend class="fieldset-legend text-xs">Hourly Rate (cents)</legend>
|
||||
<input type="number" name="rate" value="0" min="0" class="input w-full" />
|
||||
<p class="text-xs text-base-content/40 mt-1">Enter rate in cents (e.g. 5000 = $50.00)</p>
|
||||
<p class="text-xs text-base-content/60 mt-1">Enter rate in cents (e.g. 5000 = $50.00)</p>
|
||||
</fieldset>
|
||||
<div class="modal-action">
|
||||
<ModalButton client:load modalId="new_tag_modal" action="close" class="btn btn-sm">Cancel</ModalButton>
|
||||
|
||||
@@ -144,9 +144,9 @@ const paginationPages = getPaginationPages(page, totalPages);
|
||||
<h1 class="text-2xl font-extrabold tracking-tight mb-6">Time Tracker</h1>
|
||||
|
||||
<!-- Tabs for Timer and Manual Entry -->
|
||||
<div class="tabs tabs-lift mb-6">
|
||||
<div class="tabs tabs-border mb-6">
|
||||
<input type="radio" name="tracker_tabs" class="tab" aria-label="Timer" checked="checked" />
|
||||
<div class="tab-content bg-base-100 border-base-300 p-6">
|
||||
<div class="tab-content border-base-content/20 p-6">
|
||||
{allClients.length === 0 ? (
|
||||
<div class="alert alert-warning flex flex-col sm:flex-row items-center gap-4">
|
||||
<Icon name="exclamation-triangle" class="stroke-current shrink-0 h-6 w-6" />
|
||||
@@ -169,7 +169,7 @@ const paginationPages = getPaginationPages(page, totalPages);
|
||||
</div>
|
||||
|
||||
<input type="radio" name="tracker_tabs" class="tab" aria-label="Manual Entry" />
|
||||
<div class="tab-content bg-base-100 border-base-300 p-6">
|
||||
<div class="tab-content border-base-content/20 p-6">
|
||||
{allClients.length === 0 ? (
|
||||
<div class="alert alert-warning flex flex-col sm:flex-row items-center gap-4">
|
||||
<Icon name="exclamation-triangle" class="stroke-current shrink-0 h-6 w-6" />
|
||||
@@ -314,7 +314,7 @@ const paginationPages = getPaginationPages(page, totalPages);
|
||||
<td>{entryUser?.name || 'Unknown'}</td>
|
||||
<td class="whitespace-nowrap">
|
||||
{entry.startTime.toLocaleDateString()}<br/>
|
||||
<span class="text-xs text-base-content/40">
|
||||
<span class="text-xs text-base-content/60">
|
||||
{entry.startTime.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
|
||||
</span>
|
||||
</td>
|
||||
@@ -322,7 +322,7 @@ const paginationPages = getPaginationPages(page, totalPages);
|
||||
{entry.endTime ? (
|
||||
<>
|
||||
{entry.endTime.toLocaleDateString()}<br/>
|
||||
<span class="text-xs text-base-content/40">
|
||||
<span class="text-xs text-base-content/60">
|
||||
{entry.endTime.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
|
||||
</span>
|
||||
</>
|
||||
|
||||
@@ -31,11 +31,11 @@ if (Astro.locals.user) {
|
||||
</div>
|
||||
|
||||
<!-- Features -->
|
||||
<div class="bg-base-200/50 border-t border-base-200 px-4 py-16 sm:py-20">
|
||||
<div class="bg-base-200 border-t border-base-content/20 px-4 py-16 sm:py-20">
|
||||
<div class="max-w-4xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="card bg-base-100 card-border">
|
||||
<div class="card-body">
|
||||
<div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center mb-2">
|
||||
<div class="w-10 h-10 rounded-lg bg-base-200 flex items-center justify-center mb-2">
|
||||
<Icon name="bolt" class="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<h3 class="card-title text-base">Lightning Fast</h3>
|
||||
@@ -44,7 +44,7 @@ if (Astro.locals.user) {
|
||||
</div>
|
||||
<div class="card bg-base-100 card-border">
|
||||
<div class="card-body">
|
||||
<div class="w-10 h-10 rounded-lg bg-info/10 flex items-center justify-center mb-2">
|
||||
<div class="w-10 h-10 rounded-lg bg-base-200 flex items-center justify-center mb-2">
|
||||
<Icon name="chart-bar" class="h-5 w-5 text-info" />
|
||||
</div>
|
||||
<h3 class="card-title text-base">Detailed Reports</h3>
|
||||
@@ -53,7 +53,7 @@ if (Astro.locals.user) {
|
||||
</div>
|
||||
<div class="card bg-base-100 card-border">
|
||||
<div class="card-body">
|
||||
<div class="w-10 h-10 rounded-lg bg-accent/10 flex items-center justify-center mb-2">
|
||||
<div class="w-10 h-10 rounded-lg bg-base-200 flex items-center justify-center mb-2">
|
||||
<Icon name="users" class="h-5 w-5 text-accent" />
|
||||
</div>
|
||||
<h3 class="card-title text-base">Team Collaboration</h3>
|
||||
|
||||
@@ -18,7 +18,7 @@ const errorMessage =
|
||||
|
||||
<Layout title="Login - Chronus">
|
||||
<div class="flex justify-center items-center flex-1 bg-base-100">
|
||||
<div class="card card-border bg-base-100 w-full max-w-sm mx-4">
|
||||
<div class="card card-border bg-base-200 w-full max-w-sm mx-4">
|
||||
<div class="card-body gap-0">
|
||||
<img src="/logo.webp" alt="Chronus" class="h-14 w-14 mx-auto mb-3" />
|
||||
<h2 class="text-2xl font-extrabold tracking-tight text-center">Welcome Back</h2>
|
||||
|
||||
@@ -34,7 +34,7 @@ const errorMessage =
|
||||
|
||||
<Layout title="Sign Up - Chronus">
|
||||
<div class="flex justify-center items-center flex-1 bg-base-100">
|
||||
<div class="card card-border bg-base-100 w-full max-w-sm mx-4">
|
||||
<div class="card card-border bg-base-200 w-full max-w-sm mx-4">
|
||||
<div class="card-body gap-0">
|
||||
<img src="/logo.webp" alt="Chronus" class="h-14 w-14 mx-auto mb-3" />
|
||||
<h2 class="text-2xl font-extrabold tracking-tight text-center">Create Account</h2>
|
||||
|
||||
@@ -52,8 +52,6 @@ export const GET: APIRoute = async ({ params }) => {
|
||||
case ".gif":
|
||||
contentType = "image/gif";
|
||||
break;
|
||||
// SVG excluded to prevent stored XSS
|
||||
// WebP omitted — not supported in PDF generation
|
||||
}
|
||||
|
||||
return new Response(fileContent, {
|
||||
|
||||
@@ -305,7 +305,6 @@ export function createInvoiceDocument(props: InvoiceDocumentProps) {
|
||||
if (organization.logoUrl) {
|
||||
try {
|
||||
let logoPath;
|
||||
// Handle uploads directory which might be external to public/
|
||||
if (organization.logoUrl.startsWith("/uploads/")) {
|
||||
const dataDir = process.env.DATA_DIR
|
||||
? process.env.DATA_DIR
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui" {
|
||||
themes: false;
|
||||
themes: sunset --default, winter;
|
||||
}
|
||||
@plugin "./theme-dark.ts";
|
||||
@plugin "./theme-light.ts";
|
||||
|
||||
/* Smoother transitions globally */
|
||||
@layer base {
|
||||
@@ -15,3 +13,8 @@
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Override DaisyUI card-border to use a visible border in both themes */
|
||||
.card-border {
|
||||
border-color: color-mix(in oklab, var(--color-base-content) 20%, transparent);
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createCatppuccinPlugin } from "@catppuccin/daisyui";
|
||||
|
||||
export default createCatppuccinPlugin(
|
||||
"macchiato",
|
||||
{},
|
||||
{
|
||||
default: true,
|
||||
},
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createCatppuccinPlugin } from "@catppuccin/daisyui";
|
||||
|
||||
export default createCatppuccinPlugin(
|
||||
"latte",
|
||||
{},
|
||||
{
|
||||
default: false,
|
||||
},
|
||||
);
|
||||
@@ -34,7 +34,6 @@ export async function recalculateInvoiceTotals(invoiceId: string) {
|
||||
if (discountType === "percentage") {
|
||||
discountAmount = Math.round(subtotal * (discountValue / 100));
|
||||
} else {
|
||||
// Fixed amount is assumed to be in cents
|
||||
discountAmount = Math.round(discountValue);
|
||||
}
|
||||
|
||||
|
||||