- Fixed DateRangePicker when mode is singledate

- added Transaction Creation/Editing/Deletion
- added confirmation for transaction deletion
- added app icon
This commit is contained in:
2026-03-28 14:56:54 +03:00
parent fe0f1d98ef
commit e9c155b272
28 changed files with 1005 additions and 247 deletions

View File

@@ -1,34 +1,42 @@
name: Build Linux
on:
workflow_dispatch:
push:
branches: [main]
tags:
- 'v*'
jobs:
build-linux:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
dotnet-version: '8.0.x'
- name: Restore dependencies
run: dotnet restore Clario/Clario.csproj
- name: Build
run: dotnet build Clario/Clario.csproj --configuration Release --no-restore
- name: Publish
run: |
dotnet publish Clario.Desktop/Clario.Desktop.csproj \
-r linux-x64 \
-c Release \
--self-contained true \
-p:PublishSingleFile=true \
-o ./publish/linux-x64
- name: Package as tar.gz
run: tar -czf Clario-linux-x64.tar.gz -C ./publish/linux-x64 .
run: dotnet publish Clario/Clario.csproj \
--configuration Release \
--runtime linux-x64 \
--self-contained true \
--output ./publish/linux \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: clario-linux-x64
path: Clario-linux-x64.tar.gz
name: Clario-linux-x64
path: ./publish/linux
retention-days: 7

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chart-bar-icon lucide-chart-bar"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M7 16h8"/><path d="M7 11h12"/><path d="M7 6h3"/></svg>

After

Width:  |  Height:  |  Size: 334 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chart-column-icon lucide-chart-column"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M18 17V9"/><path d="M13 17V5"/><path d="M8 17v-3"/></svg>

After

Width:  |  Height:  |  Size: 343 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>

After

Width:  |  Height:  |  Size: 271 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-receipt-icon lucide-receipt"><path d="M12 17V7"/><path d="M16 8h-6a2 2 0 0 0 0 4h4a2 2 0 0 1 0 4H8"/><path d="M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"/></svg>

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

View File

@@ -1,28 +1,47 @@
<svg width="800" height="400" viewBox="0 0 800 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M211.696 66.3202C212.178 60.8183 208.105 55.9315 202.583 55.833C175.756 55.3546 149.254 62.3834 126.112 76.2222C102.97 90.061 84.238 110.082 71.9651 133.942C69.4389 138.853 71.817 144.754 76.8916 146.933L105.77 159.335C110.844 161.514 116.679 159.133 119.393 154.323C127.265 140.368 138.658 128.642 152.507 120.361C166.356 112.079 182.077 107.591 198.096 107.259C203.617 107.144 208.476 103.131 208.957 97.6291L211.696 66.3202Z" fill="url(#paint0_linear_104_124)"/>
<path d="M76.8916 146.933C71.817 144.754 65.9008 147.092 64.0787 152.306C53.1091 183.693 53.3093 218.027 64.8376 249.432C76.3659 280.837 98.4269 307.147 127.098 323.983C131.861 326.779 137.885 324.735 140.344 319.79L154.34 291.649C156.799 286.704 154.748 280.746 150.097 277.768C133.148 266.915 120.114 250.773 113.116 231.71C106.118 212.647 105.613 191.906 111.515 172.665C113.135 167.385 110.844 161.514 105.77 159.335L76.8916 146.933Z" fill="url(#paint1_linear_104_124)"/>
<path d="M140.344 319.79C137.885 324.735 139.889 330.772 144.992 332.882C172.819 344.389 203.645 346.914 233.115 339.955C262.584 332.995 289.023 316.946 308.762 294.206C312.383 290.035 311.474 283.739 307.062 280.417L281.956 261.511C277.544 258.189 271.311 259.116 267.532 263.144C255.135 276.36 239.072 285.704 221.294 289.903C203.516 294.102 184.97 292.931 167.97 286.657C162.789 284.745 156.799 286.704 154.34 291.649L140.344 319.79Z" fill="url(#paint2_linear_104_124)"/>
<path d="M307.676 120.025C312.113 116.736 313.07 110.448 309.482 106.25C298.448 93.3399 285.199 82.4632 270.333 74.1449C255.467 65.8267 239.266 60.2252 222.492 57.5748C217.037 56.7128 212.178 60.8183 211.696 66.3202L208.957 97.6291C208.476 103.131 212.563 107.927 217.981 108.998C227.511 110.884 236.706 114.261 245.22 119.025C253.734 123.789 261.423 129.859 268.015 136.994C271.763 141.051 277.988 142.026 282.425 138.738L307.676 120.025Z" fill="url(#paint3_linear_104_124)"/>
<path d="M105.77 159.335L76.8916 146.933M105.77 159.335C110.844 161.514 116.679 159.133 119.393 154.323C127.265 140.368 138.658 128.642 152.507 120.361C166.356 112.079 182.077 107.591 198.096 107.259C203.617 107.144 208.476 103.131 208.957 97.6291M105.77 159.335C110.844 161.514 113.135 167.385 111.515 172.665C105.613 191.906 106.118 212.647 113.116 231.71C120.114 250.773 133.148 266.915 150.097 277.768C154.748 280.746 156.799 286.704 154.34 291.649M76.8916 146.933C71.817 144.754 69.4389 138.853 71.9651 133.942C84.238 110.082 102.97 90.061 126.112 76.2222C149.254 62.3834 175.756 55.3546 202.583 55.833C208.105 55.9315 212.178 60.8183 211.696 66.3202M76.8916 146.933C71.817 144.754 65.9008 147.092 64.0787 152.306C53.1091 183.693 53.3093 218.027 64.8376 249.432C76.3659 280.837 98.4269 307.147 127.098 323.983C131.861 326.779 137.885 324.735 140.344 319.79M208.957 97.6291L211.696 66.3202M208.957 97.6291C208.476 103.131 212.563 107.927 217.981 108.998C227.511 110.884 236.706 114.261 245.22 119.025C253.734 123.789 261.423 129.859 268.015 136.994C271.763 141.051 277.988 142.026 282.425 138.738L307.676 120.025C312.113 116.736 313.07 110.448 309.482 106.25C298.448 93.3399 285.199 82.4632 270.333 74.1449C255.467 65.8267 239.266 60.2252 222.492 57.5748C217.037 56.7128 212.178 60.8183 211.696 66.3202M154.34 291.649L140.344 319.79M154.34 291.649C156.799 286.704 162.789 284.745 167.97 286.657C184.97 292.931 203.516 294.102 221.294 289.903C239.072 285.704 255.135 276.36 267.532 263.144C271.311 259.116 277.544 258.189 281.956 261.511L307.062 280.417C311.474 283.739 312.383 290.035 308.762 294.206C289.023 316.946 262.584 332.995 233.115 339.955C203.645 346.914 172.819 344.389 144.992 332.882C139.889 330.772 137.885 324.735 140.344 319.79" stroke="#13161E" stroke-width="5"/>
<path d="M331.8 292V146.8H362.4V292H331.8ZM424.548 294C415.615 294 407.615 291.8 400.548 287.4C393.615 283 388.082 277 383.948 269.4C379.948 261.8 377.948 253.133 377.948 243.4C377.948 233.667 379.948 225 383.948 217.4C388.082 209.8 393.615 203.8 400.548 199.4C407.615 195 415.615 192.8 424.548 192.8C431.082 192.8 436.948 194.067 442.148 196.6C447.482 199.133 451.815 202.667 455.148 207.2C458.482 211.6 460.348 216.667 460.748 222.4V264.4C460.348 270.133 458.482 275.267 455.148 279.8C451.948 284.2 447.682 287.667 442.348 290.2C437.015 292.733 431.082 294 424.548 294ZM430.748 266.4C437.282 266.4 442.548 264.267 446.548 260C450.548 255.6 452.548 250.067 452.548 243.4C452.548 238.867 451.615 234.867 449.748 231.4C448.015 227.933 445.482 225.267 442.148 223.4C438.948 221.4 435.215 220.4 430.948 220.4C426.682 220.4 422.882 221.4 419.548 223.4C416.348 225.267 413.748 227.933 411.748 231.4C409.882 234.867 408.948 238.867 408.948 243.4C408.948 247.8 409.882 251.733 411.748 255.2C413.615 258.667 416.215 261.4 419.548 263.4C422.882 265.4 426.615 266.4 430.748 266.4ZM451.348 292V265.8L455.948 242.2L451.348 218.6V194.8H481.348V292H451.348ZM502.894 292V194.8H533.494V292H502.894ZM533.494 238.6L520.694 228.6C523.227 217.267 527.494 208.467 533.494 202.2C539.494 195.933 547.827 192.8 558.494 192.8C563.16 192.8 567.227 193.533 570.694 195C574.294 196.333 577.427 198.467 580.094 201.4L561.894 224.4C560.56 222.933 558.894 221.8 556.894 221C554.894 220.2 552.627 219.8 550.094 219.8C545.027 219.8 540.96 221.4 537.894 224.6C534.96 227.667 533.494 232.333 533.494 238.6ZM588.245 292V194.8H618.845V292H588.245ZM603.645 181.4C598.845 181.4 594.845 179.8 591.645 176.6C588.579 173.267 587.045 169.267 587.045 164.6C587.045 159.8 588.579 155.8 591.645 152.6C594.845 149.4 598.845 147.8 603.645 147.8C608.445 147.8 612.379 149.4 615.445 152.6C618.512 155.8 620.045 159.8 620.045 164.6C620.045 169.267 618.512 173.267 615.445 176.6C612.379 179.8 608.445 181.4 603.645 181.4ZM687.194 294.2C677.194 294.2 668.127 292 659.994 287.6C651.994 283.067 645.66 276.933 640.994 269.2C636.327 261.467 633.994 252.8 633.994 243.2C633.994 233.6 636.327 225 640.994 217.4C645.66 209.8 651.994 203.8 659.994 199.4C667.994 194.867 677.06 192.6 687.194 192.6C697.327 192.6 706.394 194.8 714.394 199.2C722.394 203.6 728.727 209.667 733.394 217.4C738.06 225 740.394 233.6 740.394 243.2C740.394 252.8 738.06 261.467 733.394 269.2C728.727 276.933 722.394 283.067 714.394 287.6C706.394 292 697.327 294.2 687.194 294.2ZM687.194 266.4C691.594 266.4 695.46 265.467 698.794 263.6C702.127 261.6 704.66 258.867 706.394 255.4C708.26 251.8 709.194 247.733 709.194 243.2C709.194 238.667 708.26 234.733 706.394 231.4C704.527 227.933 701.927 225.267 698.594 223.4C695.394 221.4 691.594 220.4 687.194 220.4C682.927 220.4 679.127 221.4 675.794 223.4C672.46 225.267 669.86 227.933 667.994 231.4C666.127 234.867 665.194 238.867 665.194 243.4C665.194 247.8 666.127 251.8 667.994 255.4C669.86 258.867 672.46 261.6 675.794 263.6C679.127 265.467 682.927 266.4 687.194 266.4Z" fill="url(#paint4_linear_104_124)"/>
<path d="M331.8 292V146.8H362.4V292H331.8Z" fill="url(#paint0_linear_104_124)"/>
<path d="M424.548 294C415.615 294 407.615 291.8 400.548 287.4C393.615 283 388.082 277 383.948 269.4C379.948 261.8 377.948 253.133 377.948 243.4C377.948 233.667 379.948 225 383.948 217.4C388.082 209.8 393.615 203.8 400.548 199.4C407.615 195 415.615 192.8 424.548 192.8C431.082 192.8 436.948 194.067 442.148 196.6C447.482 199.133 451.815 202.667 455.148 207.2C458.482 211.6 460.348 216.667 460.748 222.4V264.4C460.348 270.133 458.482 275.267 455.148 279.8C451.948 284.2 447.682 287.667 442.348 290.2C437.015 292.733 431.082 294 424.548 294ZM430.748 266.4C437.282 266.4 442.548 264.267 446.548 260C450.548 255.6 452.548 250.067 452.548 243.4C452.548 238.867 451.615 234.867 449.748 231.4C448.015 227.933 445.482 225.267 442.148 223.4C438.948 221.4 435.215 220.4 430.948 220.4C426.682 220.4 422.882 221.4 419.548 223.4C416.348 225.267 413.748 227.933 411.748 231.4C409.882 234.867 408.948 238.867 408.948 243.4C408.948 247.8 409.882 251.733 411.748 255.2C413.615 258.667 416.215 261.4 419.548 263.4C422.882 265.4 426.615 266.4 430.748 266.4ZM451.348 292V265.8L455.948 242.2L451.348 218.6V194.8H481.348V292H451.348Z" fill="url(#paint1_linear_104_124)"/>
<path d="M502.894 292V194.8H533.494V292H502.894ZM533.494 238.6L520.694 228.6C523.227 217.267 527.494 208.467 533.494 202.2C539.494 195.933 547.827 192.8 558.494 192.8C563.16 192.8 567.227 193.533 570.694 195C574.294 196.333 577.427 198.467 580.094 201.4L561.894 224.4C560.56 222.933 558.894 221.8 556.894 221C554.894 220.2 552.627 219.8 550.094 219.8C545.027 219.8 540.96 221.4 537.894 224.6C534.96 227.667 533.494 232.333 533.494 238.6Z" fill="url(#paint2_linear_104_124)"/>
<path d="M588.245 292V194.8H618.845V292H588.245ZM603.645 181.4C598.845 181.4 594.845 179.8 591.645 176.6C588.579 173.267 587.045 169.267 587.045 164.6C587.045 159.8 588.579 155.8 591.645 152.6C594.845 149.4 598.845 147.8 603.645 147.8C608.445 147.8 612.379 149.4 615.445 152.6C618.512 155.8 620.045 159.8 620.045 164.6C620.045 169.267 618.512 173.267 615.445 176.6C612.379 179.8 608.445 181.4 603.645 181.4Z" fill="url(#paint3_linear_104_124)"/>
<path d="M687.194 294.2C677.194 294.2 668.127 292 659.994 287.6C651.994 283.067 645.66 276.933 640.994 269.2C636.327 261.467 633.994 252.8 633.994 243.2C633.994 233.6 636.327 225 640.994 217.4C645.66 209.8 651.994 203.8 659.994 199.4C667.994 194.867 677.06 192.6 687.194 192.6C697.327 192.6 706.394 194.8 714.394 199.2C722.394 203.6 728.727 209.667 733.394 217.4C738.06 225 740.394 233.6 740.394 243.2C740.394 252.8 738.06 261.467 733.394 269.2C728.727 276.933 722.394 283.067 714.394 287.6C706.394 292 697.327 294.2 687.194 294.2ZM687.194 266.4C691.594 266.4 695.46 265.467 698.794 263.6C702.127 261.6 704.66 258.867 706.394 255.4C708.26 251.8 709.194 247.733 709.194 243.2C709.194 238.667 708.26 234.733 706.394 231.4C704.527 227.933 701.927 225.267 698.594 223.4C695.394 221.4 691.594 220.4 687.194 220.4C682.927 220.4 679.127 221.4 675.794 223.4C672.46 225.267 669.86 227.933 667.994 231.4C666.127 234.867 665.194 238.867 665.194 243.4C665.194 247.8 666.127 251.8 667.994 255.4C669.86 258.867 672.46 261.6 675.794 263.6C679.127 265.467 682.927 266.4 687.194 266.4Z" fill="url(#paint4_linear_104_124)"/>
<path d="M142.075 320.852C139.663 325.703 141.628 331.626 146.634 333.697C173.927 344.985 204.162 347.462 233.066 340.635C261.97 333.807 287.902 318.062 307.262 295.753C310.813 291.661 309.922 285.484 305.595 282.225L280.97 263.678C276.643 260.419 270.529 261.329 266.823 265.28C254.664 278.246 238.909 287.413 221.472 291.532C204.035 295.651 185.845 294.502 169.171 288.348C164.089 286.472 158.214 288.394 155.802 293.245L142.075 320.852Z" fill="url(#paint5_linear_104_124)"/>
<path d="M76.2161 149.27C71.2388 147.132 65.4361 149.426 63.6489 154.541C52.8897 185.333 53.0861 219.017 64.3932 249.827C75.7004 280.637 97.3382 306.447 125.46 322.964C130.131 325.708 136.039 323.702 138.451 318.85L152.179 291.244C154.591 286.392 152.579 280.547 148.017 277.625C131.393 266.978 118.609 251.142 111.746 232.44C104.882 213.739 104.387 193.39 110.175 174.515C111.764 169.335 109.518 163.575 104.54 161.437L76.2161 149.27Z" fill="url(#paint6_linear_104_124)"/>
<path d="M209.516 66.3108C209.988 60.9133 205.994 56.119 200.578 56.0225C174.265 55.5532 148.272 62.4487 125.574 76.0252C102.876 89.6016 84.5026 109.243 72.4651 132.651C69.9874 137.469 72.3198 143.258 77.2972 145.396L105.621 157.562C110.599 159.7 116.321 157.364 118.983 152.645C126.705 138.955 137.879 127.452 151.462 119.327C165.045 111.202 180.464 106.8 196.176 106.473C201.592 106.361 206.357 102.424 206.829 97.0263L209.516 66.3108Z" fill="url(#paint7_linear_104_124)"/>
<path d="M307.263 118.728C311.615 115.502 312.553 109.332 309.034 105.213C298.212 92.5486 285.217 81.878 270.636 73.7174C256.055 65.5569 240.165 60.0616 223.713 57.4614C218.362 56.6158 213.597 60.6435 213.124 66.041L210.438 96.7565C209.966 102.154 213.975 106.859 219.289 107.91C228.636 109.76 237.655 113.073 246.005 117.747C254.356 122.421 261.897 128.375 268.363 135.376C272.038 139.356 278.145 140.312 282.497 137.086L307.263 118.728Z" fill="url(#paint8_linear_104_124)"/>
<defs>
<linearGradient id="paint0_linear_104_124" x1="317.366" y1="64.3917" x2="259.803" y2="159.58" gradientUnits="userSpaceOnUse">
<linearGradient id="paint0_linear_104_124" x1="745" y1="91.9999" x2="321" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#7B9CFF"/>
<stop offset="1" stop-color="#3B6AFF"/>
</linearGradient>
<linearGradient id="paint1_linear_104_124" x1="317.366" y1="64.3917" x2="259.803" y2="159.58" gradientUnits="userSpaceOnUse">
<linearGradient id="paint1_linear_104_124" x1="745" y1="91.9999" x2="321" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#7B9CFF"/>
<stop offset="1" stop-color="#3B6AFF"/>
</linearGradient>
<linearGradient id="paint2_linear_104_124" x1="317.366" y1="64.3917" x2="259.803" y2="159.58" gradientUnits="userSpaceOnUse">
<linearGradient id="paint2_linear_104_124" x1="745" y1="91.9999" x2="321" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#7B9CFF"/>
<stop offset="1" stop-color="#3B6AFF"/>
</linearGradient>
<linearGradient id="paint3_linear_104_124" x1="317.366" y1="64.3917" x2="259.803" y2="159.58" gradientUnits="userSpaceOnUse">
<linearGradient id="paint3_linear_104_124" x1="745" y1="91.9999" x2="321" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#7B9CFF"/>
<stop offset="1" stop-color="#3B6AFF"/>
</linearGradient>
<linearGradient id="paint4_linear_104_124" x1="745" y1="92" x2="321" y2="344" gradientUnits="userSpaceOnUse">
<linearGradient id="paint4_linear_104_124" x1="745" y1="91.9999" x2="321" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#7B9CFF"/>
<stop offset="1" stop-color="#3B6AFF"/>
</linearGradient>
<linearGradient id="paint5_linear_104_124" x1="315.701" y1="70.2952" x2="259.223" y2="163.668" gradientUnits="userSpaceOnUse">
<stop stop-color="#7B9CFF"/>
<stop offset="1" stop-color="#3B6AFF"/>
</linearGradient>
<linearGradient id="paint6_linear_104_124" x1="312.078" y1="68.2937" x2="255.6" y2="161.666" gradientUnits="userSpaceOnUse">
<stop stop-color="#7B9CFF"/>
<stop offset="1" stop-color="#3B6AFF"/>
</linearGradient>
<linearGradient id="paint7_linear_104_124" x1="313.159" y1="64.4189" x2="256.681" y2="157.791" gradientUnits="userSpaceOnUse">
<stop stop-color="#7B9CFF"/>
<stop offset="1" stop-color="#3B6AFF"/>
</linearGradient>
<linearGradient id="paint8_linear_104_124" x1="316.768" y1="64.1491" x2="260.29" y2="157.521" gradientUnits="userSpaceOnUse">
<stop stop-color="#7B9CFF"/>
<stop offset="1" stop-color="#3B6AFF"/>
</linearGradient>

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -0,0 +1,65 @@
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactivity;
namespace Clario.Behaviors;
public class NumericInputBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject!.AddHandler(TextBox.TextInputEvent, OnTextInput, RoutingStrategies.Tunnel);
AssociatedObject.TextChanged += OnTextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject!.RemoveHandler(TextBox.TextInputEvent, OnTextInput);
AssociatedObject.TextChanged -= OnTextChanged;
}
private void OnTextInput(object? sender, TextInputEventArgs e)
{
if (e.Text is null) return;
foreach (var c in e.Text)
{
if (!char.IsDigit(c) && c != '.')
{
e.Handled = true;
return;
}
}
var current = (sender as TextBox)?.Text ?? "";
if (e.Text.Contains('.') && current.Contains('.'))
{
e.Handled = true;
}
}
private void OnTextChanged(object? sender, TextChangedEventArgs e)
{
if (sender is not TextBox tb) return;
var text = tb.Text ?? "";
var clean = new string(text.Where(c => char.IsDigit(c) || c == '.').ToArray());
var dotIndex = clean.IndexOf('.');
if (dotIndex >= 0)
{
clean = clean[..(dotIndex + 1)] + clean[(dotIndex + 1)..].Replace(".", "");
}
if (clean != text)
{
var caret = tb.CaretIndex;
tb.Text = clean;
tb.CaretIndex = Math.Min(caret, clean.Length);
}
}
}

View File

@@ -27,6 +27,8 @@
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" />
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" />
<PackageReference Include="Supabase" />
<PackageReference Include="Xaml.Behaviors.Interactions" />
<PackageReference Include="Xaml.Behaviors.Interactivity" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,30 +0,0 @@
{
"GeneralSettings": {
"NetProjectPath": "Clario.csproj",
"ApplicationName": "Clario",
"Version": "1.0.0",
"PackageName": {
"$type": "msbuild",
"property": "AssemblyName"
},
"AssemblyName": {
"$type": "msbuild",
"property": "AssemblyName"
}
},
"LinuxSettings": {
"CreateBinSymlink": "True"
},
"Win32Settings": {
"IncludeUninstaller": "True"
},
"MacOsSettings": {
"CreateBundle": true,
"BundleIdentifier": "com.CompanyName.Clario",
"SigningCredentialsType": "AdHoc"
},
"PublishSettings": {
"PublishSingleFile": "True",
"ExtraBuildProperties": {}
}
}

View File

@@ -8,7 +8,7 @@ public class AccountMaskToStringConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not string mask) return string.Empty;
if (value is not string mask || string.IsNullOrWhiteSpace(mask)) return string.Empty;
return $"•••• {mask}";
}

View File

@@ -9,7 +9,7 @@
<Setter Property="Foreground" Value="{DynamicResource TextSecondary}" />
<Setter Property="BorderBrush" Value="{DynamicResource BorderSubtle}" />
<Setter Property="CornerRadius" Value="{DynamicResource RadiusControl}" />
<Setter Property="Padding" Value="10,8" />
<Setter Property="Padding" Value="10,8 2 8" />
<Setter Property="FontSize" Value="13" />
<Setter Property="Template">
<ControlTemplate>
@@ -37,7 +37,8 @@
Text="{TemplateBinding DisplayText}"
FontSize="{TemplateBinding FontSize}"
Foreground="{TemplateBinding Foreground}"
VerticalAlignment="Center" />
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
<Svg Grid.Column="2"
Path="../Assets/Icons/chevron-down.svg"
Width="12" Height="12"

View File

@@ -5,6 +5,8 @@ using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Calendar = Avalonia.Controls.Calendar;
namespace Clario.CustomControls;
@@ -66,7 +68,11 @@ public class DateRangePicker : TemplatedControl
if (_button != null) _button.Click -= OnButtonClick;
if (_calendar != null) _calendar.SelectedDatesChanged -= OnCalendarDatesChanged;
if (_calendar != null)
{
_calendar.SelectedDatesChanged -= OnCalendarDatesChanged;
_calendar.RemoveHandler(PointerReleasedEvent, OnCalendarPointerReleased);
}
_button = e.NameScope.Find<Button>("PART_Button");
_popup = e.NameScope.Find<Popup>("PART_Popup");
@@ -81,7 +87,7 @@ public class DateRangePicker : TemplatedControl
_calendar.SelectedDatesChanged += OnCalendarDatesChanged;
// _calendar.PointerPressed
_calendar.AddHandler(PointerReleasedEvent, OnCalendarPointerReleased, RoutingStrategies.Tunnel);
SyncToCalendar();
@@ -90,8 +96,45 @@ public class DateRangePicker : TemplatedControl
UpdateDisplayText();
}
private void OnCalendarPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (_calendar!.SelectionMode != CalendarSelectionMode.SingleDate) return;
private void OnButtonClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
if (_isSyncing) return;
if (_popup is null || !_popup.IsOpen) return;
var newDates = _calendar!.SelectedDates.OrderBy(d => d).ToList();
_isSyncing = true;
try
{
SelectedDates = newDates;
SelectedDate = newDates.Count > 0 ? newDates[0] : null;
UpdateDisplayText();
bool shouldClose = SelectionMode switch
{
CalendarSelectionMode.SingleDate => newDates.Count >= 1,
CalendarSelectionMode.SingleRange => newDates.Count >= 2,
_ => false
};
if (shouldClose)
_popup.IsOpen = false;
}
finally
{
_isSyncing = false;
}
}
private void OnButtonClick(object? sender, RoutedEventArgs e)
{
if (_popup is null) return;
@@ -102,10 +145,12 @@ public class DateRangePicker : TemplatedControl
private void OnCalendarDatesChanged(object? sender, SelectionChangedEventArgs e)
{
if (_calendar!.SelectionMode == CalendarSelectionMode.SingleDate) return;
if (_isSyncing) return;
if (_popup is null || !_popup.IsOpen) return;
Console.WriteLine("test");
var newDates = _calendar!.SelectedDates.OrderBy(d => d).ToList();
_isSyncing = true;

View File

@@ -3,13 +3,22 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Clario.ViewModels"
xmlns:views="clr-namespace:Clario.Views"
xmlns:mobileViews="clr-namespace:Clario.MobileViews"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Clario.MobileViews.MainViewMobile"
x:CompileBindings="False"
Classes="mobile">
x:DataType="vm:MainViewModel"
Classes="mobile"
x:Name="MainControl">
<Design.DataContext>
<vm:MainViewModel />
</Design.DataContext>
<Grid RowDefinitions="*,Auto"
Background="{DynamicResource BgBase}">
<mobileViews:TransactionFormViewMobile Grid.Row="0" Grid.RowSpan="2" ZIndex="2"
DataContext="{Binding TransactionFormViewModel}"
IsVisible="{Binding DataContext.IsTransactionFormVisible,ElementName=MainControl}">
</mobileViews:TransactionFormViewMobile>
<!-- ── Content area ──────────────────────── -->
<ContentControl Grid.Row="0"
Content="{Binding CurrentView}" />
@@ -65,7 +74,7 @@
VerticalAlignment="Center"
Width="52" Height="52"
CornerRadius="26"
Padding="0">
Padding="0" Command="{Binding OpenAddTransactionCommand}">
<Svg Path="../Assets/Icons/plus.svg"
Width="22" Height="22"
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #0D0F14; }" />

View File

@@ -0,0 +1,440 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Clario.ViewModels"
xmlns:cc="clr-namespace:Clario.CustomControls"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="800"
x:Class="Clario.MobileViews.TransactionFormViewMobile"
x:DataType="vm:TransactionFormViewModel"
Classes="mobile">
<Design.DataContext>
<vm:TransactionFormViewModel />
</Design.DataContext>
<Grid RowDefinitions="Auto,*,Auto"
Background="{DynamicResource BgBase}">
<!-- ── Top bar ────────────────────────────── -->
<Grid Grid.Row="0"
ColumnDefinitions="Auto,*,Auto"
Margin="16,16,16,0">
<!-- Close -->
<Button Grid.Column="0"
Background="{DynamicResource BgSurface}"
BorderBrush="{DynamicResource BorderSubtle}"
BorderThickness="1"
CornerRadius="20"
Width="36" Height="36"
Padding="0"
HorizontalContentAlignment="Center"
Command="{Binding CancelCommand}">
<Svg Path="../Assets/Icons/x.svg"
Width="14" Height="14"
Css="{DynamicResource SvgMuted}" />
</Button>
<!-- Title -->
<StackPanel Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="1">
<TextBlock Text="{Binding FormTitle}"
FontSize="15"
FontWeight="Bold"
Foreground="{DynamicResource TextPrimary}"
HorizontalAlignment="Center" />
<TextBlock Text="{Binding FormSubtitle}"
FontSize="11"
Foreground="{DynamicResource TextMuted}"
HorizontalAlignment="Center" />
</StackPanel>
<!-- Delete (edit mode only) -->
<Button Grid.Column="2"
Background="#1A0808"
BorderBrush="#3A1515"
BorderThickness="1"
CornerRadius="20"
Width="36" Height="36"
Padding="0"
HorizontalContentAlignment="Center"
IsVisible="{Binding IsEditMode}"
Command="{Binding RequestDeleteCommand}">
<Svg Path="../Assets/Icons/trash-2.svg"
Width="14" Height="14"
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FF5E5E; }" />
</Button>
<!-- placeholder to keep title centered when not in edit mode -->
<Border Grid.Column="2"
Width="36"
IsVisible="{Binding !IsEditMode}" />
</Grid>
<!-- ── Scrollable form ────────────────────── -->
<ScrollViewer Grid.Row="1"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Margin="0,16,0,0">
<StackPanel Margin="16,0,16,16" Spacing="0">
<TextBlock Text="TYPE" Classes="label" Margin="0,0,0,6" />
<Border Background="{DynamicResource BgBase}"
BorderBrush="{DynamicResource BorderSubtle}"
BorderThickness="1"
CornerRadius="{DynamicResource RadiusControl}"
Padding="3"
Margin="0,0,0,20"
Height="50">
<Grid ColumnDefinitions="*,*">
<!-- Expense -->
<Button Grid.Column="0"
Classes="nav"
Classes.accented="{Binding IsExpense}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalAlignment="Stretch"
CornerRadius="7"
Padding="0,8"
Focusable="False"
Command="{Binding SetTypeCommand}"
CommandParameter="expense">
<StackPanel Orientation="Horizontal" Spacing="6">
<Svg Path="../Assets/Icons/arrow-up-right.svg"
Width="16" Height="16" />
<TextBlock Text="Expense"
FontSize="16"
FontWeight="SemiBold"
VerticalAlignment="Center" />
</StackPanel>
</Button>
<!-- Income -->
<Button Grid.Column="1"
Classes="nav"
Classes.accented="{Binding IsIncome}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalAlignment="Stretch"
CornerRadius="7"
Padding="0,8"
Focusable="False"
Command="{Binding SetTypeCommand}"
CommandParameter="income">
<StackPanel Orientation="Horizontal" Spacing="6">
<Svg Path="../Assets/Icons/arrow-down-left.svg"
Width="16" Height="16" />
<TextBlock Text="Income"
FontSize="16"
VerticalAlignment="Center" />
</StackPanel>
</Button>
</Grid>
</Border>
<!-- ── Amount ──────────────────────── -->
<TextBlock Text="AMOUNT" Classes="label" FontSize="14" Margin="0,0,0,6" />
<Border Background="{DynamicResource BgBase}"
BorderBrush="{DynamicResource BorderSubtle}"
BorderThickness="1"
CornerRadius="{DynamicResource RadiusControl}"
Padding="14,0"
Margin="0,0,0,16"
Height="64">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0"
Text="$"
FontSize="32"
FontWeight="Bold"
Foreground="{DynamicResource TextMuted}"
VerticalAlignment="Center"
Margin="0,0,8,0"
Padding="0 0 0 2" />
<TextBox Grid.Column="1"
Classes="ghost numeric"
Text="{Binding Amount, Mode=TwoWay}"
Watermark="0.00"
FontSize="32"
FontWeight="Bold"
Foreground="{DynamicResource TextPrimary}"
Height="54"
Padding="0 0 0 2"
VerticalContentAlignment="Center">
</TextBox>
<TextBlock Grid.Column="2"
Text="{Binding Currency}"
FontSize="18"
Foreground="{DynamicResource TextDisabled}"
VerticalAlignment="Center"
Padding="0 0 0 2" />
</Grid>
</Border>
<!-- ── Description ─────────────────── -->
<TextBlock Text="DESCRIPTION" Classes="label" FontSize="14" Margin="0,0,0,6" />
<TextBox Text="{Binding Description, Mode=TwoWay}"
Watermark="e.g. Grocery Shopping"
FontSize="16"
Height="48"
Padding="12,0"
VerticalContentAlignment="Center"
Margin="0,0,0,16" />
<!-- ── Category + Account ──────────── -->
<Grid ColumnDefinitions="*,14,*" Margin="0,0,0,16">
<!-- Category -->
<StackPanel Grid.Column="0" Spacing="6">
<TextBlock Text="CATEGORY" Classes="label" FontSize="14" />
<Border Background="{DynamicResource BgBase}"
BorderBrush="{DynamicResource BorderSubtle}"
BorderThickness="1"
CornerRadius="{DynamicResource RadiusControl}">
<Grid ColumnDefinitions="Auto,*" Height="42">
<Border Grid.Column="0"
CornerRadius="7"
Width="36" Height="36"
Margin="3,0"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Border.Background>
<SolidColorBrush
Color="{Binding SelectedCategory.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
Opacity="0.15" />
</Border.Background>
<Svg Path="{Binding SelectedCategory.Icon, Converter={StaticResource SvgPathFromName}}"
Width="14" Height="14"
Css="{Binding SelectedCategory.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
</Border>
<ComboBox Grid.Column="1"
VerticalAlignment="Stretch"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"
DisplayMemberBinding="{Binding Name}"
Background="Transparent"
BorderThickness="0"
Padding="8,10"
FontSize="13"
HorizontalAlignment="Stretch" />
</Grid>
</Border>
</StackPanel>
<!-- Account -->
<StackPanel Grid.Column="2" Spacing="6">
<TextBlock Text="ACCOUNT" Classes="label" FontSize="14" />
<Border Background="{DynamicResource BgBase}"
BorderBrush="{DynamicResource BorderSubtle}"
BorderThickness="1"
CornerRadius="{DynamicResource RadiusControl}">
<Grid ColumnDefinitions="Auto,*" Height="42">
<Border Grid.Column="0"
CornerRadius="7"
Width="36" Height="36"
Margin="3,0"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Border.Background>
<SolidColorBrush
Color="{Binding SelectedAccount.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=color}"
Opacity="0.15" />
</Border.Background>
<Svg Path="{Binding SelectedAccount.Icon, Converter={StaticResource SvgPathFromName}}"
Width="14" Height="14"
Css="{Binding SelectedAccount.Color, Converter={StaticResource HexToColorConverter}, ConverterParameter=css}" />
</Border>
<ComboBox Grid.Column="1"
VerticalAlignment="Stretch"
ItemsSource="{Binding Accounts}"
SelectedItem="{Binding SelectedAccount, Mode=TwoWay}"
DisplayMemberBinding="{Binding Name}"
Background="Transparent"
BorderThickness="0"
Padding="8,10"
FontSize="13"
HorizontalAlignment="Stretch" />
</Grid>
</Border>
</StackPanel>
</Grid>
<!-- ── Date ────────────────────────── -->
<TextBlock Text="DATE" Classes="label" FontSize="14" Margin="0,0,0,6" />
<Border Background="{DynamicResource BgBase}"
BorderBrush="{DynamicResource BorderSubtle}"
BorderThickness="1"
CornerRadius="{DynamicResource RadiusControl}"
Margin="0,0,0,16">
<Grid ColumnDefinitions="*,Auto" Height="42">
<cc:DateRangePicker Grid.Column="0"
Classes="ghost"
SelectionMode="SingleDate"
SelectedDates="{Binding Dates}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Padding="12,10" />
<Button Grid.Column="1"
Background="Transparent"
BorderThickness="0"
Padding="8,0"
Cursor="Hand"
CornerRadius="{DynamicResource RadiusControl}"
Margin="1"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
Command="{Binding SetTodayCommand}">
<TextBlock Text="Today"
FontSize="11"
Foreground="{DynamicResource AccentBlue}" />
</Button>
</Grid>
</Border>
<!-- ── Note ────────────────────────── -->
<TextBlock Text="NOTE (OPTIONAL)" Classes="label" FontSize="14" Margin="0,0,0,6" />
<TextBox Text="{Binding Note, Mode=TwoWay}"
Watermark="Add a note..."
FontSize="16"
Height="42"
Padding="12,0"
VerticalContentAlignment="Center"
Margin="0,0,0,8" />
<!-- ── Validation error ─────────────── -->
<Border Background="{DynamicResource BadgeBgRed}"
BorderBrush="{DynamicResource AccentRed}"
BorderThickness="1"
CornerRadius="10"
Padding="12,8"
Margin="0,8,0,16"
IsVisible="{Binding HasError}">
<StackPanel Orientation="Horizontal" Spacing="8">
<Svg Path="../Assets/Icons/circle-alert.svg"
Width="13" Height="13"
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FF5E5E; }" />
<TextBlock Text="{Binding ErrorMessage}"
FontSize="12"
Foreground="{DynamicResource AccentRed}"
VerticalAlignment="Center" />
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
<!-- ── Bottom action bar ──────────────────── -->
<Border Grid.Row="2"
Background="{DynamicResource BgSurface}"
BorderBrush="{DynamicResource BorderSubtle}"
BorderThickness="0,1,0,0"
Padding="16,12,16,20">
<Grid ColumnDefinitions="*,*">
<Button Grid.Column="0"
Classes="base"
Margin="0,0,6,0"
Padding="0,13"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
FontSize="14"
Content="Cancel"
Command="{Binding CancelCommand}" />
<Button Grid.Column="1"
Classes="accented"
Margin="6,0,0,0"
Padding="0,13"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
IsEnabled="{Binding IsValid}"
Command="{Binding SaveCommand}">
<StackPanel Orientation="Horizontal" Spacing="8">
<Svg Path="../Assets/Icons/check.svg"
Width="14" Height="14"
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #0D0F14; }" />
<TextBlock Text="{Binding SaveButtonLabel}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource BgBase}"
VerticalAlignment="Center" />
</StackPanel>
</Button>
</Grid>
</Border>
<Grid Grid.Row="0" Grid.RowSpan="3" IsVisible="{Binding ShowDeleteConfirm}">
<Border Background="#50000000" />
<Border HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="{DynamicResource BgSurface}"
BorderBrush="{DynamicResource AccentRed}"
BorderThickness="1"
CornerRadius="18"
Padding="28"
Width="340"
BoxShadow="0 24 72 0 #60000000">
<StackPanel Spacing="0">
<!-- Icon -->
<Border Background="#2A0D0D"
CornerRadius="14"
Width="52" Height="52"
HorizontalAlignment="Center"
Margin="0,0,0,16">
<Svg Path="../Assets/Icons/trash-2.svg"
Width="22" Height="22"
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FF5E5E; }" />
</Border>
<!-- Title -->
<TextBlock Text="Delete Transaction"
FontSize="16"
FontWeight="Bold"
Foreground="{DynamicResource TextPrimary}"
HorizontalAlignment="Center"
Margin="0,0,0,8" />
<!-- Description -->
<TextBlock Text="This action cannot be undone. The transaction will be permanently removed from your records."
FontSize="13"
Foreground="{DynamicResource TextMuted}"
TextWrapping="Wrap"
TextAlignment="Center"
HorizontalAlignment="Center"
Margin="0,0,0,24" />
<!-- Actions -->
<UniformGrid Rows="1">
<Button Classes="base"
Margin="0,0,6,0"
Padding="0,11"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
FontSize="13"
Content="Cancel"
Command="{Binding CancelDeleteCommand}" />
<Button Margin="6,0,0,0"
Padding="0,11"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Background="#FF5E5E"
BorderThickness="0"
CornerRadius="{DynamicResource RadiusControl}"
Command="{Binding ConfirmDeleteCommand}">
<StackPanel Orientation="Horizontal" Spacing="8">
<Svg Path="../Assets/Icons/trash-2.svg"
Width="13" Height="13"
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FFFFFF; }" />
<TextBlock Text="Delete"
FontSize="13"
FontWeight="SemiBold"
Foreground="#FFFFFF"
VerticalAlignment="Center" />
</StackPanel>
</Button>
</UniformGrid>
</StackPanel>
</Border>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Clario.MobileViews;
public partial class TransactionFormViewMobile : UserControl
{
public TransactionFormViewMobile()
{
InitializeComponent();
}
}

View File

@@ -306,21 +306,21 @@
<Setter Property="Background" Value="{DynamicResource BadgeBgGreen}" />
<Setter Property="CornerRadius" Value="{DynamicResource RadiusPill}" />
<Setter Property="Padding" Value="{DynamicResource BadgePadding}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentGreen}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentGreen}" />
</Style>
<Style Selector="Border.badge-red">
<Setter Property="Background" Value="{DynamicResource BadgeBgRed}" />
<Setter Property="CornerRadius" Value="{DynamicResource RadiusPill}" />
<Setter Property="Padding" Value="{DynamicResource BadgePadding}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentRed}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentRed}" />
</Style>
<Style Selector="Border.badge-yellow">
<Setter Property="Background" Value="{DynamicResource BadgeBgYellow}" />
<Setter Property="CornerRadius" Value="{DynamicResource RadiusPill}" />
<Setter Property="Padding" Value="{DynamicResource BadgePadding}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentYellow}"/>
<Setter Property="TextElement.Foreground" Value="{DynamicResource AccentYellow}" />
</Style>
<Style Selector="Border.badge-blue">
@@ -727,6 +727,12 @@
</Style>
<Style Selector=".mobile TextBox.numeric">
<Setter Property="(TextInputOptions.ContentType)" Value="Number" />
<Setter Property="(TextInputOptions.ReturnKeyType)" Value="Next" />
<Setter Property="(TextInputOptions.Multiline)" Value="False" />
</Style>
<Style Selector="TextBox.ghost">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />

View File

@@ -30,7 +30,7 @@ public partial class BudgetViewModel : ViewModelBase
private DateTime _currentPeriod = DateTime.Now.Date;
public bool CanGoToNextPeriod => CurrentPeriod.Month < DateTime.Now.Month;
public bool CanGoToPreviousPeriod => CurrentPeriod.Month > Transactions.Min(x => x.Date.Month);
public bool CanGoToPreviousPeriod => Transactions.Any() && CurrentPeriod.Month > Transactions.Min(x => x.Date.Month);
public string CurrentPeriodFormatted => CurrentPeriod.ToString("MMMM yyyy");
[ObservableProperty] private ISeries[] _spendingBreakdownChartSeries = [];

View File

@@ -31,6 +31,8 @@ public partial class DashboardViewModel : ViewModelBase
[ObservableProperty] private decimal _totalNetworth;
[ObservableProperty] private decimal _monthlyIncome;
private decimal _monthlyIncomeChange;
public int MaxChartWidth => SpendingByCategoryChartData.Count * 150;
public string MonthlyIncomeChangeFormatted => _monthlyIncomeChange >= 0
? "↑" + _monthlyIncomeChange.ToString("0.0%")
@@ -46,6 +48,10 @@ public partial class DashboardViewModel : ViewModelBase
public string AccountsSubtitle =>
AccountsSummaryData.Count == 1 ? $" {AccountsSummaryData.Count} linked Account" : $"{AccountsSummaryData.Count} linked Accounts";
public bool HasSpendingData => SpendingByCategoryChartData.Any();
public bool HasBudgetData => BudgetsTrackerData.Any();
public bool HasTransactionData => RecentTransactions.Any();
[ObservableProperty] private List<string> _chartTimePeriods = new()
{
"This Month",
@@ -175,19 +181,23 @@ public partial class DashboardViewModel : ViewModelBase
Values = x.Values,
Fill = x.Fill,
Padding = 4,
MaxBarWidth = double.MaxValue
MaxBarWidth = 150
}).ToArray();
OnPropertyChanged(nameof(HasSpendingData));
OnPropertyChanged(nameof(MaxChartWidth));
}
private async Task UpdateBudgetTracker()
{
var budgets = await DataRepo.General.FetchProcessedBudgets(new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1));
BudgetsTrackerData = new ObservableCollection<Budget>(budgets.Where(x => !x.GroupHeader).OrderByDescending(x => x.PercentageUsed));
OnPropertyChanged(nameof(HasBudgetData));
}
private void UpdateRecentTransactions()
{
RecentTransactions = new ObservableCollection<Transaction>(Transactions.OrderByDescending(x => x.Date).Take(5));
OnPropertyChanged(nameof(HasTransactionData));
}
private void UpdateAccountsSummary()

View File

@@ -125,35 +125,47 @@ public partial class MainViewModel : ViewModelBase
public void OpenAddTransaction()
{
if (IsTransactionFormVisible) return;
TransactionFormViewModel.SetupForAdd(
new ObservableCollection<Category>(_categories),
new ObservableCollection<Account>(_accounts)
);
TransactionFormViewModel.OnSaved = () =>
try
{
if (TransactionFormViewModel.ResultTransaction is not null)
TransactionFormViewModel.SetupForAdd(
new ObservableCollection<Category>(_categories),
new ObservableCollection<Account>(_accounts)
);
TransactionFormViewModel.OnSaved = () =>
{
var previousItem = _transactionsViewModel.AllTransactions.First(x => x.Date < TransactionFormViewModel.ResultTransaction.Date);
var index = _transactionsViewModel.AllTransactions.IndexOf(previousItem);
if (index != -1)
if (TransactionFormViewModel.ResultTransaction is not null)
{
var previousItem = _transactionsViewModel.AllTransactions.FirstOrDefault(x => x.Date < TransactionFormViewModel.ResultTransaction.Date);
var index = 0;
if (previousItem is not null)
index = _transactionsViewModel.AllTransactions.IndexOf(previousItem);
if (index == -1) index = 0;
_transactionsViewModel.AllTransactions.Insert(index, TransactionFormViewModel.ResultTransaction);
_transactionsViewModel.LoadPageCommand.Execute(1);
}
_dashboardViewModel.Transactions.Insert(index, TransactionFormViewModel.ResultTransaction);
_dashboardViewModel.UpdateUserOverviewCommand.Execute(null);
_transactionsViewModel.LoadPageCommand.Execute(1);
}
CloseTransactionForm();
};
TransactionFormViewModel.OnCancelled = () => CloseTransactionForm();
TransactionFormViewModel.OnDeleted = () =>
{
if (TransactionFormViewModel.ResultTransaction is { } resultTransaction)
CloseTransactionForm();
};
TransactionFormViewModel.OnCancelled = () => CloseTransactionForm();
TransactionFormViewModel.OnDeleted = () =>
{
_transactionsViewModel.AllTransactions.Remove(resultTransaction);
_transactionsViewModel.LoadPageCommand.Execute(1);
}
if (TransactionFormViewModel.ResultTransaction is { } resultTransaction)
{
_transactionsViewModel.AllTransactions.Remove(resultTransaction);
_transactionsViewModel.LoadPageCommand.Execute(1);
}
CloseTransactionForm();
};
IsTransactionFormVisible = true;
CloseTransactionForm();
};
IsTransactionFormVisible = true;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
[RelayCommand]

View File

@@ -65,6 +65,8 @@ public partial class TransactionFormViewModel : ViewModelBase
public Action? OnCancelled;
public Action? OnDeleted;
[ObservableProperty] private bool _showDeleteConfirm = false;
// ── Edit mode: original transaction ─────────────────────
private Transaction? _editingTransaction;
private Guid? _editingId;
@@ -74,6 +76,18 @@ public partial class TransactionFormViewModel : ViewModelBase
// ── Commands ────────────────────────────────────────────
partial void OnSelectedCategoryChanged(Category? value)
{
if (value.Type == Type) return;
Type = value.Type;
}
partial void OnTypeChanged(string value)
{
if (value == SelectedCategory?.Type) return;
SelectedCategory = _categories.FirstOrDefault(c => c.Type == value);
}
[RelayCommand]
private void SetType(string type)
{
@@ -164,7 +178,7 @@ public partial class TransactionFormViewModel : ViewModelBase
}
[RelayCommand]
private async Task Delete()
private async Task ConfirmDelete()
{
if (!IsEditMode || !_editingId.HasValue) return;
@@ -180,6 +194,18 @@ public partial class TransactionFormViewModel : ViewModelBase
}
}
[RelayCommand]
private void RequestDelete()
{
ShowDeleteConfirm = true;
}
[RelayCommand]
private void CancelDelete()
{
ShowDeleteConfirm = false;
}
[RelayCommand]
private void Cancel()
{
@@ -193,6 +219,7 @@ public partial class TransactionFormViewModel : ViewModelBase
ObservableCollection<Category> categories,
ObservableCollection<Account> accounts)
{
ShowDeleteConfirm = false;
IsEditMode = false;
_editingId = null;
Categories = categories;
@@ -214,6 +241,7 @@ public partial class TransactionFormViewModel : ViewModelBase
ObservableCollection<Category> categories,
ObservableCollection<Account> accounts)
{
ShowDeleteConfirm = false;
IsEditMode = true;
_editingId = transaction.Id;
Categories = categories;

View File

@@ -36,9 +36,9 @@
Margin="0,0,0,32">
<Border
CornerRadius="16"
Height="100"
HorizontalAlignment="Center">
<!-- <Image Source="../Assets/Logo textmark.png"></Image> -->
Height="80"
HorizontalAlignment="Center" Margin="0 0 0 10">
<Image Source="../Assets/logo-textmark.png"/>
</Border>
<!-- REPLACE: app name -->
<StackPanel Spacing="4" HorizontalAlignment="Center">

View File

@@ -121,42 +121,58 @@
CornerRadius="{StaticResource RadiusIcon}" Padding="10,6">
</ComboBox>
</Grid>
<lvc:CartesianChart Series="{Binding SpendingByCategoryChartSeries}" Height="250" Background="{DynamicResource BgSurface}"
LegendPosition="Hidden" TooltipPosition="Hidden" ZoomMode="None" Name="chart">
<lvc:CartesianChart.XAxes>
<lvc:XamlAxis IsVisible="False" />
</lvc:CartesianChart.XAxes>
<lvc:CartesianChart.YAxes>
<lvc:XamlLogarithmicAxis LogBase="10" IsVisible="False" MinLimit="1" />
</lvc:CartesianChart.YAxes>
</lvc:CartesianChart>
<ItemsControl ItemsSource="{Binding SpendingByCategoryChartData}" Margin="0 -10 ">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="model:ColumnChartData">
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center" Foreground="{DynamicResource TextDisabled}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border HorizontalAlignment="Stretch" Height="1" Background="{DynamicResource BorderSubtle}" Margin="5 0" />
<ItemsControl ItemsSource="{Binding SpendingByCategoryChartData}" Margin="0 -10 0 0 ">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="model:ColumnChartData">
<TextBlock Text="{Binding Values, Converter={StaticResource FirstValueConverter},StringFormat='$0,00'}"
HorizontalAlignment="Center" Foreground="{Binding Fill, Converter={StaticResource SkPaintToBrushConverter}}"
FontWeight="SemiBold" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Panel>
<StackPanel Spacing="20" IsVisible="{Binding HasSpendingData}">
<lvc:CartesianChart Series="{Binding SpendingByCategoryChartSeries}" Height="250" Background="{DynamicResource BgSurface}"
LegendPosition="Hidden" TooltipPosition="Hidden" ZoomMode="None" Name="chart">
<lvc:CartesianChart.XAxes>
<lvc:XamlAxis IsVisible="False" />
</lvc:CartesianChart.XAxes>
<lvc:CartesianChart.YAxes>
<lvc:XamlLogarithmicAxis LogBase="10" IsVisible="False" MinLimit="1" />
</lvc:CartesianChart.YAxes>
</lvc:CartesianChart>
<ItemsControl ItemsSource="{Binding SpendingByCategoryChartData}" Margin="0 -10 ">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" HorizontalAlignment="Stretch" MaxWidth="{Binding MaxChartWidth}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="model:ColumnChartData">
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center" Foreground="{DynamicResource TextDisabled}"
Margin="4 0" TextTrimming="CharacterEllipsis" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border HorizontalAlignment="Stretch" Height="1" Background="{DynamicResource BorderSubtle}" Margin="5 0" />
<ItemsControl ItemsSource="{Binding SpendingByCategoryChartData}" Margin="0 -10 0 0 ">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" HorizontalAlignment="Stretch" MaxWidth="{Binding MaxChartWidth}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="model:ColumnChartData">
<TextBlock Text="{Binding Values, Converter={StaticResource FirstValueConverter},StringFormat='$0'}"
HorizontalAlignment="Center" Margin="4 0" TextTrimming="CharacterEllipsis"
Foreground="{Binding Fill, Converter={StaticResource SkPaintToBrushConverter}}"
FontWeight="SemiBold" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="12" Margin="0,60"
IsVisible="{Binding !HasSpendingData}">
<Svg Path="../Assets/Icons/chart-column.svg" Css="{DynamicResource SvgDisabled}" Height="40" Width="40" />
<TextBlock Text="No spending data" FontSize="15" FontWeight="SemiBold"
Foreground="{DynamicResource TextDisabled}" HorizontalAlignment="Center" />
<TextBlock Text="Add expense transactions to see your spending breakdown." FontSize="13"
Foreground="{DynamicResource TextDisabled}" HorizontalAlignment="Center" TextWrapping="Wrap"
TextAlignment="Center"
MaxWidth="250" />
</StackPanel>
</Panel>
</StackPanel>
</Border>
<!-- Budget Tracker -->
@@ -168,49 +184,63 @@
Foreground="{DynamicResource TextPrimary}" />
<TextBlock Classes="muted" Text="Monthly limits" />
</StackPanel>
<ItemsControl ItemsSource="{Binding BudgetsTrackerData}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="20" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="model:Budget">
<StackPanel Spacing="8">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4">
<Svg Path="{Binding Category.Icon, Converter={StaticResource SvgPathFromName}}" Height="13" Width="13"
VerticalAlignment="Center" />
<TextBlock Text="{Binding Category.Name}" FontSize="{StaticResource FontSizeBody}"
VerticalAlignment="Center" Foreground="{DynamicResource TextSecondary}" />
</StackPanel>
<Panel Grid.Column="1">
<StackPanel Orientation="Horizontal" IsVisible="{Binding !IsOverBudget}">
<TextBlock Text="{Binding Spent, StringFormat='$0'}" FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource TextMuted}" />
<TextBlock Text=" / " FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource TextMuted}" />
<TextBlock Text="{Binding LimitAmount, StringFormat='$0'}" FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource TextMuted}" />
<Panel>
<ItemsControl ItemsSource="{Binding BudgetsTrackerData}" IsVisible="{Binding HasBudgetData}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="20" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="model:Budget">
<StackPanel Spacing="8">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4">
<Svg Path="{Binding Category.Icon, Converter={StaticResource SvgPathFromName}}" Height="13"
Width="13"
VerticalAlignment="Center" />
<TextBlock Text="{Binding Category.Name}" FontSize="{StaticResource FontSizeBody}"
VerticalAlignment="Center" Foreground="{DynamicResource TextSecondary}" />
</StackPanel>
<StackPanel Orientation="Horizontal" IsVisible="{Binding IsOverBudget}">
<TextBlock Text="{Binding Spent, StringFormat='$0'}" FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource AccentRed}" />
<TextBlock Text=" / " FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource AccentRed}" />
<TextBlock Text="{Binding LimitAmount, StringFormat='$0'}" FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource AccentRed}" />
</StackPanel>
</Panel>
</Grid>
<ProgressBar Classes.green="{Binding IsOnTrack}" Classes.yellow="{Binding IsWarning}"
Classes.red="{Binding IsOverBudget}" Minimum="0" Value="{Binding Spent}"
Maximum="{Binding LimitAmount}" />
<Separator Margin="-8 4" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Panel Grid.Column="1">
<StackPanel Orientation="Horizontal" IsVisible="{Binding !IsOverBudget}">
<TextBlock Text="{Binding Spent, StringFormat='$0'}" FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource TextMuted}" />
<TextBlock Text=" / " FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource TextMuted}" />
<TextBlock Text="{Binding LimitAmount, StringFormat='$0'}"
FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource TextMuted}" />
</StackPanel>
<StackPanel Orientation="Horizontal" IsVisible="{Binding IsOverBudget}">
<TextBlock Text="{Binding Spent, StringFormat='$0'}" FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource AccentRed}" />
<TextBlock Text=" / " FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource AccentRed}" />
<TextBlock Text="{Binding LimitAmount, StringFormat='$0'}"
FontSize="{StaticResource FontSizeMeta}"
Foreground="{DynamicResource AccentRed}" />
</StackPanel>
</Panel>
</Grid>
<ProgressBar Classes.green="{Binding IsOnTrack}" Classes.yellow="{Binding IsWarning}"
Classes.red="{Binding IsOverBudget}" Minimum="0" Value="{Binding Spent}"
Maximum="{Binding LimitAmount}" />
<Separator Margin="-8 4" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="12" Margin="0,60"
IsVisible="{Binding !HasBudgetData}">
<Svg Path="../Assets/Icons/wallet.svg" Css="{DynamicResource SvgDisabled}" Height="40" Width="40" />
<TextBlock Text="No budgets set" FontSize="15" FontWeight="SemiBold"
Foreground="{DynamicResource TextDisabled}" HorizontalAlignment="Center" />
<TextBlock Text="Create budgets to track your spending limits." FontSize="13"
Foreground="{DynamicResource TextDisabled}" HorizontalAlignment="Center" TextWrapping="Wrap"
TextAlignment="Center" MaxWidth="200" />
</StackPanel>
</Panel>
</StackPanel>
</ScrollViewer>
</Border>
@@ -231,50 +261,64 @@
FontSize="{StaticResource FontSizeBody}" Cursor="Hand" Content="View all →" VerticalAlignment="Center"
Command="{Binding ViewAllTransactionsCommand}" />
</Grid>
<ItemsControl ItemsSource="{Binding RecentTransactions}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="18" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="model:Transaction">
<StackPanel Spacing="4">
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0,4,0,0">
<Border Grid.Column="0" CornerRadius="{StaticResource RadiusControl}" Width="42" Height="42" Margin="0,0,14,0">
<Border.Background>
<SolidColorBrush
Color="{Binding Category.Color, Converter={StaticResource HexToColorConverter},ConverterParameter=color}"
Opacity="0.15" />
</Border.Background>
<Svg Path="{Binding Category.Icon,Converter={StaticResource SvgPathFromName}}" Height="18" Width="18"
Css="{Binding Category.Color, Converter={StaticResource HexToColorConverter},ConverterParameter=css}" />
</Border>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{Binding Description}" FontSize="{StaticResource FontSizeBody}" FontWeight="SemiBold"
Foreground="{DynamicResource TextSecondary}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Category.Name}" Classes="muted" /> <TextBlock Text=" · " Classes="muted" />
<TextBlock
Text="{Binding Date,Converter={StaticResource DateFormatConverter},ConverterParameter='MMM d'}"
Classes="muted" />
<Panel>
<ItemsControl ItemsSource="{Binding RecentTransactions}" IsVisible="{Binding HasTransactionData}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="18" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="model:Transaction">
<StackPanel Spacing="4">
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0,4,0,0">
<Border Grid.Column="0" CornerRadius="{StaticResource RadiusControl}" Width="42" Height="42"
Margin="0,0,14,0">
<Border.Background>
<SolidColorBrush
Color="{Binding Category.Color, Converter={StaticResource HexToColorConverter},ConverterParameter=color}"
Opacity="0.15" />
</Border.Background>
<Svg Path="{Binding Category.Icon,Converter={StaticResource SvgPathFromName}}" Height="18" Width="18"
Css="{Binding Category.Color, Converter={StaticResource HexToColorConverter},ConverterParameter=css}" />
</Border>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{Binding Description}" FontSize="{StaticResource FontSizeBody}" FontWeight="SemiBold"
Foreground="{DynamicResource TextSecondary}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Category.Name}" Classes="muted" />
<TextBlock Text=" · " Classes="muted" />
<TextBlock
Text="{Binding Date,Converter={StaticResource DateFormatConverter},ConverterParameter='MMM d'}"
Classes="muted" />
</StackPanel>
</StackPanel>
</StackPanel>
<TextBlock Grid.Column="2" FontSize="{StaticResource FontSizeAmount}" FontWeight="SemiBold"
Foreground="{Binding Type, Converter={StaticResource AmountColorConverter}}"
VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource AmountSignConverter}">
<Binding Path="Amount" /> <Binding Path="Type" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
<Separator />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Grid.Column="2" FontSize="{StaticResource FontSizeAmount}" FontWeight="SemiBold"
Foreground="{Binding Type, Converter={StaticResource AmountColorConverter}}"
VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource AmountSignConverter}">
<Binding Path="Amount" /> <Binding Path="Type" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
<Separator />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="12" Margin="0,60"
IsVisible="{Binding !HasTransactionData}">
<Svg Path="../Assets/Icons/receipt.svg" Css="{DynamicResource SvgDisabled}" Height="40" Width="40" />
<TextBlock Text="No transactions yet" FontSize="15" FontWeight="SemiBold"
Foreground="{DynamicResource TextDisabled}" HorizontalAlignment="Center" />
<TextBlock Text="Start tracking your finances by adding a transaction." FontSize="13"
Foreground="{DynamicResource TextDisabled}" HorizontalAlignment="Center" TextWrapping="Wrap"
TextAlignment="Center"
MaxWidth="250" />
</StackPanel>
</Panel>
</StackPanel>
</Border>
<!-- Accounts Summary -->

View File

@@ -91,20 +91,12 @@
<Grid Grid.Row="1" ColumnDefinitions="220,*">
<!-- ───────────────────────────────────── SIDEBAR ───────────────────────────────────── -->
<Border Grid.Column="0" Background="{DynamicResource BgSidebar}" BorderBrush="{DynamicResource BorderSubtle}"
BorderThickness="0,0,1,0" Padding="16,28,16,24">
BorderThickness="0,0,1,0" Padding="16,28,16,24" IsEnabled="{Binding !IsTransactionFormVisible}">
<DockPanel>
<!-- Logo / App Name -->
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,36">
<StackPanel Orientation="Horizontal" Spacing="10">
<Border Background="{DynamicResource AccentBlue}" CornerRadius="{StaticResource RadiusIcon}" Width="32" Height="32">
<TextBlock Text="F" FontSize="16" FontWeight="Bold" Foreground="{DynamicResource BgBase}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="Clario" FontSize="17" FontWeight="Bold"
Foreground="{DynamicResource TextPrimary}" />
</StackPanel>
</StackPanel>
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,24">
<!-- <Svg Path="../Assets/logo-textmark-no-color.svg" Css="{DynamicResource SvgSecondary}" HorizontalAlignment="Stretch" -->
<!-- Margin="4,12,24,0"/> -->
</StackPanel>
<!-- User Profile (bottom) -->
<Border DockPanel.Dock="Bottom" Background="{DynamicResource BgSurface}" CornerRadius="{StaticResource RadiusInset}"
@@ -186,10 +178,11 @@
</StackPanel>
</DockPanel>
</Border>
<Border Grid.Column="0" Background="#70000000" IsVisible="{Binding IsTransactionFormVisible}" />
<Grid Grid.Column="1">
<ContentControl Content="{Binding CurrentView}" />
<views:TransactionFormView
DataContext="{Binding TransactionFormViewModel}"
DataContext="{Binding TransactionFormViewModel}"
IsVisible="{Binding DataContext.IsTransactionFormVisible,ElementName=MainControl}">
</views:TransactionFormView>
</Grid>

View File

@@ -7,6 +7,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
MinWidth="1000" MinHeight="600"
x:Class="Clario.Views.MainWindow"
Icon="../Assets/logo-no-bg.ico"
Title="Clario"
x:CompileBindings="False">
<ContentControl Content="{Binding}" />

View File

@@ -4,6 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Clario.ViewModels"
xmlns:cc="clr-namespace:Clario.CustomControls"
xmlns:behaviors="clr-namespace:Clario.Behaviors"
mc:Ignorable="d"
x:Class="Clario.Views.TransactionFormView"
x:DataType="vm:TransactionFormViewModel">
@@ -87,7 +88,7 @@
CommandParameter="expense">
<StackPanel Orientation="Horizontal" Spacing="6">
<Svg Path="../Assets/Icons/arrow-up-right.svg"
Width="13" Height="13"/>
Width="13" Height="13" />
<TextBlock Text="Expense"
FontSize="13"
FontWeight="SemiBold"
@@ -107,7 +108,7 @@
CommandParameter="income">
<StackPanel Orientation="Horizontal" Spacing="6">
<Svg Path="../Assets/Icons/arrow-down-left.svg"
Width="13" Height="13"/>
Width="13" Height="13" />
<TextBlock Text="Income"
FontSize="13"
VerticalAlignment="Center" />
@@ -141,7 +142,11 @@
Foreground="{DynamicResource TextPrimary}"
Height="54"
Padding="0"
VerticalContentAlignment="Center" />
VerticalContentAlignment="Center">
<Interaction.Behaviors>
<behaviors:NumericInputBehavior />
</Interaction.Behaviors>
</TextBox>
<TextBlock Grid.Column="2"
Text="{Binding Currency}"
FontSize="12"
@@ -165,7 +170,12 @@
<!-- Category -->
<StackPanel Grid.Column="0" Spacing="6">
<TextBlock Text="CATEGORY" Classes="label" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="CATEGORY" Classes="label" />
<Button Classes="nav" Padding="2 0">
<Svg Path="../Assets/Icons/plus.svg" Height="11" Width="11"></Svg>
</Button>
</StackPanel>
<Border Background="{DynamicResource BgBase}"
BorderBrush="{DynamicResource BorderSubtle}"
BorderThickness="1"
@@ -248,7 +258,7 @@
SelectionMode="SingleDate"
SelectedDates="{Binding Dates}"
HorizontalAlignment="Stretch"
Padding="12,10"/>
Padding="12,10" />
<Button Grid.Column="1"
Background="Transparent"
BorderThickness="0"
@@ -302,7 +312,7 @@
Padding="0,10"
Margin="0,0,0,10"
IsVisible="{Binding IsEditMode}"
Command="{Binding DeleteCommand}">
Command="{Binding RequestDeleteCommand}">
<StackPanel Orientation="Horizontal" Spacing="8">
<Svg Path="../Assets/Icons/trash-2.svg"
Width="13" Height="13"
@@ -316,9 +326,8 @@
</Button>
<!-- ── Actions ──────────────────────── -->
<Grid ColumnDefinitions="*,*">
<Button Grid.Column="0"
Classes="base"
<UniformGrid Rows="1">
<Button Classes="base"
Margin="0,0,6,0"
Padding="0,11"
HorizontalAlignment="Stretch"
@@ -326,8 +335,7 @@
FontSize="13"
Content="Cancel"
Command="{Binding CancelCommand}" />
<Button Grid.Column="1"
Classes="accented"
<Button Classes="accented"
Margin="6,0,0,0"
Padding="0,11"
HorizontalAlignment="Stretch"
@@ -345,9 +353,87 @@
VerticalAlignment="Center" />
</StackPanel>
</Button>
</Grid>
</UniformGrid>
</StackPanel>
</Border>
<!-- DELETE CONFIRM MODAL -->
<!-- ── Delete confirm modal ──────────────── -->
<Grid IsVisible="{Binding ShowDeleteConfirm}">
<Border Background="#50000000" />
<Border HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="{DynamicResource BgSurface}"
BorderBrush="{DynamicResource AccentRed}"
BorderThickness="1"
CornerRadius="18"
Padding="28"
Width="340"
BoxShadow="0 24 72 0 #60000000">
<StackPanel Spacing="0">
<!-- Icon -->
<Border Background="#2A0D0D"
CornerRadius="14"
Width="52" Height="52"
HorizontalAlignment="Center"
Margin="0,0,0,16">
<Svg Path="../Assets/Icons/trash-2.svg"
Width="22" Height="22"
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FF5E5E; }" />
</Border>
<!-- Title -->
<TextBlock Text="Delete Transaction"
FontSize="16"
FontWeight="Bold"
Foreground="{DynamicResource TextPrimary}"
HorizontalAlignment="Center"
Margin="0,0,0,8" />
<!-- Description -->
<TextBlock Text="This action cannot be undone. The transaction will be permanently removed from your records."
FontSize="13"
Foreground="{DynamicResource TextMuted}"
TextWrapping="Wrap"
TextAlignment="Center"
HorizontalAlignment="Center"
Margin="0,0,0,24" />
<!-- Actions -->
<UniformGrid Rows="1">
<Button Classes="base"
Margin="0,0,6,0"
Padding="0,11"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
FontSize="13"
Content="Cancel"
Command="{Binding CancelDeleteCommand}" />
<Button Margin="6,0,0,0"
Padding="0,11"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Background="#FF5E5E"
BorderThickness="0"
CornerRadius="{DynamicResource RadiusControl}"
Command="{Binding ConfirmDeleteCommand}">
<StackPanel Orientation="Horizontal" Spacing="8">
<Svg Path="../Assets/Icons/trash-2.svg"
Width="13" Height="13"
Css="path, circle, rect, ellipse, line, polyline, polygon, text, use { stroke: #FFFFFF; }" />
<TextBlock Text="Delete"
FontSize="13"
FontWeight="SemiBold"
Foreground="#FFFFFF"
VerticalAlignment="Center" />
</StackPanel>
</Button>
</UniformGrid>
</StackPanel>
</Border>
</Grid>
</Grid>
</UserControl>

View File

@@ -22,5 +22,7 @@
<PackageVersion Include="SkiaSharp.NativeAssets.WebAssembly" Version="3.116.1" />
<PackageVersion Include="Supabase" Version="1.1.1" />
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.15" />
<PackageVersion Include="Xaml.Behaviors.Interactions" Version="11.3.6.6" />
<PackageVersion Include="Xaml.Behaviors.Interactivity" Version="11.3.6.6" />
</ItemGroup>
</Project>