Luu Thai Toan
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # [WP] BistroQ - Milestone 1 Report :::info This report can be viewed in [HackMD](https://hackmd.io/@CXS0caWySWi1obKmwFzL-Q/tondeptrai) (We strongly recommend to read on the web for better reading experience). ::: :::info Link to project: [Google Drive](https://drive.google.com/drive/folders/1ciKlv2l09x2DuuUC523eUsIVdXE5pfft?usp=sharing) ::: ## Table Of Contents - [Overview](#Overview) - [Project Setup](#Project-Setup) - [Backend Setup](#Backend-Setup) - [WinUI Setup](#WinUI-Setup) - [Features](#Features) - [Planning](#Planning) - [Final](#Final) - [Completed Works](#Completed-Works) - [Technical Debt](#Technical-Debt) - [UI/UX](#UIUX) - [Design Pattern](#Design-Pattern) - [Dependency Injection](#Dependency-Injection) - [Command Pattern](#Command-Pattern) - [Builder Pattern](#Builder-Pattern) - [Repository Pattern](#Repository-Pattern) - [UnitOfWork Pattern](#UnitOfWork-Pattern) - [Singleton Pattern](#Singleton-Pattern) - [Architecture](#Architecture) - [WinUI Architecture](#WinUI-Architecture) - [Backend Architecture](#Backend-Architecture) - [Advanced Topics](#Advanced-Topics) - [Semaphore](#Semaphore) - [ICommand and RelayCommand](#ICommand-and-RelayCommand) - [DelegatingHandler](#DelegatingHandler) - [DI Containers](#DI-Containers) - [JWT](#JWT) - [DPAPI](#DPAPI) - [INotifyDataErrorInfo](#INotifyDataErrorInfo) - [Git Flow Evidence](#Git-Flow-Evidence) - [Code Review Evidence](#Code-Review-Evidence) - [Login Page WinUI](#Login-Page-WinUI) - [Validator WinUI](#Validator-WinUI) - [Zone Admin Page WinUI](#Zone-Admin-Page-WinUI) - [Navigation Pane WinUI](#Navigation-Pane-WinUI) - [Skeleton API](#Skeleton-API) - [Authentication/Authorization API](#Authentication/Authorization-API) - [Category API](#Category-API) - [Table Zone API](#Table-Zone-API) - [Hotfix Zone Table API](#Hotfix-Zone-Table-API) ## Overview Tech stack: - WinUI .NET 7.0 - ASP.NET Framework .NET 8.0 - MySQL + Entity Framework Core > [!Important] >Despite this course mainly focused on WinUI 3 Programming, we would greatly appreciate if you can give us feedback on the backend of our project. We believe that your expertise could provide us with valuable insights and practical knowledge that would enhance our learning experience. Your review would help us a lot in understand real-world applications and improve our skills. ## Project Setup ### Backend Setup 1. Install dependencies ```zsh dotnet tool install --global dotnet-ef dotnet tool update --global dotnet-ef ``` 2. Create ENV - Create file `.env` based on the file `.env.sample` in the Solution directory. 3. Ensure MySQL and database is working. 4. Run migration ```zsh dotnet ef database update ``` 5. Run project 5.1. If running in an IDE - Run `BistroQ.API` Project through Visual Studio. 5.2. If running using .NET CLI ```zsh dotnet restore dotnet watch run --project BistroQ.API ``` ### WinUI Setup 1. Have backend server running successfully. 2. Run `BistroQ` Project through Visual Studio. ### Testing Account | Role | Username | Password | | ------- | -------- | -------- | | Admin | admin | admin | | Kitchen | Kitchen | Kitchen | | Cashier | cashier | cashier | | Client | client1 | client | | Client | client2 | client | | Client | client3 | client | | Client | client4 | client | | Client | client5 | client | | Client | client6 | client | | Client | client7 | client | | Client | client8 | client | | Client | client9 | client | ## Features ### Planning We planned to do these features in Milestone 1: - Authentication/Authorization: Login, create account for clients. - Available Table Management: Control the number of available tables and the orders for occupied tables. Take-away and delivery channels are not included in this control. - Tables Changing: Allow customers to change their table. - Client can make orders: Have an UI for user to order a dish. ### Final We were unable to complete all the tasks as planned. Despite our efforts, several tasks remain incomplete or delayed. However, it still meets the requirement of 1/3 of the working hours (approximately 6 hours). | Task | Working hours (Estimated) | Responsible by | | ----------------------------------------------------- | ------------------------------ | -------------- | | API for zones, tables | 0.5 hour | Tiến | | API for login | 0.5 hour | **Both** | | API for products, categories | 0.5 hour | Toàn | | Pagination user control | 0.75 hour | Tiến | | Login page | 1.25 hour | Toàn | | Zone management page (CRUD) with sorting, pagination | 0.125 hour *(CRUD excluded)* | Tiến | | Table management page (CRUD) with sorting, pagination | 0.125 hour *(CRUD excluded)* | Tiến | | Table management page for cashier (use mock data) | 1 hour | Toàn | | Generic validation | 1 hour | Toàn | | Navigation based on user role | 0.5 hour | Toàn | | Order page for client (use mock data) | 1 hour | Toàn | | ApiClient for API calls in WinUI app | 0.25 hour | **Both** | :::info Total estimated working hours: 7.5 hours ::: Reason: - Starting the project from scratch, choosing the right architecture anDd organizing the code was a challenge and time-consuming than expected. - An additional API needs to be built to support specific functions. ### Completed Works Authentication/Authorization: - Works with real users. - Automatically remember users. - Logout supported. - Navigate to user's role-specific page. - Store token in a secure way. Available Table Management: - Cashiers can view the status of tables that are currently occupied. - Cashiers can also access the order details for each table. Admin Resource Management: - CRUD for Zones and Tables. Client Order: - Create new order and view current order (No data). ### Technical Debt These technical debts will be resolved in the next milestone. :::warning - Using `async` inside constructor `Shell Page`. - Mismatch ID data type between resources. - `Available Table Management` feature is not completed. No animation, lack of highlighted parts. - WinUI ::: ## UI/UX We strongly follows the Windows 11 design guidelines, mentioned in WinUI 3 Gallery. (Mica backdrops, spacing, etc.) :::warning Some parts of our project still use WPF components because the equivalent components in WinUI 3 are either still in beta or not yet available. ::: :::info **Login** ::: Login Screen when invalid input ![image](https://hackmd.io/_uploads/SJo2sMKb1x.png) Loading animation ![image](https://hackmd.io/_uploads/ryalCfYWyl.png) Invalid password ![image](https://hackmd.io/_uploads/rkOF0zYWkl.png) :::info **Admin Zone** ::: Zones View ![image](https://hackmd.io/_uploads/ByFpjfYWke.png) Add Zone Form ![image](https://hackmd.io/_uploads/Byk13zKWyx.png) Added successfully ![image](https://hackmd.io/_uploads/SJkehfKbJl.png) After added zone ![image](https://hackmd.io/_uploads/BkPehGt-Jg.png) Choose a zone ![image](https://hackmd.io/_uploads/r1kZ3ft-Jl.png) Edit zone form ![image](https://hackmd.io/_uploads/SkjQhzYZyl.png) Update zone successfully ![image](https://hackmd.io/_uploads/Hkgf3GY-kg.png) :::info **Pagination** ::: Choosing rows per page ![image](https://hackmd.io/_uploads/S1R5nMK-yl.png) After choose row ![image](https://hackmd.io/_uploads/HyLsnzFZye.png) Page 2 with 10 rows per page ![image](https://hackmd.io/_uploads/Hyf22fYbke.png) :::info **Sorting** ::: Some fields are sortable ![image](https://hackmd.io/_uploads/SyeoyQKbJx.png) :::info **Admin Tables** ::: Admin tables view ![image](https://hackmd.io/_uploads/H1HphMtWyx.png) Add table form ![image](https://hackmd.io/_uploads/SknT2GY-ke.png) Pick zone from existing zone ![image](https://hackmd.io/_uploads/SJk16zYb1x.png) :::info **Cashier Table** ::: ![image](https://hackmd.io/_uploads/ryKYjzYZ1e.png) :::info Client Order ::: ![image](https://hackmd.io/_uploads/SykXRfFZJe.png) :::info Logout Button ::: ![image](https://hackmd.io/_uploads/HJ_cofF-kl.png) ## Design Pattern ### **Dependency Injection** In our application, we need to store tokens. We started by implementing `TokenJsonStorageService` to save tokens in a JSON file. Later, we upgraded to `TokenSecureStorageService` to securely store tokens using DPAPI (Data Protection API). ```csharp!= // ITokenStorageService.cs public interface ITokenStorageService { Task SaveAccessToken(string accessToken); Task<string> GetAccessToken(); } // TokenJsonStorageService.cs public class TokenJsonStorageService : ITokenStorageService { Task SaveAccessToken(string accessToken) { /* Save token in JSON file */ } Task<string> GetAccessToken(); { /* Retrieve token from JSON */ } } // TokenSecureStorageService.cs public class TokenSecureStorageService : ITokenStorageService { Task SaveAccessToken(string accessToken) { /* Save token securely with DPAPI */ } Task<string> GetAccessToken(); { /* Retrieve token from secure storage */ } } // App.xaml.cs services.AddSingleton<ITokenStorageService, TokenJsonStorageService>(); // Use DI to inject this service ``` ### **Command Pattern** Instead of creating `EventHandler` in `LoginPage`, using `ICommand` class (which implements Command Pattern) in `ViewModel` makes the code easier to test and improves separation of concerns. ```csharp!= // LoginViewModel.cs public ICommand LoginCommand { get; } public LoginViewModel() { LoginCommand = new AsyncRelayCommand(async () => { await Login(); }); } ``` ```xml!= <!-- LoginPage.xaml --> <!-- Clicking this button will execute LoginCommand --> <Button Style="{StaticResource AccentButtonStyle}" Width="60" Command="{x:Bind ViewModel.LoginCommand}"/> ``` ### **Builder Pattern** To retrieve resources from the database, we use `BaseQueryableBuilder` to construct an `IQueryable` query. This builder allows chaining configurations for filtering, sorting, and paginating data, making it easy to assemble different query conditions. ```csharp!= // BaseQueryableBuilder.cs public abstract class BaseQueryableBuilder<T> where T : class { protected IQueryable<T> Queryable; protected BaseQueryableBuilder(IQueryable<T> queryable) { Queryable = queryable; } public IQueryable<T> Build() { return Queryable; } } // ProductQueryableBuilder.cs public class ProductQueryableBuilder : BaseQueryableBuilder<Product> { public ProductQueryableBuilder WithName(string? name) {/* Apply filter for name */} public ProductQueryableBuilder ApplyPaging(int page, int size) {/* Apply pagination */} public ProductQueryableBuilder ApplySorting(string? orderBy, string? orderDirection) {/* Apply sorting */} } // Usage: var builder = new ProductQueryableBuilder(_unitOfWork.ProductRepository.GetQueryable()) .WithName(queryParams.Name) .ApplySorting(queryParams.OrderBy, queryParams.OrderDirection) .ApplyPaging(queryParams.Page, queryParams.Size); ``` This can be used to construct complex LINQ queries or queryable objects (`IQueryable`) dynamically based on various conditions. ### **Repository Pattern** Encapsulates data access logic, keeping it separate from business logic. Implementing this pattern makes it easier to change the ORM if needed or enhance database scaling techniques (Database Replication, Caching, etc). ### **UnitOfWork Pattern** A `UnitOfWork` class that encapsulates transaction management across multiple repositories for a single operation. For example in the application, `UnitOfWork` coordinates between `OrderRepository` and `ProductRepository` when placing an order. If updating the order and adjusting product stock both succeed, the transaction is committed; if one fails, both are rolled back. ```csharp!= public interface IUnitOfWork { IProductRepository ProductRepository { get; } IOrderRepository OrderRepository { get; } ICategoryRepository CategoryRepository { get; } Task<int> SaveChangesAsync(); Task BeginTransactionAsync(); Task CommitTransactionAsync(); Task RollbackTransactionAsync(); } ``` ### **Singleton Pattern** The Singleton pattern ensures a class has only one instance and provides a global access point to it (Most `Services` in application implements this pattern). Besides, we use a singleton for `JwtSettings` to manage the same authentication settings for all parts of our API. ```csharp!= public class JwtSettings { public string SecretKey { get; set; } public string Issuer { get; set; } public string Audience { get; set; } public int AccessTokenExpiresInMinutes { get; set; } public int RefreshTokenExpiredInMinutes { get; set; } public JwtSettings ReadFromEnvironment() { /* Read data when program starts */ } } ``` ## Architecture :::success The architecture of the system is designed to promote the principles of separation of concerns, maintainability, and scalability. ::: ### WinUI Architecture The WinUI application follows `MVVM` architecture, and separates into 3 Projects within a Solution: - **UI Layer**: Contains Views, ViewModels, Styles and application's configuration. - **Core Layer**: Implements business logic and stores Models, Dtos. *Folder Structure*: ``` . ├── BistroQ │   ├── Activation │   ├── Assets │   ├── Behaviors │   ├── Contracts │   │   ├── Services │   │   └── ViewModels │   ├── Converters │   ├── Helpers │   ├── Models │   ├── Properties │   │   └── PublishProfiles │   ├── Services │   ├── Strings │   │   └── en-us │   ├── Styles │   ├── Validation │   ├── ViewModels │   │   ├── AdminTable │   │   ├── AdminZone │   │   └── Client │   └── Views │   ├── AdminTable │   ├── AdminZone │   ├── Client │   └── UserControls ├── BistroQ.Core │   ├── Contracts │   │   └── Services │   ├── Dtos │   │   ├── Tables │   │   └── Zones │   ├── Helpers │   ├── Models │   │   ├── Entities │   │   └── Exceptions │   └── Services │   ├── Auth │   ├── Http │   ├── Mock │   └── Order └── BistroQ.Tests.MSTest ``` ### Backend Architecture The API follows `Layered Architecture Pattern`, and separates into 4 Projects inside a Solution: - **Core Layer**: Contains Dtos, Models and Interfaces. - **Infrastructure Layer**: Also called Data Access Layer, implements Repositories and DbContext. - **Services Layer**: Implements business logic and application services. - **API Layer**: Manages HTTP requests, client communication and application's configuration. *Folder Structure*: ``` . ├── BistroQ.API │   ├── Configurations │   ├── Controllers │   │   ├── Auth │   │   ├── Category │   │   ├── Product │   │   ├── Table │   │   └── Zone │   ├── Middlewares │   └── Properties ├── BistroQ.Core │   ├── Common │   │   ├── Builder │   │   └── Settings │   ├── Dtos │   │   ├── Auth │   │   ├── Category │   │   ├── Products │   │   ├── Tables │   │   └── Zones │   ├── Entities │   ├── Enums │   ├── Exceptions │   ├── Interfaces │   │   ├── Repositories │   │   └── Services │   ├── Mappings │   └── Specifications ├── BistroQ.Infrastructure │   ├── Data │   ├── Migrations │   ├── Repositories │   └── UnitOfWork ├── BistroQ.Services │   └── Services └── bistro-q-api ``` *Diagrams*: :::info This diagram displays the folder structure above ::: ![Backend1](https://hackmd.io/_uploads/BypyIsN-Jl.png) :::info The diagram below illustrates the interaction flow for a typical HTTP request from a client application to the API. ::: ![Backend2](https://hackmd.io/_uploads/B1QkNiE-1l.png) ## Advanced Topics ### **Semaphore** `Semaphore` class is used to control access to a pool of resources in concurrency operations. It limits the number of threads that can access the resource at a time. In our application, it’s used for working with files. Example: ```csharp! public class TokenSecureStorageService : ITokenStorageService { private readonly SemaphoreSlim _semaphore; private readonly IFileService _fileService; public TokenSecureStorageService(IFileService fileService) { _fileService = fileService; _semaphore = new SemaphoreSlim(1, 1); // Initialize a semaphore that allows only 1 operation at a time } public async Task ClearTokensAsync() { await _semaphore.WaitAsync(); // Start the semaphore try { await Task.Run(() => { _fileService.Delete(_folderPath, _fileName); }); } finally { _semaphore.Release(); // Release the semaphore } } } ``` ### **ICommand and RelayCommand** `ICommand` is an interface that represents a command, used to bind user actions in the UI, such as button clicks, to methods in the `ViewModel`. It helps separate the UI from the business logic. Most of buttons in our application implement this. `RelayCommand` and `AsyncRelayCommand` is two implementations of `ICommand` provided by `Microsoft Community Toolkit`. Example: ```csharp= // Define Command in ViewModel public partial class AdminZoneViewModel : ObservableRecipient, INavigationAware, IDisposable { private readonly IAdminZoneService _adminZoneService; private readonly INavigationService _navigationService; [ObservableProperty] [NotifyCanExecuteChangedFor("EditCommand")] [NotifyCanExecuteChangedFor("DeleteCommand")] private ZoneDto? selectedZone; public IRelayCommand AddCommand { get; } public IRelayCommand EditCommand { get; } public IAsyncRelayCommand DeleteCommand { get; } public AdminZoneViewModel(IAdminZoneService adminZoneService, INavigationService navigationService) { _adminZoneService = adminZoneService; _navigationService = navigationService; AddCommand = new RelayCommand(NavigateToAddPage); EditCommand = new RelayCommand(NavigateToEditPage, CanEdit); DeleteCommand = new AsyncRelayCommand(DeleteSelectedZoneAsync, CanDelete); } ``` ```xml!= <!-- Bind it in Page.xaml --> <Button Command="{x:Bind ViewModel.AddCommand}" Margin="0,0,8,0"> <Button.Content> <!-- --> </Button.Content> </Button> <Button Command="{x:Bind ViewModel.EditCommand}" Margin="0,0,8,0"> <Button.Content> <!-- --> </Button.Content> </Button> <Button Command="{x:Bind ViewModel.DeleteCommand}" Margin="0,0,8,0"> <Button.Content> <!-- --> </Button.Content> </Button> ``` ### **DelegatingHandler** `DelegatingHandler` is a specialized `HttpMessageHandler` that allows customize the behavior of HTTP requests and responses in a pipeline. In our application, it is used to set request headers and to retry requests if the access token has expired. :::info The diagram below show the `Login Http Pipeline` implemented in the application. ::: ![HttpPipeline](https://hackmd.io/_uploads/H18jIyO-yl.png) Implementation: ```csharp= public class AuthenticationDelegatingHandler : DelegatingHandler { private readonly IAuthService _authService; public AuthenticationDelegatingHandler(IAuthService authService) { _authService = authService; } protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { // Add token to initial request await AddAuthenticationHeader(request); // Send the request var response = await base.SendAsync(request, cancellationToken); if (response.StatusCode == HttpStatusCode.Unauthorized) { // Some logics var newResponse = await base.SendAsync(newRequest, cancellationToken); // Some logics } return response; } } ``` ### **DI Containers** In .NET, `DI Container` is a built-in software framework that automates the process of managing dependencies and object lifetimes in applications. We use `DI Container` to inject most of dependency injection implementation. Example: ```csharp!= // Core Services services.AddSingleton<IAuthService, AuthService>(); services.AddSingleton<ITokenStorageService, TokenSecureStorageService>(); // In old version: // services.AddSingleton<IAuthService, MockAuthService>(); // services.AddSingleton<ITokenStorageService, TokenJsonStorageService>(); // Views and ViewModels services.AddTransient<MainViewModel>(); services.AddTransient<MainPage>(); services.AddTransient<ShellPage>(); services.AddTransient<ShellViewModel>(); ``` ### **JWT** The application uses `JWT` for authentication. When users sign in, a `AccessToken` is generated and sent back to the client through Authorization header to authorize future requests. A `RefreshToken` is also created to allow users to request a new `AccessToken` without needing to log in again. :::info The diagram below illustrates the authentication flow. ::: ![AuthFlow](https://hackmd.io/_uploads/Bkcm61OZyl.png) ### **DPAPI** `DPAPI` is a Windows feature that provides a simple way to securely store sensitive data. Currently, user's login result is stored using this feature. Example: ```csharp! _entropy = Encoding.UTF8.GetBytes("BistroQ"); // This will be a more secret key and store in .env // Encryption private Task<string> EncryptData(string data) { var jsonData = JsonSerializer.Serialize(data); var dataBytes = Encoding.UTF8.GetBytes(jsonData); var encryptedData = ProtectedData.Protect( dataBytes, _entropy, DataProtectionScope.CurrentUser ); return Task.FromResult(Convert.ToBase64String(encryptedData)); } // Decryption private Task<string> DecryptData(string data) { var byteData = Convert.FromBase64String(data); var decryptedData = ProtectedData.Unprotect( byteData, _entropy, DataProtectionScope.CurrentUser ); var jsonData = Encoding.UTF8.GetString(decryptedData); var dataString = JsonSerializer.Deserialize<string>(jsonData); return Task.FromResult(dataString); } ``` ### **INotifyDataErrorInfo** `INotifyDataErrorInfo` is an interface in .NET that enables data entity classes to implement custom validation rules and expose validation results asynchronously. The `ValidatorBase` class implements the interface to provide validation infrastructure that all model forms should inherit from. This enables: - Real-time form validation - Multiple validation rules per field - Asynchronous validation support - WinUI data binding integration Example: ```csharp! public partial class ValidatorBase : INotifyPropertyChanged, INotifyDataErrorInfo { private readonly Dictionary<string, List<string>> _errors = new(); private readonly Dictionary<string, Func<object?, List<(bool IsValid, string Message)>>> _validators = new(); // EventHandler of two Interfaces public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged; public event PropertyChangedEventHandler? PropertyChanged; // Public methods public bool HasErrors => _errors.Any(); public IEnumerable GetErrors(string? propertyName) { /* Implementation */ } public void ValidateProperty(string propertyName, object? value) { /* Implementation */ } protected void AddValidator(string propertyName, Func<object?, List<(bool IsValid, string Message)>> validators) { /* Implementation */ } } ``` ## Github Project Evidence (for managing tasks and issues) ![GitHubProject](https://hackmd.io/_uploads/rkW-yet-Jl.png) #### Tasks The task list and estimated working hours mentioned above. [here](#Features) ## Testing In this milestone, we are mainly test the UI manually. Link to the testcases sheet: [here]() - We are planning to write Unit Test/Automation test in the next milestone. ## Git commit history #### WinUI ![image](https://hackmd.io/_uploads/S1qaRbt-yg.png) ![image](https://hackmd.io/_uploads/B13aAZF-1l.png) #### Backend ![image](https://hackmd.io/_uploads/SyRT0bYZke.png) ## Code Review Evidence ### **Login Page WinUI** ![PR_Login_WinUI-images-0](https://hackmd.io/_uploads/ryT2Q3Lbke.jpg) ![PR_Login_WinUI-images-1](https://hackmd.io/_uploads/S1C3Q2Ub1e.jpg) ![PR_Login_WinUI-images-2](https://hackmd.io/_uploads/SJAhXnUW1g.jpg) ![PR_Login_WinUI-images-3](https://hackmd.io/_uploads/rk0hmnU-yg.jpg) ![PR_Login_WinUI-images-4](https://hackmd.io/_uploads/Hk6hmhUW1e.jpg) ### **Validator WinUI** ![PR_Validator_WinUI-images-0](https://hackmd.io/_uploads/rJ4tnEwZJe.jpg) ![PR_Validator_WinUI-images-1](https://hackmd.io/_uploads/rkNY2EDZ1g.jpg) ![PR_Validator_WinUI-images-2](https://hackmd.io/_uploads/r1NK3NPWJg.jpg) ![PR_Validator_WinUI-images-3](https://hackmd.io/_uploads/ryVt2Ew-Jg.jpg) ### **Zone Admin Page WinUI** ![PR_Zone_Admin_WinUI-images-0](https://hackmd.io/_uploads/H1KyYdPZkl.jpg) ![PR_Zone_Admin_WinUI-images-1](https://hackmd.io/_uploads/HJYkYOvWyx.jpg) ![PR_Zone_Admin_WinUI-images-2](https://hackmd.io/_uploads/HkYkK_wWyg.jpg) ![PR_Zone_Admin_WinUI-images-3](https://hackmd.io/_uploads/S1Y1FOPWkg.jpg) ![PR_Zone_Admin_WinUI-images-4](https://hackmd.io/_uploads/B1KytdDZJl.jpg) ![PR_Zone_Admin_WinUI-images-5](https://hackmd.io/_uploads/BkY1Y_w-1e.jpg) ### **Navigation Pane WinUI** ![PR_Navigation_WinUI-images-0](https://hackmd.io/_uploads/BkGLOx_Z1x.jpg) ![PR_Navigation_WinUI-images-1](https://hackmd.io/_uploads/HyG8deOWyg.jpg) ![PR_Navigation_WinUI-images-2](https://hackmd.io/_uploads/HJfLdeuW1l.jpg) ![PR_Navigation_WinUI-images-3](https://hackmd.io/_uploads/SJGIOldbJx.jpg) ### **Skeleton API** ![PR_Skeleton_API-images-0](https://hackmd.io/_uploads/rJdYVhLbJx.jpg) ![PR_Skeleton_API-images-1](https://hackmd.io/_uploads/Hyvt4n8Zkx.jpg) ### **Authentication/Authorization API** ![PR_Auth_API-images-0](https://hackmd.io/_uploads/rkC6r2LWye.jpg) ![PR_Auth_API-images-1](https://hackmd.io/_uploads/SJ06S38WJx.jpg) ![PR_Auth_API-images-2](https://hackmd.io/_uploads/SJCTSnLbkl.jpg) ### **Category API** ![PR_Category_API-images-0](https://hackmd.io/_uploads/Sk_HPhLb1x.jpg) ![PR_Category_API-images-1](https://hackmd.io/_uploads/Sy_HD3Lb1e.jpg) ![PR_Category_API-images-2](https://hackmd.io/_uploads/r1PrvhL-kx.jpg) ### **Table Zone API** ![PR_Table_Zone_API-images-0](https://hackmd.io/_uploads/BJqav3I-kg.jpg) ![PR_Table_Zone_API-images-1](https://hackmd.io/_uploads/HyPov2LWyx.jpg) ![PR_Table_Zone_API-images-2](https://hackmd.io/_uploads/ryDsDhUb1x.jpg) ![PR_Table_Zone_API-images-3](https://hackmd.io/_uploads/HJDjPh8byl.jpg) ![PR_Table_Zone_API-images-4](https://hackmd.io/_uploads/rkwswhUWJx.jpg) ### **Hotfix Zone Table API** ![PR_Hotfix_Zone_API-images-0](https://hackmd.io/_uploads/ry77YluWJx.jpg) ![PR_Hotfix_Zone_API-images-1](https://hackmd.io/_uploads/rk7mYgu-yg.jpg) ### Add Client Order feature ![Add Client Order feature by Tondeptrai23 · Pull Request #7 · tien4112004_bistro-q-api-1](https://hackmd.io/_uploads/r10JnMFWJx.png) ### Client Order Page ![Client Order Page by Tondeptrai23 · Pull Request #7 · tien4112004_bistro-q-1](https://hackmd.io/_uploads/rkgZ3zF-1e.png)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully