From 0ea8f1866b97762c376170850caca28f97448c7c Mon Sep 17 00:00:00 2001 From: Nyavokevin <42602932+nyavokevin@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:08:08 +0300 Subject: [PATCH] add employee --- .../Organism/CRM/AddEmployeePresentation.vue | 310 +++++++++ .../Employee/EmployeePresentation.vue | 228 +++++++ .../Stock/ProductCategoryPresentation.vue | 175 +++++ .../molecules/Employees/EmployeeTable.vue | 507 +++++++++++++++ .../molecules/Stock/ProductCategoryModal.vue | 473 ++++++++++++++ .../Tables/Stock/ProductCategoryTable | 516 +++++++++++++++ .../Tables/Stock/ProductCategoryTable.vue | 532 +++++++++++++++ .../molecules/form/NewEmployeeForm.vue | 311 +++++++++ .../src/components/templates/CRM/Employee | 23 + .../templates/CRM/EmployeeTemplate.vue | 23 + .../templates/CRM/NewEmployeeTemplate.vue | 21 + .../templates/Employee/EmployeeTemplate.vue | 141 ++++ .../Stock/ProductCategoryTemplate.vue | 23 + .../src/examples/Sidenav/SidenavList.vue | 609 ++++++++++-------- thanasoft-front/src/router/index.js | 15 +- thanasoft-front/src/services/employee.ts | 293 +++++++++ thanasoft-front/src/services/product.ts | 13 +- .../src/services/productCategorie.ts | 287 +++++++++ .../src/services/productCategory.ts | 190 ++++++ thanasoft-front/src/stores/employeeStore.ts | 373 +++++++++++ .../src/stores/productCategoryStore.ts | 510 +++++++++++++++ thanasoft-front/src/stores/productStore.ts | 16 +- .../src/views/pages/CRM/AddEmployee.vue | 59 ++ .../src/views/pages/Employes/Employees.vue | 53 ++ .../src/views/pages/Employes/Employes.vue | 11 - .../Employes/EmployesThanatopracteurs.vue | 45 ++ .../src/views/pages/Stock/AddProduct.vue | 5 +- .../views/pages/Stock/AddProductCategory.vue | 72 +++ .../views/pages/Stock/EditProductCategory.vue | 98 +++ .../views/pages/Stock/ProductCategories.vue | 42 ++ .../pages/Stock/ProductCategoryDetails.vue | 95 +++ 31 files changed, 5764 insertions(+), 305 deletions(-) create mode 100644 thanasoft-front/src/components/Organism/CRM/AddEmployeePresentation.vue create mode 100644 thanasoft-front/src/components/Organism/Employee/EmployeePresentation.vue create mode 100644 thanasoft-front/src/components/Organism/Stock/ProductCategoryPresentation.vue create mode 100644 thanasoft-front/src/components/molecules/Employees/EmployeeTable.vue create mode 100644 thanasoft-front/src/components/molecules/Stock/ProductCategoryModal.vue create mode 100644 thanasoft-front/src/components/molecules/Tables/Stock/ProductCategoryTable create mode 100644 thanasoft-front/src/components/molecules/Tables/Stock/ProductCategoryTable.vue create mode 100644 thanasoft-front/src/components/molecules/form/NewEmployeeForm.vue create mode 100644 thanasoft-front/src/components/templates/CRM/Employee create mode 100644 thanasoft-front/src/components/templates/CRM/EmployeeTemplate.vue create mode 100644 thanasoft-front/src/components/templates/CRM/NewEmployeeTemplate.vue create mode 100644 thanasoft-front/src/components/templates/Employee/EmployeeTemplate.vue create mode 100644 thanasoft-front/src/components/templates/Stock/ProductCategoryTemplate.vue create mode 100644 thanasoft-front/src/services/employee.ts create mode 100644 thanasoft-front/src/services/productCategorie.ts create mode 100644 thanasoft-front/src/services/productCategory.ts create mode 100644 thanasoft-front/src/stores/employeeStore.ts create mode 100644 thanasoft-front/src/stores/productCategoryStore.ts create mode 100644 thanasoft-front/src/views/pages/CRM/AddEmployee.vue create mode 100644 thanasoft-front/src/views/pages/Employes/Employees.vue delete mode 100644 thanasoft-front/src/views/pages/Employes/Employes.vue create mode 100644 thanasoft-front/src/views/pages/Employes/EmployesThanatopracteurs.vue create mode 100644 thanasoft-front/src/views/pages/Stock/AddProductCategory.vue create mode 100644 thanasoft-front/src/views/pages/Stock/EditProductCategory.vue create mode 100644 thanasoft-front/src/views/pages/Stock/ProductCategories.vue create mode 100644 thanasoft-front/src/views/pages/Stock/ProductCategoryDetails.vue diff --git a/thanasoft-front/src/components/Organism/CRM/AddEmployeePresentation.vue b/thanasoft-front/src/components/Organism/CRM/AddEmployeePresentation.vue new file mode 100644 index 0000000..3d159a6 --- /dev/null +++ b/thanasoft-front/src/components/Organism/CRM/AddEmployeePresentation.vue @@ -0,0 +1,310 @@ + + + + + diff --git a/thanasoft-front/src/components/Organism/Employee/EmployeePresentation.vue b/thanasoft-front/src/components/Organism/Employee/EmployeePresentation.vue new file mode 100644 index 0000000..b1c50eb --- /dev/null +++ b/thanasoft-front/src/components/Organism/Employee/EmployeePresentation.vue @@ -0,0 +1,228 @@ + + + + diff --git a/thanasoft-front/src/components/Organism/Stock/ProductCategoryPresentation.vue b/thanasoft-front/src/components/Organism/Stock/ProductCategoryPresentation.vue new file mode 100644 index 0000000..e211444 --- /dev/null +++ b/thanasoft-front/src/components/Organism/Stock/ProductCategoryPresentation.vue @@ -0,0 +1,175 @@ + + diff --git a/thanasoft-front/src/components/molecules/Employees/EmployeeTable.vue b/thanasoft-front/src/components/molecules/Employees/EmployeeTable.vue new file mode 100644 index 0000000..5a56c33 --- /dev/null +++ b/thanasoft-front/src/components/molecules/Employees/EmployeeTable.vue @@ -0,0 +1,507 @@ + + + + + diff --git a/thanasoft-front/src/components/molecules/Stock/ProductCategoryModal.vue b/thanasoft-front/src/components/molecules/Stock/ProductCategoryModal.vue new file mode 100644 index 0000000..ef8f138 --- /dev/null +++ b/thanasoft-front/src/components/molecules/Stock/ProductCategoryModal.vue @@ -0,0 +1,473 @@ + + + + + diff --git a/thanasoft-front/src/components/molecules/Tables/Stock/ProductCategoryTable b/thanasoft-front/src/components/molecules/Tables/Stock/ProductCategoryTable new file mode 100644 index 0000000..9942bf5 --- /dev/null +++ b/thanasoft-front/src/components/molecules/Tables/Stock/ProductCategoryTable @@ -0,0 +1,516 @@ + + + + + diff --git a/thanasoft-front/src/components/molecules/Tables/Stock/ProductCategoryTable.vue b/thanasoft-front/src/components/molecules/Tables/Stock/ProductCategoryTable.vue new file mode 100644 index 0000000..688ecc3 --- /dev/null +++ b/thanasoft-front/src/components/molecules/Tables/Stock/ProductCategoryTable.vue @@ -0,0 +1,532 @@ + + + + + diff --git a/thanasoft-front/src/components/molecules/form/NewEmployeeForm.vue b/thanasoft-front/src/components/molecules/form/NewEmployeeForm.vue new file mode 100644 index 0000000..ceeb331 --- /dev/null +++ b/thanasoft-front/src/components/molecules/form/NewEmployeeForm.vue @@ -0,0 +1,311 @@ + + + + + diff --git a/thanasoft-front/src/components/templates/CRM/Employee b/thanasoft-front/src/components/templates/CRM/Employee new file mode 100644 index 0000000..fe2a9d3 --- /dev/null +++ b/thanasoft-front/src/components/templates/CRM/Employee @@ -0,0 +1,23 @@ + + diff --git a/thanasoft-front/src/components/templates/CRM/EmployeeTemplate.vue b/thanasoft-front/src/components/templates/CRM/EmployeeTemplate.vue new file mode 100644 index 0000000..fe2a9d3 --- /dev/null +++ b/thanasoft-front/src/components/templates/CRM/EmployeeTemplate.vue @@ -0,0 +1,23 @@ + + diff --git a/thanasoft-front/src/components/templates/CRM/NewEmployeeTemplate.vue b/thanasoft-front/src/components/templates/CRM/NewEmployeeTemplate.vue new file mode 100644 index 0000000..b5348a6 --- /dev/null +++ b/thanasoft-front/src/components/templates/CRM/NewEmployeeTemplate.vue @@ -0,0 +1,21 @@ + diff --git a/thanasoft-front/src/components/templates/Employee/EmployeeTemplate.vue b/thanasoft-front/src/components/templates/Employee/EmployeeTemplate.vue new file mode 100644 index 0000000..27a9551 --- /dev/null +++ b/thanasoft-front/src/components/templates/Employee/EmployeeTemplate.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/thanasoft-front/src/components/templates/Stock/ProductCategoryTemplate.vue b/thanasoft-front/src/components/templates/Stock/ProductCategoryTemplate.vue new file mode 100644 index 0000000..9d2395e --- /dev/null +++ b/thanasoft-front/src/components/templates/Stock/ProductCategoryTemplate.vue @@ -0,0 +1,23 @@ + + diff --git a/thanasoft-front/src/examples/Sidenav/SidenavList.vue b/thanasoft-front/src/examples/Sidenav/SidenavList.vue index d5d043c..7539f90 100644 --- a/thanasoft-front/src/examples/Sidenav/SidenavList.vue +++ b/thanasoft-front/src/examples/Sidenav/SidenavList.vue @@ -4,298 +4,54 @@ class="w-auto h-auto collapse navbar-collapse max-height-vh-100 h-100" > + diff --git a/thanasoft-front/src/router/index.js b/thanasoft-front/src/router/index.js index 891d8bb..a72549f 100644 --- a/thanasoft-front/src/router/index.js +++ b/thanasoft-front/src/router/index.js @@ -522,7 +522,12 @@ const routes = [ { path: "/employes", name: "Gestion employes", - component: () => import("@/views/pages/Employes/Employes.vue"), + component: () => import("@/views/pages/Employes/Employees.vue"), + }, + { + path: "/employes/new", + name: "Creation employé", + component: () => import("@/views/pages/CRM/AddEmployee.vue"), }, { path: "/employes/ndf", @@ -531,7 +536,7 @@ const routes = [ }, { path: "/employes/vehicules", - name: "Vehicules", + name: "Véhicules", component: () => import("@/views/pages/Employes/Vehicules.vue"), }, { @@ -539,6 +544,12 @@ const routes = [ name: "Absences", component: () => import("@/views/pages/Employes/Absences.vue"), }, + { + path: "/employes/thanatopracteurs", + name: "Employés Thanatopracteurs", + component: () => + import("@/views/pages/Employes/EmployesThanatopracteurs.vue"), + }, // Paramétrage { path: "/parametrage/droits", diff --git a/thanasoft-front/src/services/employee.ts b/thanasoft-front/src/services/employee.ts new file mode 100644 index 0000000..a031dab --- /dev/null +++ b/thanasoft-front/src/services/employee.ts @@ -0,0 +1,293 @@ +import { request } from "./http"; + +export interface EmployeeAddress { + line1: string | null; + line2: string | null; + postal_code: string | null; + city: string | null; + country_code: string | null; + full_address?: string; +} + +export interface Employee { + id: number; + first_name: string; + last_name: string; + full_name: string; + email: string | null; + phone: string | null; + hire_date: string; + job_title: string | null; // Changed from position to job_title + salary?: number | null; // Optional since API doesn't always return it + active: boolean; // Changed from is_active to active + created_at: string; + updated_at: string; + // Relations + thanatopractitioner?: { + id: number; + license_number: string; + authorization_number: string; + authorization_valid_until: string; + created_at: string; + updated_at: string; + } | null; +} + +export interface EmployeeListResponse { + data: Employee[]; + pagination: { + current_page: number; + per_page: number; + total: number; + last_page: number; + from: number; + to: number; + }; + message: string; +} + +// For nested response structure +export interface NestedEmployeeListResponse { + data: { + data: Employee[]; + pagination: { + current_page: number; + per_page: number; + total: number; + last_page: number; + from: number; + to: number; + }; + message: string; + }; +} + +export interface EmployeeResponse { + data: Employee; + message: string; +} + +export interface CreateEmployeePayload { + first_name: string; + last_name: string; + email?: string | null; + phone?: string | null; + hire_date: string; + job_title?: string | null; // Changed from position to job_title + salary?: number | null; + active?: boolean; // Changed from is_active to active +} + +export interface UpdateEmployeePayload extends Partial { + id: number; +} + +export const EmployeeService = { + /** + * Get all employees with pagination and filtering + */ + async getAllEmployees(params?: { + page?: number; + per_page?: number; + search?: string; + active?: boolean; + sort_by?: string; + sort_direction?: string; + }): Promise { + const response = await request({ + url: "/api/employees", + method: "get", + params, + }); + + return response; + }, + + /** + * Get a specific employee by ID + */ + async getEmployee(id: number): Promise { + const response = await request({ + url: `/api/employees/${id}`, + method: "get", + }); + + return response; + }, + + /** + * Create a new employee + */ + async createEmployee( + payload: CreateEmployeePayload + ): Promise { + const formattedPayload = this.transformEmployeePayload(payload); + + const response = await request({ + url: "/api/employees", + method: "post", + data: formattedPayload, + }); + + return response; + }, + + /** + * Update an existing employee + */ + async updateEmployee( + payload: UpdateEmployeePayload + ): Promise { + const { id, ...updateData } = payload; + const formattedPayload = this.transformEmployeePayload(updateData); + + const response = await request({ + url: `/api/employees/${id}`, + method: "put", + data: formattedPayload, + }); + + return response; + }, + + /** + * Delete an employee + */ + async deleteEmployee( + id: number + ): Promise<{ success: boolean; message: string }> { + const response = await request<{ success: boolean; message: string }>({ + url: `/api/employees/${id}`, + method: "delete", + }); + + return response; + }, + + /** + * Get active employees only + */ + async getActiveEmployees(params?: { + page?: number; + per_page?: number; + }): Promise { + const response = await request({ + url: "/api/employees", + method: "get", + params: { + active: true, + ...params, + }, + }); + + return response; + }, + + /** + * Get employees with thanatopractitioner data + */ + async getEmployeesWithThanatopractitioner(): Promise<{ + data: Employee[]; + }> { + const response = await request<{ + data: Employee[]; + }>({ + url: "/api/employees/with-thanatopractitioner", + method: "get", + }); + + return response; + }, + + /** + * Get employee statistics + */ + async getStatistics(): Promise<{ + data: { + total_employees: number; + active_employees: number; + inactive_employees: number; + total_salary: number; + average_salary: number; + }; + }> { + const response = await request<{ + data: { + total_employees: number; + active_employees: number; + inactive_employees: number; + total_salary: number; + average_salary: number; + }; + }>({ + url: "/api/employees/statistics", + method: "get", + }); + + return response; + }, + + /** + * Transform employee payload to match Laravel form request structure + */ + transformEmployeePayload(payload: Partial): any { + const transformed: any = { ...payload }; + + // Ensure boolean values are properly formatted + if (typeof transformed.active === "boolean") { + transformed.active = transformed.active ? 1 : 0; + } + + // Format salary if present + if (transformed.salary !== undefined && transformed.salary !== null) { + transformed.salary = parseFloat(transformed.salary.toString()); + } + + // Remove undefined values to avoid sending them + Object.keys(transformed).forEach((key) => { + if (transformed[key] === undefined) { + delete transformed[key]; + } + }); + + return transformed; + }, + + /** + * Search employees by name, email, or other criteria + */ + async searchEmployees(query: string): Promise { + const response = await request<{ + data: { + data: Employee[]; + }; + }>({ + url: "/api/employees", + method: "get", + params: { + search: query, + }, + }); + return response.data.data; + }, + + /** + * Toggle employee active status + */ + async toggleEmployeeStatus( + id: number, + isActive: boolean + ): Promise { + const response = await request({ + url: `/api/employees/${id}`, + method: "put", + data: { + active: isActive, + }, + }); + + return response; + }, +}; + +export default EmployeeService; diff --git a/thanasoft-front/src/services/product.ts b/thanasoft-front/src/services/product.ts index 29b4374..75c18ad 100644 --- a/thanasoft-front/src/services/product.ts +++ b/thanasoft-front/src/services/product.ts @@ -5,7 +5,12 @@ export interface Product { id: number; nom: string; reference: string; - categorie: string; + categorie_id: number; + category: { + id: number; + name: string; + code: string; + }; fabricant: string | null; stock_actuel: number; stock_minimum: number; @@ -41,7 +46,7 @@ export interface Product { export interface ProductFormData { nom: string; reference: string; - categorie: string; + categorie_id: number; fabricant?: string | null; stock_actuel: number; stock_minimum: number; @@ -88,7 +93,7 @@ class ProductService { page?: number; per_page?: number; search?: string; - categorie?: string; + categorie_id?: number; fournisseur_id?: number; low_stock?: boolean; expiring_soon?: boolean; @@ -185,7 +190,7 @@ class ProductService { // Get products by category async getProductsByCategory( - category: string, + category: number, params: { per_page?: number } = {} ): Promise { const response = await request({ diff --git a/thanasoft-front/src/services/productCategorie.ts b/thanasoft-front/src/services/productCategorie.ts new file mode 100644 index 0000000..fec6ef2 --- /dev/null +++ b/thanasoft-front/src/services/productCategorie.ts @@ -0,0 +1,287 @@ +import { request } from "./http"; + +// Type definitions +export interface ProductCategorie { + id: number; + nom: string; + reference: string; + categorie: string; + fabricant: string | null; + stock_actuel: number; + stock_minimum: number; + unite: string; + prix_unitaire: number; + date_expiration: string | null; + numero_lot: string | null; + conditionnement_nom: string | null; + conditionnement_quantite: number | null; + conditionnement_unite: string | null; + photo_url: string | null; + fiche_technique_url: string | null; + fournisseur_id: number | null; + is_low_stock: boolean; + created_at: string; + updated_at: string; + fournisseur?: { + id: number; + name: string; + email: string; + }; + conditionnement?: { + nom: string | null; + quantite: number | null; + unite: string | null; + }; + media?: { + photo_url: string | null; + fiche_technique_url: string | null; + }; +} + +export interface ProductCategorieFormData { + nom: string; + reference: string; + categorie: string; + fabricant?: string | null; + stock_actuel: number; + stock_minimum: number; + unite: string; + prix_unitaire: number; + date_expiration?: string | null; + numero_lot?: string | null; + conditionnement_nom?: string | null; + conditionnement_quantite?: number | null; + conditionnement_unite?: string | null; + photo_url?: string | null; + fiche_technique_url?: string | null; + fournisseur_id?: number | null; +} + +export interface ProductCategorieListResponse { + data: ProductCategorie[]; + pagination: { + current_page: number; + from: number; + last_page: number; + per_page: number; + to: number; + total: number; + }; + summary: { + total_productCategories: number; + low_stock_productCategories: number; + total_value: number; + }; +} + +export interface ProductCategorieStatistics { + total_productCategories: number; + low_stock_productCategories: number; + expiring_productCategories: number; + total_value: number; +} + +class ProductCategorieService { + // Get all productCategories with pagination and filters + async getAllProductCategories( + params: { + page?: number; + per_page?: number; + search?: string; + categorie?: string; + fournisseur_id?: number; + low_stock?: boolean; + expiring_soon?: boolean; + sort_by?: string; + sort_direction?: "asc" | "desc"; + } = {} + ): Promise { + const response = await request({ + url: "/api/productCategories", + method: "get", + params, + }); + return response; + } + + // Get a single productCategorie by ID + async getProductCategorie(id: number): Promise<{ data: ProductCategorie }> { + const response = await request<{ data: ProductCategorie }>({ + url: `/api/productCategories/${id}`, + method: "get", + }); + return response; + } + + // Create a new productCategorie + async createProductCategorie( + productCategorieData: ProductCategorieFormData | FormData + ): Promise<{ data: ProductCategorie }> { + let formattedPayload: any; + + // Check if data is FormData (for file uploads) + if (productCategorieData instanceof FormData) { + formattedPayload = productCategorieData; + } else { + formattedPayload = this.transformProductCategoriePayload( + productCategorieData + ); + } + + const response = await request<{ data: ProductCategorie }>({ + url: "/api/productCategories", + method: "post", + data: formattedPayload, + }); + return response; + } + + // Update an existing productCategorie + async updateProductCategorie( + id: number, + productCategorieData: ProductCategorieFormData | FormData + ): Promise<{ data: ProductCategorie }> { + let formattedPayload: any; + + // Check if data is FormData (for file uploads) + if (productCategorieData instanceof FormData) { + formattedPayload = productCategorieData; + } else { + formattedPayload = this.transformProductCategoriePayload( + productCategorieData + ); + } + + const response = await request<{ data: ProductCategorie }>({ + url: `/api/productCategories/${id}`, + method: "put", + data: formattedPayload, + }); + return response; + } + + // Delete a productCategorie + async deleteProductCategorie(id: number): Promise<{ message: string }> { + const response = await request<{ message: string }>({ + url: `/api/productCategories/${id}`, + method: "delete", + }); + return response; + } + + // Search productCategories by name + async searchProductCategories( + searchTerm: string, + exactMatch: boolean = false + ): Promise<{ data: ProductCategorie[]; count: number; message: string }> { + const response = await request<{ + data: ProductCategorie[]; + count: number; + message: string; + }>({ + url: "/api/productCategories/searchBy", + method: "get", + params: { + name: searchTerm, + exact: exactMatch, + }, + }); + return response; + } + + // Get productCategories with low stock + async getLowStockProductCategories( + params: { per_page?: number } = {} + ): Promise { + const response = await request({ + url: "/api/productCategories/low-stock", + method: "get", + params, + }); + return response; + } + + // Get productCategories by category + async getProductCategoriesByCategory( + category: string, + params: { per_page?: number } = {} + ): Promise { + const response = await request({ + url: "/api/productCategories/by-category", + method: "get", + params: { + category, + ...params, + }, + }); + return response; + } + + // Get productCategorie statistics + async getProductCategorieStatistics(): Promise<{ + data: ProductCategorieStatistics; + message: string; + }> { + const response = await request<{ + data: ProductCategorieStatistics; + message: string; + }>({ + url: "/api/productCategories/statistics", + method: "get", + }); + return response; + } + + // Update stock quantity for a productCategorie + async updateStock( + productCategorieId: number, + newStock: number + ): Promise<{ data: ProductCategorie; message: string }> { + const response = await request<{ data: ProductCategorie; message: string }>( + { + url: `/api/productCategories/${productCategorieId}/stock`, + method: "patch", + data: { + stock_actuel: newStock, + }, + } + ); + return response; + } + + /** + * Transform productCategorie payload to match Laravel form request structure + */ + transformProductCategoriePayload( + payload: Partial + ): any { + const transformed: any = { ...payload }; + + // Remove undefined values to avoid sending them + Object.keys(transformed).forEach((key) => { + if (transformed[key] === undefined) { + delete transformed[key]; + } + }); + + return transformed; + } + + // Utility methods for productCategorie expiration checks + static isExpired(productCategorie: ProductCategorie): boolean { + if (!productCategorie.date_expiration) return false; + return new Date(productCategorie.date_expiration) < new Date(); + } + + static isExpiringSoon( + productCategorie: ProductCategorie, + days: number = 30 + ): boolean { + if (!productCategorie.date_expiration) return false; + const expirationDate = new Date(productCategorie.date_expiration); + const soonDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000); + return expirationDate <= soonDate && expirationDate >= new Date(); + } +} + +export default ProductCategorieService; diff --git a/thanasoft-front/src/services/productCategory.ts b/thanasoft-front/src/services/productCategory.ts new file mode 100644 index 0000000..ead663f --- /dev/null +++ b/thanasoft-front/src/services/productCategory.ts @@ -0,0 +1,190 @@ +import { request } from "./http"; + +// Type definitions +export interface ProductCategory { + id: number; + parent_id: number | null; + code: string; + name: string; + description: string | null; + active: boolean; + path: string; + has_children: boolean; + has_products: boolean; + children_count: number; + products_count: number; + parent?: ProductCategory; + children?: ProductCategory[]; + created_at: string; + updated_at: string; +} + +export interface ProductCategoryFormData { + parent_id?: number | null; + code: string; + name: string; + description?: string | null; + active?: boolean; +} + +export interface ProductCategoryListResponse { + data: ProductCategory[]; + pagination: { + current_page: number; + from: number; + last_page: number; + per_page: number; + to: number; + total: number; + }; +} + +export interface ProductCategoryStatistics { + total_categories: number; + active_categories: number; + inactive_categories: number; + categories_with_children: number; + root_categories: number; +} + +class ProductCategoryService { + // Get all product categories with pagination and filters + async getAllProductCategories( + params: { + page?: number; + per_page?: number; + search?: string; + active?: boolean; + parent_id?: number; + sort_by?: string; + sort_direction?: "asc" | "desc"; + } = {} + ): Promise { + const response = await request({ + url: "/api/product-categories", + method: "get", + params, + }); + return response; + } + + // Get a single product category by ID + async getProductCategory(id: number): Promise<{ data: ProductCategory }> { + const response = await request<{ data: ProductCategory }>({ + url: `/api/product-categories/${id}`, + method: "get", + }); + return response; + } + + // Create a new product category + async createProductCategory( + categoryData: ProductCategoryFormData + ): Promise<{ data: ProductCategory }> { + const response = await request<{ data: ProductCategory }>({ + url: "/api/product-categories", + method: "post", + data: categoryData, + }); + return response; + } + + // Update an existing product category + async updateProductCategory( + id: number, + categoryData: ProductCategoryFormData + ): Promise<{ data: ProductCategory }> { + const response = await request<{ data: ProductCategory }>({ + url: `/api/product-categories/${id}`, + method: "put", + data: categoryData, + }); + return response; + } + + // Delete a product category + async deleteProductCategory(id: number): Promise<{ message: string }> { + const response = await request<{ message: string }>({ + url: `/api/product-categories/${id}`, + method: "delete", + }); + return response; + } + + // Search product categories + async searchProductCategories( + term: string, + params: { per_page?: number } = {} + ): Promise { + const response = await request({ + url: "/api/product-categories/search", + method: "get", + params: { + term, + ...params, + }, + }); + return response; + } + + // Get active product categories only + async getActiveProductCategories(): Promise<{ data: ProductCategory[] }> { + const response = await request<{ data: ProductCategory[] }>({ + url: "/api/product-categories/active", + method: "get", + }); + return response; + } + + // Get root product categories (no parent) + async getRootProductCategories(): Promise<{ data: ProductCategory[] }> { + const response = await request<{ data: ProductCategory[] }>({ + url: "/api/product-categories/roots", + method: "get", + }); + return response; + } + + // Get hierarchical structure + async getHierarchicalProductCategories(): Promise<{ + data: ProductCategory[]; + }> { + const response = await request<{ data: ProductCategory[] }>({ + url: "/api/product-categories/hierarchical", + method: "get", + }); + return response; + } + + // Get product category statistics + async getProductCategoryStatistics(): Promise<{ + data: ProductCategoryStatistics; + message: string; + }> { + const response = await request<{ + data: ProductCategoryStatistics; + message: string; + }>({ + url: "/api/product-categories/statistics", + method: "get", + }); + return response; + } + + // Toggle category active status + async toggleProductCategoryStatus( + id: number, + active: boolean + ): Promise<{ data: ProductCategory; message: string }> { + const response = await request<{ data: ProductCategory; message: string }>({ + url: `/api/product-categories/${id}/toggle-active`, + method: "patch", + data: { + active, + }, + }); + return response; + } +} + +export default ProductCategoryService; diff --git a/thanasoft-front/src/stores/employeeStore.ts b/thanasoft-front/src/stores/employeeStore.ts new file mode 100644 index 0000000..44280f3 --- /dev/null +++ b/thanasoft-front/src/stores/employeeStore.ts @@ -0,0 +1,373 @@ +import { defineStore } from "pinia"; +import { ref, computed } from "vue"; +import EmployeeService from "@/services/employee"; + +import type { + Employee, + CreateEmployeePayload, + UpdateEmployeePayload, + EmployeeListResponse, + NestedEmployeeListResponse, +} from "@/services/employee"; + +export const useEmployeeStore = defineStore("employee", () => { + // State + const employees = ref([]); + const currentEmployee = ref(null); + const loading = ref(false); + const error = ref(null); + const searchResults = ref([]); + + // Pagination state + const pagination = ref({ + current_page: 1, + last_page: 1, + per_page: 10, + total: 0, + from: 0, + to: 0, + }); + + // Getters + const allEmployees = computed(() => employees.value); + const activeEmployees = computed(() => + employees.value.filter((employee: Employee) => employee.active) + ); + const inactiveEmployees = computed(() => + employees.value.filter((employee: Employee) => !employee.active) + ); + const isLoading = computed(() => loading.value); + const hasError = computed(() => error.value !== null); + const getError = computed(() => error.value); + const getEmployeeById = computed(() => (id: number) => + employees.value.find((employee: Employee) => employee.id === id) + ); + const getPagination = computed(() => pagination.value); + + // Actions + const setLoading = (isLoading: boolean) => { + loading.value = isLoading; + }; + + const setError = (err: string | null) => { + error.value = err; + }; + + const clearError = () => { + error.value = null; + }; + + const setEmployees = (newEmployees: Employee[]) => { + employees.value = newEmployees; + }; + + const setCurrentEmployee = (employee: Employee | null) => { + currentEmployee.value = employee; + }; + + const setSearchEmployee = (searchEmployee: Employee[]) => { + searchResults.value = searchEmployee; + }; + + const setPagination = (meta: any) => { + if (meta) { + pagination.value = { + current_page: meta.current_page || 1, + last_page: meta.last_page || 1, + per_page: meta.per_page || 10, + total: meta.total || 0, + from: meta.from || 0, + to: meta.to || 0, + }; + } + }; + + /** + * Fetch all employees with optional pagination and filters + */ + const fetchEmployees = async (params?: { + page?: number; + per_page?: number; + search?: string; + active?: boolean; + sort_by?: string; + sort_direction?: string; + }) => { + setLoading(true); + setError(null); + + try { + const response = await EmployeeService.getAllEmployees(params); + setEmployees(response.data.data); + setPagination(response.data.pagination); + return response; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to fetch employees"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Fetch a single employee by ID + */ + const fetchEmployee = async (id: number) => { + setLoading(true); + setError(null); + + try { + const response = await EmployeeService.getEmployee(id); + setCurrentEmployee(response.data); + return response.data; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to fetch employee"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Create a new employee + */ + const createEmployee = async (payload: CreateEmployeePayload) => { + setLoading(true); + setError(null); + + try { + const response = await EmployeeService.createEmployee(payload); + // Add the new employee to the list + employees.value.push(response.data); + setCurrentEmployee(response.data); + return response.data; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to create employee"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Update an existing employee + */ + const updateEmployee = async (payload: UpdateEmployeePayload) => { + setLoading(true); + setError(null); + + try { + console.log(payload); + const response = await EmployeeService.updateEmployee(payload); + const updatedEmployee = response.data; + + // Update in the employees list + const index = employees.value.findIndex( + (employee: Employee) => employee.id === updatedEmployee.id + ); + if (index !== -1) { + employees.value[index] = updatedEmployee; + } + + // Update current employee if it's the one being edited + if ( + currentEmployee.value && + currentEmployee.value.id === updatedEmployee.id + ) { + setCurrentEmployee(updatedEmployee); + } + + return updatedEmployee; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to update employee"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Delete an employee + */ + const deleteEmployee = async (id: number) => { + setLoading(true); + setError(null); + + try { + const response = await EmployeeService.deleteEmployee(id); + + // Remove from the employees list + employees.value = employees.value.filter( + (employee: Employee) => employee.id !== id + ); + + // Clear current employee if it's the one being deleted + if (currentEmployee.value && currentEmployee.value.id === id) { + setCurrentEmployee(null); + } + + return response; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to delete employee"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Search employees + */ + const searchEmployees = async (query: string) => { + setLoading(true); + error.value = null; + + try { + const results = await EmployeeService.searchEmployees(query); + setSearchEmployee(results); + return results; + } catch (err) { + error.value = "Erreur lors de la recherche des employés"; + console.error("Error searching employees:", err); + setSearchEmployee([]); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Toggle employee active status + */ + const toggleEmployeeStatus = async (id: number, isActive: boolean) => { + setLoading(true); + setError(null); + + try { + const response = await EmployeeService.toggleEmployeeStatus(id, isActive); + const updatedEmployee = response.data; + + // Update in the employees list + const index = employees.value.findIndex( + (employee: Employee) => employee.id === id + ); + if (index !== -1) { + employees.value[index] = updatedEmployee; + } + + // Update current employee if it's the one being toggled + if (currentEmployee.value && currentEmployee.value.id === id) { + setCurrentEmployee(updatedEmployee); + } + + return updatedEmployee; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to toggle employee status"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Get employee statistics + */ + const fetchStatistics = async () => { + setLoading(true); + setError(null); + + try { + const response = await EmployeeService.getStatistics(); + return response.data; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to fetch statistics"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Clear current employee + */ + const clearCurrentEmployee = () => { + setCurrentEmployee(null); + }; + + /** + * Clear all state + */ + const clearStore = () => { + employees.value = []; + currentEmployee.value = null; + error.value = null; + pagination.value = { + current_page: 1, + last_page: 1, + per_page: 10, + total: 0, + from: 0, + to: 0, + }; + }; + + return { + // State + employees, + currentEmployee, + loading, + error, + searchResults, + + // Getters + allEmployees, + activeEmployees, + inactiveEmployees, + isLoading, + hasError, + getError, + getEmployeeById, + getPagination, + + // Actions + fetchEmployees, + fetchEmployee, + createEmployee, + updateEmployee, + deleteEmployee, + searchEmployees, + toggleEmployeeStatus, + fetchStatistics, + clearCurrentEmployee, + clearStore, + clearError, + }; +}); diff --git a/thanasoft-front/src/stores/productCategoryStore.ts b/thanasoft-front/src/stores/productCategoryStore.ts new file mode 100644 index 0000000..22e6261 --- /dev/null +++ b/thanasoft-front/src/stores/productCategoryStore.ts @@ -0,0 +1,510 @@ +import { defineStore } from "pinia"; +import { ref, computed } from "vue"; +import ProductCategoryService from "@/services/productCategory"; + +import type { + ProductCategory, + ProductCategoryFormData, + ProductCategoryListResponse, +} from "@/services/productCategory"; + +// Create an instance of productCategoryService +const productCategoryService = new ProductCategoryService(); + +export const useProductCategoryStore = defineStore("productCategory", () => { + // State + const productCategories = ref([]); + const currentProductCategory = ref(null); + const loading = ref(false); + const error = ref(null); + + // Pagination state + const pagination = ref({ + current_page: 1, + last_page: 1, + per_page: 10, + total: 0, + }); + + // Statistics state + const statistics = ref({ + total_categories: 0, + active_categories: 0, + inactive_categories: 0, + categories_with_children: 0, + root_categories: 0, + }); + + // Getters + const allProductCategories = computed(() => productCategories.value); + + const activeProductCategories = computed(() => + productCategories.value.filter((category) => category.active) + ); + + const inactiveProductCategories = computed(() => + productCategories.value.filter((category) => !category.active) + ); + + const rootProductCategories = computed(() => + productCategories.value.filter((category) => !category.parent_id) + ); + + const parentProductCategories = computed(() => + productCategories.value.filter((category) => category.has_children) + ); + + const isLoading = computed(() => loading.value); + const hasError = computed(() => error.value !== null); + const getError = computed(() => error.value); + + const getProductCategoryById = computed(() => (id: number) => + productCategories.value.find((category) => category.id === id) + ); + + const getPagination = computed(() => pagination.value); + const getStatistics = computed(() => statistics.value); + + // Actions + const setLoading = (isLoading: boolean) => { + loading.value = isLoading; + }; + + const setError = (err: string | null) => { + error.value = err; + }; + + const clearError = () => { + error.value = null; + }; + + const setProductCategories = (newProductCategories: ProductCategory[]) => { + productCategories.value = newProductCategories; + }; + + const setCurrentProductCategory = (category: ProductCategory | null) => { + currentProductCategory.value = category; + }; + + const setPagination = (meta: any) => { + if (meta) { + pagination.value = { + current_page: meta.current_page || 1, + last_page: meta.last_page || 1, + per_page: meta.per_page || 10, + total: meta.total || 0, + }; + } + }; + + const setStatistics = (stats: any) => { + if (stats) { + statistics.value = { + total_categories: stats.total_categories || 0, + active_categories: stats.active_categories || 0, + inactive_categories: stats.inactive_categories || 0, + categories_with_children: stats.categories_with_children || 0, + root_categories: stats.root_categories || 0, + }; + } + }; + + /** + * Récupérer toutes les catégories de produits avec pagination et filtres optionnels + */ + const fetchProductCategories = async (params?: { + page?: number; + per_page?: number; + search?: string; + active?: boolean; + parent_id?: number; + sort_by?: string; + sort_direction?: "asc" | "desc"; + }) => { + setLoading(true); + setError(null); + + try { + const response = await productCategoryService.getAllProductCategories( + params + ); + setProductCategories(response.data); + if (response.pagination) { + setPagination(response.pagination); + } + return response; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec du chargement des catégories de produits"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Récupérer une seule catégorie de produit par ID + */ + const fetchProductCategory = async (id: number) => { + setLoading(true); + setError(null); + + try { + const response = await productCategoryService.getProductCategory(id); + setCurrentProductCategory(response.data); + return response.data; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec du chargement de la catégorie de produit"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Créer une nouvelle catégorie de produit + */ + const createProductCategory = async (payload: ProductCategoryFormData) => { + setLoading(true); + setError(null); + + try { + const response = await productCategoryService.createProductCategory( + payload + ); + // Ajouter la nouvelle catégorie à la liste + productCategories.value.push(response.data); + setCurrentProductCategory(response.data); + return response.data; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec de la création de la catégorie de produit"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Mettre à jour une catégorie de produit existante + */ + const updateProductCategory = async ( + payload: ProductCategoryFormData & { id: number } + ) => { + setLoading(true); + setError(null); + + try { + const response = await productCategoryService.updateProductCategory( + payload.id, + payload + ); + const updatedCategory = response.data; + + // Mettre à jour dans la liste des catégories + const index = productCategories.value.findIndex( + (category) => category.id === updatedCategory.id + ); + if (index !== -1) { + productCategories.value[index] = updatedCategory; + } + + // Mettre à jour la catégorie actuelle si c'est celle en cours d'édition + if ( + currentProductCategory.value && + currentProductCategory.value.id === updatedCategory.id + ) { + setCurrentProductCategory(updatedCategory); + } + + return updatedCategory; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec de la mise à jour de la catégorie de produit"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Supprimer une catégorie de produit + */ + const deleteProductCategory = async (id: number) => { + setLoading(true); + setError(null); + + try { + const response = await productCategoryService.deleteProductCategory(id); + + // Retirer de la liste des catégories + productCategories.value = productCategories.value.filter( + (category) => category.id !== id + ); + + // Effacer la catégorie actuelle si c'est celle en cours de suppression + if ( + currentProductCategory.value && + currentProductCategory.value.id === id + ) { + setCurrentProductCategory(null); + } + + return response; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec de la suppression de la catégorie de produit"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Rechercher des catégories de produits + */ + const searchProductCategories = async ( + term: string, + params?: { + per_page?: number; + } + ) => { + setLoading(true); + setError(null); + + try { + const response = await productCategoryService.searchProductCategories( + term, + params + ); + setProductCategories(response.data); + if (response.pagination) { + setPagination(response.pagination); + } + return response; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec de la recherche de catégories de produits"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Récupérer uniquement les catégories actives + */ + const fetchActiveProductCategories = async (params?: { + per_page?: number; + }) => { + setLoading(true); + setError(null); + + try { + const response = await productCategoryService.getActiveProductCategories(); + setProductCategories(response.data); + return response; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec du chargement des catégories actives"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Récupérer les catégories racine + */ + const fetchRootProductCategories = async () => { + setLoading(true); + setError(null); + + try { + const response = await productCategoryService.getRootProductCategories(); + setProductCategories(response.data); + return response; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec du chargement des catégories racine"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Récupérer la structure hiérarchique + */ + const fetchHierarchicalProductCategories = async () => { + setLoading(true); + setError(null); + + try { + const response = await productCategoryService.getHierarchicalProductCategories(); + setProductCategories(response.data); + return response; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec du chargement de la structure hiérarchique"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Récupérer les statistiques des catégories + */ + const fetchProductCategoryStatistics = async () => { + setLoading(true); + setError(null); + + try { + const response = await productCategoryService.getProductCategoryStatistics(); + setStatistics(response.data); + return response.data; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec du chargement des statistiques"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Basculer le statut actif/inactif d'une catégorie + */ + const toggleProductCategoryStatus = async (id: number, active: boolean) => { + setLoading(true); + setError(null); + + try { + const response = await productCategoryService.toggleProductCategoryStatus( + id, + active + ); + const updatedCategory = response.data; + + // Mettre à jour dans la liste + const index = productCategories.value.findIndex( + (category) => category.id === updatedCategory.id + ); + if (index !== -1) { + productCategories.value[index] = updatedCategory; + } + + return updatedCategory; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Échec de la mise à jour du statut"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + /** + * Réinitialiser l'état + */ + const resetState = () => { + productCategories.value = []; + currentProductCategory.value = null; + loading.value = false; + error.value = null; + pagination.value = { + current_page: 1, + last_page: 1, + per_page: 10, + total: 0, + }; + statistics.value = { + total_categories: 0, + active_categories: 0, + inactive_categories: 0, + categories_with_children: 0, + root_categories: 0, + }; + }; + + return { + // State + productCategories, + currentProductCategory, + loading, + error, + pagination, + statistics, + + // Getters + allProductCategories, + activeProductCategories, + inactiveProductCategories, + rootProductCategories, + parentProductCategories, + isLoading, + hasError, + getError, + getProductCategoryById, + getPagination, + getStatistics, + + // Actions + setLoading, + setError, + clearError, + setProductCategories, + setCurrentProductCategory, + setPagination, + setStatistics, + fetchProductCategories, + fetchProductCategory, + createProductCategory, + updateProductCategory, + deleteProductCategory, + searchProductCategories, + fetchActiveProductCategories, + fetchRootProductCategories, + fetchHierarchicalProductCategories, + fetchProductCategoryStatistics, + toggleProductCategoryStatus, + resetState, + }; +}); + +export default useProductCategoryStore; diff --git a/thanasoft-front/src/stores/productStore.ts b/thanasoft-front/src/stores/productStore.ts index 94bf2e4..fb4043b 100644 --- a/thanasoft-front/src/stores/productStore.ts +++ b/thanasoft-front/src/stores/productStore.ts @@ -1,6 +1,6 @@ import { defineStore } from "pinia"; import ProductService from "@/services/product"; -import type { Product } from "@/services/product"; +import { Product } from "@/services/product"; interface Meta { current_page: number; @@ -43,7 +43,7 @@ export const useProductStore = defineStore("product", { ), categories: (state) => { const categorySet = new Set( - state.products.map((product) => product.categorie).filter(Boolean) + state.products.map((product) => product.categorie_id).filter(Boolean) ); return Array.from(categorySet).sort(); }, @@ -212,12 +212,12 @@ export const useProductStore = defineStore("product", { } }, - async fetchProductsByCategory(category: string) { + async fetchProductsByCategory(categoryId: number) { this.loading = true; this.error = null; try { - const products = await productService.getProductsByCategory(category); + const products = await productService.getProductsByCategory(categoryId); return products.data; } catch (error: any) { this.error = @@ -269,9 +269,11 @@ export const useProductStore = defineStore("product", { }, // Local filtering functions - filterByCategory(category: string) { - if (!category) return this.products; - return this.products.filter((product) => product.categorie === category); + filterByCategory(categoryId: number) { + if (!categoryId) return this.products; + return this.products.filter( + (product) => product.categorie_id === categoryId + ); }, filterByLowStock() { diff --git a/thanasoft-front/src/views/pages/CRM/AddEmployee.vue b/thanasoft-front/src/views/pages/CRM/AddEmployee.vue new file mode 100644 index 0000000..fdf4aac --- /dev/null +++ b/thanasoft-front/src/views/pages/CRM/AddEmployee.vue @@ -0,0 +1,59 @@ + + diff --git a/thanasoft-front/src/views/pages/Employes/Employees.vue b/thanasoft-front/src/views/pages/Employes/Employees.vue new file mode 100644 index 0000000..250d38d --- /dev/null +++ b/thanasoft-front/src/views/pages/Employes/Employees.vue @@ -0,0 +1,53 @@ + + + diff --git a/thanasoft-front/src/views/pages/Employes/Employes.vue b/thanasoft-front/src/views/pages/Employes/Employes.vue deleted file mode 100644 index e4cedaf..0000000 --- a/thanasoft-front/src/views/pages/Employes/Employes.vue +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/thanasoft-front/src/views/pages/Employes/EmployesThanatopracteurs.vue b/thanasoft-front/src/views/pages/Employes/EmployesThanatopracteurs.vue new file mode 100644 index 0000000..6ea115e --- /dev/null +++ b/thanasoft-front/src/views/pages/Employes/EmployesThanatopracteurs.vue @@ -0,0 +1,45 @@ + + + diff --git a/thanasoft-front/src/views/pages/Stock/AddProduct.vue b/thanasoft-front/src/views/pages/Stock/AddProduct.vue index 8db1515..c1fa7b2 100644 --- a/thanasoft-front/src/views/pages/Stock/AddProduct.vue +++ b/thanasoft-front/src/views/pages/Stock/AddProduct.vue @@ -4,6 +4,7 @@ :loading="productStore.isLoading" :validation-errors="validationErrors" :success="showSuccess" + :categories="productCategoryStore.productCategories" @create-product="handleCreateProduct" /> @@ -13,6 +14,7 @@ import AddProductPresentation from "@/components/Organism/Stock/AddProductPresen import { useFournisseurStore } from "@/stores/fournisseurStore"; import { useProductStore } from "@/stores/productStore"; import { useNotificationStore } from "@/stores/notification"; +import { useProductCategoryStore } from "@/stores/productCategoryStore"; import { onMounted, ref } from "vue"; import { useRouter } from "vue-router"; @@ -20,12 +22,13 @@ const router = useRouter(); const fournisseurStore = useFournisseurStore(); const productStore = useProductStore(); const notificationStore = useNotificationStore(); +const productCategoryStore = useProductCategoryStore(); const validationErrors = ref({}); const showSuccess = ref(false); onMounted(async () => { - // Load fournisseurs for the supplier dropdown await fournisseurStore.fetchFournisseurs(); + await productCategoryStore.fetchProductCategories(); }); const handleCreateProduct = async (form) => { diff --git a/thanasoft-front/src/views/pages/Stock/AddProductCategory.vue b/thanasoft-front/src/views/pages/Stock/AddProductCategory.vue new file mode 100644 index 0000000..48da4ac --- /dev/null +++ b/thanasoft-front/src/views/pages/Stock/AddProductCategory.vue @@ -0,0 +1,72 @@ + + + diff --git a/thanasoft-front/src/views/pages/Stock/EditProductCategory.vue b/thanasoft-front/src/views/pages/Stock/EditProductCategory.vue new file mode 100644 index 0000000..3b06360 --- /dev/null +++ b/thanasoft-front/src/views/pages/Stock/EditProductCategory.vue @@ -0,0 +1,98 @@ + + + diff --git a/thanasoft-front/src/views/pages/Stock/ProductCategories.vue b/thanasoft-front/src/views/pages/Stock/ProductCategories.vue new file mode 100644 index 0000000..b6d92cf --- /dev/null +++ b/thanasoft-front/src/views/pages/Stock/ProductCategories.vue @@ -0,0 +1,42 @@ + + diff --git a/thanasoft-front/src/views/pages/Stock/ProductCategoryDetails.vue b/thanasoft-front/src/views/pages/Stock/ProductCategoryDetails.vue new file mode 100644 index 0000000..6f39ebe --- /dev/null +++ b/thanasoft-front/src/views/pages/Stock/ProductCategoryDetails.vue @@ -0,0 +1,95 @@ + + +