Merge pull request #150 from Oloodi/148-web-connect-frontend-web-with-firebase
148 web connect frontend web with firebase
This commit is contained in:
@@ -35,6 +35,7 @@ query listEvents @auth(level: USER) {
|
|||||||
notes
|
notes
|
||||||
requested
|
requested
|
||||||
assignedStaff
|
assignedStaff
|
||||||
|
createdBy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,5 +144,6 @@ query filterEvents(
|
|||||||
notes
|
notes
|
||||||
requested
|
requested
|
||||||
assignedStaff
|
assignedStaff
|
||||||
|
createdBy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ mutation CreateStaff(
|
|||||||
$profileType: ProfileType,
|
$profileType: ProfileType,
|
||||||
$employmentType: EmploymentType!,
|
$employmentType: EmploymentType!,
|
||||||
$english: EnglishLevel,
|
$english: EnglishLevel,
|
||||||
|
$rate: Float,
|
||||||
$rating: Float,
|
$rating: Float,
|
||||||
$reliabilityScore: Int,
|
$reliabilityScore: Int,
|
||||||
$backgroundCheckStatus: BackgroundCheckStatus!
|
$backgroundCheckStatus: BackgroundCheckStatus!
|
||||||
|
$notes: String
|
||||||
) @auth(level: USER) {
|
) @auth(level: USER) {
|
||||||
staff_insert(
|
staff_insert(
|
||||||
data: {
|
data: {
|
||||||
@@ -31,9 +33,11 @@ mutation CreateStaff(
|
|||||||
profileType: $profileType
|
profileType: $profileType
|
||||||
employmentType: $employmentType
|
employmentType: $employmentType
|
||||||
english: $english
|
english: $english
|
||||||
|
rate: $rate
|
||||||
rating: $rating
|
rating: $rating
|
||||||
reliabilityScore: $reliabilityScore
|
reliabilityScore: $reliabilityScore
|
||||||
backgroundCheckStatus: $backgroundCheckStatus
|
backgroundCheckStatus: $backgroundCheckStatus
|
||||||
|
notes: $notes
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -53,9 +57,11 @@ mutation UpdateStaff(
|
|||||||
$profileType: ProfileType,
|
$profileType: ProfileType,
|
||||||
$employmentType: EmploymentType,
|
$employmentType: EmploymentType,
|
||||||
$english: EnglishLevel,
|
$english: EnglishLevel,
|
||||||
|
$rate: Float,
|
||||||
$rating: Float,
|
$rating: Float,
|
||||||
$reliabilityScore: Int,
|
$reliabilityScore: Int,
|
||||||
$backgroundCheckStatus: BackgroundCheckStatus
|
$backgroundCheckStatus: BackgroundCheckStatus
|
||||||
|
$notes: String
|
||||||
) @auth(level: USER) {
|
) @auth(level: USER) {
|
||||||
staff_update(
|
staff_update(
|
||||||
id: $id,
|
id: $id,
|
||||||
@@ -73,9 +79,11 @@ mutation UpdateStaff(
|
|||||||
profileType: $profileType
|
profileType: $profileType
|
||||||
employmentType: $employmentType
|
employmentType: $employmentType
|
||||||
english: $english
|
english: $english
|
||||||
|
rate: $rate
|
||||||
rating: $rating
|
rating: $rating
|
||||||
reliabilityScore: $reliabilityScore
|
reliabilityScore: $reliabilityScore
|
||||||
backgroundCheckStatus: $backgroundCheckStatus
|
backgroundCheckStatus: $backgroundCheckStatus
|
||||||
|
notes: $notes
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ query listStaff @auth(level: USER) {
|
|||||||
profileType
|
profileType
|
||||||
employmentType
|
employmentType
|
||||||
english
|
english
|
||||||
|
rate
|
||||||
rating
|
rating
|
||||||
reliabilityScore
|
reliabilityScore
|
||||||
backgroundCheckStatus
|
backgroundCheckStatus
|
||||||
|
notes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,9 +40,11 @@ query getStaffById(
|
|||||||
profileType
|
profileType
|
||||||
employmentType
|
employmentType
|
||||||
english
|
english
|
||||||
|
rate
|
||||||
rating
|
rating
|
||||||
reliabilityScore
|
reliabilityScore
|
||||||
backgroundCheckStatus
|
backgroundCheckStatus
|
||||||
|
notes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,8 +74,10 @@ query filterStaff(
|
|||||||
position
|
position
|
||||||
employmentType
|
employmentType
|
||||||
english
|
english
|
||||||
|
rate
|
||||||
rating
|
rating
|
||||||
reliabilityScore
|
reliabilityScore
|
||||||
backgroundCheckStatus
|
backgroundCheckStatus
|
||||||
|
notes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
dataconnect/connector/user/mutations.gql
Normal file
41
dataconnect/connector/user/mutations.gql
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
mutation CreateUser(
|
||||||
|
$id: String!, # Firebase UID
|
||||||
|
$email: String!,
|
||||||
|
$fullName: String!,
|
||||||
|
$role: UserBaseRole!,
|
||||||
|
$userRole: String
|
||||||
|
) @auth(level: USER) {
|
||||||
|
user_insert(
|
||||||
|
data: {
|
||||||
|
id: $id
|
||||||
|
email: $email
|
||||||
|
fullName: $fullName
|
||||||
|
role: $role
|
||||||
|
userRole: $userRole
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation UpdateUser(
|
||||||
|
$id: String!,
|
||||||
|
$email: String,
|
||||||
|
$fullName: String,
|
||||||
|
$role: UserBaseRole,
|
||||||
|
$userRole: String
|
||||||
|
) @auth(level: USER) {
|
||||||
|
user_update(
|
||||||
|
id: $id,
|
||||||
|
data: {
|
||||||
|
email: $email
|
||||||
|
fullName: $fullName
|
||||||
|
role: $role
|
||||||
|
userRole: $userRole
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation DeleteUser(
|
||||||
|
$id: String!
|
||||||
|
) @auth(level: USER) {
|
||||||
|
user_delete(id: $id)
|
||||||
|
}
|
||||||
45
dataconnect/connector/user/queries.gql
Normal file
45
dataconnect/connector/user/queries.gql
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
query listUsers @auth(level: USER) {
|
||||||
|
users {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
fullName
|
||||||
|
role
|
||||||
|
userRole
|
||||||
|
createdDate
|
||||||
|
updatedDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query getUserById(
|
||||||
|
$id: String!
|
||||||
|
) @auth(level: USER) {
|
||||||
|
user(id: $id) {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
fullName
|
||||||
|
role
|
||||||
|
userRole
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query filterUsers(
|
||||||
|
$id: String,
|
||||||
|
$email: String,
|
||||||
|
$role: UserBaseRole,
|
||||||
|
$userRole: String
|
||||||
|
) @auth(level: USER) {
|
||||||
|
users(
|
||||||
|
where: {
|
||||||
|
id: { eq: $id }
|
||||||
|
email: { eq: $email }
|
||||||
|
role: { eq: $role }
|
||||||
|
userRole: { eq: $userRole }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
fullName
|
||||||
|
role
|
||||||
|
userRole
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,22 +42,24 @@ enum BackgroundCheckStatus {
|
|||||||
|
|
||||||
type Staff @table(name: "staffs") {
|
type Staff @table(name: "staffs") {
|
||||||
id: UUID! @default(expr: "uuidV4()")
|
id: UUID! @default(expr: "uuidV4()")
|
||||||
employeeName: String!
|
employeeName: String! @col(name: "employee_name")
|
||||||
vendorId: UUID # vendor_id (FK lógica a Vendor.id)
|
vendorId: UUID @col(name: "vendor_id") # vendor_id (FK lógica a Vendor.id)
|
||||||
vendorName: String
|
vendorName: String @col(name: "vendor_name")
|
||||||
manager: String
|
manager: String
|
||||||
contactNumber: String
|
contactNumber: String @col(name: "contact_number")
|
||||||
email: String
|
email: String
|
||||||
department: StaffDepartment
|
department: StaffDepartment
|
||||||
hubLocation: String
|
hubLocation: String @col(name: "hub_location")
|
||||||
track: String
|
track: String
|
||||||
position: String
|
position: String
|
||||||
profileType: ProfileType
|
profileType: ProfileType @col(name: "profile_type")
|
||||||
employmentType: EmploymentType!
|
employmentType: EmploymentType! @col(name: "employment_type")
|
||||||
english: EnglishLevel
|
english: EnglishLevel
|
||||||
|
rate: Float
|
||||||
rating: Float
|
rating: Float
|
||||||
reliabilityScore: Int
|
reliabilityScore: Int @col(name: "reliability_score")
|
||||||
backgroundCheckStatus: BackgroundCheckStatus! # background_check_status
|
backgroundCheckStatus: BackgroundCheckStatus! @col(name: "background_check_status")
|
||||||
|
notes: String
|
||||||
createdDate: Timestamp @default(expr: "request.time")
|
createdDate: Timestamp @default(expr: "request.time")
|
||||||
updatedDate: Timestamp @default(expr: "request.time")
|
updatedDate: Timestamp @default(expr: "request.time")
|
||||||
createdBy: String @default(expr: "auth.uid")
|
createdBy: String @default(expr: "auth.uid")
|
||||||
|
|||||||
15
dataconnect/schema/user.gql
Normal file
15
dataconnect/schema/user.gql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
enum UserBaseRole {
|
||||||
|
ADMIN
|
||||||
|
USER
|
||||||
|
}
|
||||||
|
|
||||||
|
type User @table(name: "users") {
|
||||||
|
id: String! # user_id / uid de Firebase
|
||||||
|
email: String!
|
||||||
|
fullName: String!
|
||||||
|
role: UserBaseRole!
|
||||||
|
userRole: String
|
||||||
|
createdDate: Timestamp @default(expr: "request.time")
|
||||||
|
updatedDate: Timestamp @default(expr: "request.time")
|
||||||
|
createdBy: String @default(expr: "auth.uid")
|
||||||
|
}
|
||||||
253
frontend-web/dataconnect-naming-and-enum-findings.md
Normal file
253
frontend-web/dataconnect-naming-and-enum-findings.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
# DataConnect – Inconsistencies between the Base44 frontend and the backend (Firebase Data Connect + PostgreSQL)
|
||||||
|
**Author:** José Salazar
|
||||||
|
**Date:** Dec 2025
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 Purpose of this document
|
||||||
|
|
||||||
|
Document all findings during the integration of the new backend
|
||||||
|
(**Firebase Data Connect + PostgreSQL**) with the frontend generated by **Base44 AI**.
|
||||||
|
|
||||||
|
This document summarizes:
|
||||||
|
|
||||||
|
- Issues found
|
||||||
|
- camelCase vs snake_case inconsistencies
|
||||||
|
- Enum inconsistencies (uppercase/lowercase and dashes)
|
||||||
|
- Differences between what DataConnect returns vs what the frontend expects
|
||||||
|
- Recommended fixes
|
||||||
|
- Suggestions for Base44 AI to update its model
|
||||||
|
- Impact on queries, mutations, and the generated SDK
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1️⃣ Enums – Different formats between Front and Backend
|
||||||
|
|
||||||
|
### Observation
|
||||||
|
|
||||||
|
In the frontend, enum values are in mixed formats, for example:
|
||||||
|
|
||||||
|
- UPPERCASE: SKILLED
|
||||||
|
- camelCase: fullTime
|
||||||
|
|
||||||
|
In the backend (DataConnect schema), enums are defined only in UPPERCASE, for example:
|
||||||
|
|
||||||
|
- FULL_TIME
|
||||||
|
- CROSS_TRAINED
|
||||||
|
- NOT_REQUIRED
|
||||||
|
- PENDING
|
||||||
|
|
||||||
|
DataConnect only accepts these exact values.
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
When the frontend sends:
|
||||||
|
|
||||||
|
- "crossTrained" instead of CROSS_TRAINED
|
||||||
|
- "fluent" instead of FLUENT
|
||||||
|
|
||||||
|
### Impact
|
||||||
|
|
||||||
|
- Mutations can fail or return enum validation errors.
|
||||||
|
- Filters using enums return no results.
|
||||||
|
- Behavior changes depending on how Base44 AI generated the object.
|
||||||
|
|
||||||
|
### Recommendation
|
||||||
|
|
||||||
|
- Define a single standard: **ALL enums must be UPPERCASE** on the frontend and backend.
|
||||||
|
- Before sending to the backend, normalize enum values to uppercase.
|
||||||
|
|
||||||
|
### Suggestion for Base44 AI
|
||||||
|
|
||||||
|
- Adjust models so they always generate enums in UPPERCASE.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2️⃣ Enums with dashes (“-”) – Not valid in GraphQL
|
||||||
|
|
||||||
|
### Observation
|
||||||
|
|
||||||
|
In the legacy frontend, some enum values contain dashes, for example:
|
||||||
|
|
||||||
|
- CUSTOMER-SERVICE
|
||||||
|
- CROSS-TRAINED
|
||||||
|
- PART-TIME
|
||||||
|
|
||||||
|
But in GraphQL enums only allow letters, numbers, and underscores.
|
||||||
|
The backend had to define them as:
|
||||||
|
|
||||||
|
- CUSTOMER_SERVICE
|
||||||
|
- CROSS_TRAINED
|
||||||
|
- PART_TIME
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
When the frontend sends "CUSTOMER-SERVICE" or "CROSS-TRAINED":
|
||||||
|
|
||||||
|
- The backend expects CUSTOMER_SERVICE or CROSS_TRAINED.
|
||||||
|
- There is no match between the frontend value and the DataConnect enum.
|
||||||
|
|
||||||
|
### Impact
|
||||||
|
|
||||||
|
- Enum filters return nothing.
|
||||||
|
- Mutations fail when trying to save invalid enum values.
|
||||||
|
- Compatibility between the Base44 model and the DataConnect schema breaks.
|
||||||
|
|
||||||
|
### Recommendation
|
||||||
|
|
||||||
|
- Standardize all enums to UPPERCASE SNAKE_CASE (e.g., CUSTOMER_SERVICE).
|
||||||
|
- Never use dashes “-” in enum values.
|
||||||
|
|
||||||
|
### Suggestion for Base44 AI
|
||||||
|
|
||||||
|
- Update models so enum values are always generated as
|
||||||
|
UPPERCASE_WITH_UNDERSCORE (e.g., CUSTOMER_SERVICE), without dashes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3️⃣ Field names – Front in snake_case vs DataConnect in camelCase
|
||||||
|
|
||||||
|
### Observation
|
||||||
|
|
||||||
|
The original Base44 frontend uses snake_case field names, for example:
|
||||||
|
|
||||||
|
- contact_number
|
||||||
|
- vendor_id
|
||||||
|
- background_check_status
|
||||||
|
- hub_location
|
||||||
|
|
||||||
|
In DataConnect the schema is camelCase, and although you can map to the actual PostgreSQL column using @col, the GraphQL type remains camelCase, for example:
|
||||||
|
|
||||||
|
- contactNumber (mapped to "contact_number" in Postgres)
|
||||||
|
- vendorId (mapped to "vendor_id")
|
||||||
|
- backgroundCheckStatus (mapped to "background_check_status")
|
||||||
|
- hubLocation (mapped to "hub_location")
|
||||||
|
|
||||||
|
Meaning:
|
||||||
|
|
||||||
|
- In the database (PostgreSQL) names remain snake_case.
|
||||||
|
- In DataConnect and the SDK they are exposed as camelCase.
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
The frontend still expects/reads fields like contact_number, but the SDK returns contactNumber.
|
||||||
|
A similar issue happens when the frontend sends payloads in snake_case:
|
||||||
|
|
||||||
|
- The GraphQL schema does not recognize contact_number.
|
||||||
|
- It only accepts contactNumber.
|
||||||
|
|
||||||
|
### Impact
|
||||||
|
|
||||||
|
- UI fails to show data because it reads keys that don’t exist (snake_case).
|
||||||
|
- Mutations fail or ignore fields due to mismatched names.
|
||||||
|
- Filters with snake_case are invalid in GraphQL.
|
||||||
|
|
||||||
|
### Recommendation
|
||||||
|
|
||||||
|
- Agree that **all communication with DataConnect (frontend + SDK) uses camelCase**.
|
||||||
|
- Keep snake_case only at PostgreSQL level using @col, for example:
|
||||||
|
|
||||||
|
employeeName: String @col(name: "employee_name")
|
||||||
|
|
||||||
|
Thus:
|
||||||
|
|
||||||
|
- Frontend / SDK / GraphQL → camelCase (employeeName)
|
||||||
|
- PostgreSQL → snake_case (employee_name)
|
||||||
|
|
||||||
|
### Suggestion for Base44 AI
|
||||||
|
|
||||||
|
- Adjust generated frontend code so it uses camelCase when consuming the new backend.
|
||||||
|
- If Supabase or another backend is still used, document all mappings clearly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4️⃣ Fields used by the frontend but not listed in API Spec v3
|
||||||
|
|
||||||
|
### Observation
|
||||||
|
|
||||||
|
During integration we found that Base44 frontend uses fields not defined in the official document:
|
||||||
|
|
||||||
|
Reference file:
|
||||||
|
docs/03-backend-api-specification-v3.md
|
||||||
|
|
||||||
|
Examples in the Staff entity:
|
||||||
|
|
||||||
|
The frontend sends and displays fields like:
|
||||||
|
|
||||||
|
- notes
|
||||||
|
- rate
|
||||||
|
|
||||||
|
But these fields were not defined in the original v3 specification for Staff.
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
- The frontend assumes these fields exist because the old database had them.
|
||||||
|
- The DataConnect schema does not include them.
|
||||||
|
- Sending these values in mutations causes validation errors.
|
||||||
|
|
||||||
|
### Impact
|
||||||
|
|
||||||
|
- Inconsistency between what the UI shows/edits and what is actually persisted.
|
||||||
|
- Risk of losing data the user believes is being saved.
|
||||||
|
- Hard to maintain a 1:1 mapping between the previous Base44 model and the new backend.
|
||||||
|
|
||||||
|
### Recommendation
|
||||||
|
|
||||||
|
- Validate which fields should truly exist for each entity (e.g., Staff).
|
||||||
|
- Align these three items:
|
||||||
|
1. API Spec v3
|
||||||
|
2. DataConnect Schema
|
||||||
|
3. Base44 Frontend
|
||||||
|
|
||||||
|
- If a field is no longer needed, remove it from the frontend.
|
||||||
|
- If important, add it formally to the API Spec and to the DataConnect schema.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5️⃣ DataConnect vs Front – Observed behavior
|
||||||
|
|
||||||
|
1. DataConnect:
|
||||||
|
- Always exposes fields in camelCase.
|
||||||
|
- Enforces enum restrictions exactly as defined (UPPERCASE, no dashes).
|
||||||
|
- Allows mapping to Postgres column names using @col, but GraphQL names remain camelCase.
|
||||||
|
|
||||||
|
2. Base44 Frontend:
|
||||||
|
- Uses snake_case in many areas.
|
||||||
|
- Uses enums in mixed formats (uppercase, camelCase, with dashes).
|
||||||
|
- Contains extra fields not included in API Spec v3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6️⃣ Suggested fixes (personal criteria)
|
||||||
|
|
||||||
|
1. Enums
|
||||||
|
- Standardize to UPPERCASE_SNAKE_CASE for all enum values.
|
||||||
|
- Apply a normalization layer in the frontend to convert any format into the official one before hitting the backend.
|
||||||
|
|
||||||
|
2. Field names
|
||||||
|
- Migrate the frontend to camelCase for any interaction with DataConnect.
|
||||||
|
- Keep snake_case only at the database layer using @col.
|
||||||
|
|
||||||
|
3. Extra fields
|
||||||
|
- Review all fields the frontend sends and compare with API Spec v3.
|
||||||
|
- Remove or add fields depending on what becomes the “source of truth” (Spec v3).
|
||||||
|
|
||||||
|
4. Documentation
|
||||||
|
- Keep this file updated as the Base44 → DataConnect migration reference.
|
||||||
|
- Add valid payload examples for each entity (Staff, Vendor, Invoice, etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7️⃣ Summary
|
||||||
|
|
||||||
|
- Always generate:
|
||||||
|
- Enums: UPPERCASE_SNAKE_CASE (e.g., FULL_TIME, CUSTOMER_SERVICE).
|
||||||
|
- Fields: camelCase (e.g., contactNumber, hubLocation, backgroundCheckStatus).
|
||||||
|
|
||||||
|
- Avoid:
|
||||||
|
- Dashes “-” in enum values.
|
||||||
|
- Spaces in enum values.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This document captures the current findings and serves as a guide to fully align the Base44 frontend with the backend based on Firebase Data Connect + PostgreSQL.
|
||||||
22
frontend-web/src/api/client.js
Normal file
22
frontend-web/src/api/client.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { auth } from "../firebase";
|
||||||
|
|
||||||
|
const apiClient = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_API_BASE_URL, // You will need to add this to your .env file
|
||||||
|
});
|
||||||
|
|
||||||
|
apiClient.interceptors.request.use(
|
||||||
|
async (config) => {
|
||||||
|
const user = auth.currentUser;
|
||||||
|
if (user) {
|
||||||
|
const token = await user.getIdToken();
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default apiClient;
|
||||||
491
frontend-web/src/api/krowSDK.js
Normal file
491
frontend-web/src/api/krowSDK.js
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
import apiClient from './client';
|
||||||
|
import { auth, dataConnect } from '../firebase';
|
||||||
|
import { signOut } from 'firebase/auth';
|
||||||
|
|
||||||
|
import * as dcSdk from '@dataconnect/generated'; // listEvents, createEvent, etc.
|
||||||
|
|
||||||
|
// --- Auth Module ---
|
||||||
|
const authModule = {
|
||||||
|
/**
|
||||||
|
* Fetches the currently authenticated user's profile from the backend.
|
||||||
|
* @returns {Promise<object>} The user profile.
|
||||||
|
*/
|
||||||
|
me: async () => {
|
||||||
|
// 1. Firebase auth user
|
||||||
|
const fbUser = auth.currentUser;
|
||||||
|
|
||||||
|
if (!fbUser) {
|
||||||
|
return null; // NO ESTÁ LOGGEADO
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Attempt to load matching Krow User from DataConnect
|
||||||
|
// (because your Krow user metadata is stored in the "users" table)
|
||||||
|
let krowUser = null;
|
||||||
|
try {
|
||||||
|
const response = await dcSdk.getUser(dataConnect, { id: fbUser.uid });
|
||||||
|
krowUser = response.data?.user || null;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Krow user not found in DataConnect, returning Firebase-only info.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Build unified "me" object
|
||||||
|
return {
|
||||||
|
id: fbUser.uid,
|
||||||
|
email: fbUser.email,
|
||||||
|
fullName: krowUser?.fullName || fbUser.displayName || null,
|
||||||
|
role: krowUser?.role || "user",
|
||||||
|
user_role: krowUser?.userRole || null,
|
||||||
|
firebase: fbUser,
|
||||||
|
krow: krowUser
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs the user out.
|
||||||
|
* @param {string} [redirectUrl] - Optional URL to redirect to after logout.
|
||||||
|
*/
|
||||||
|
logout: async (redirectUrl) => {
|
||||||
|
await signOut(auth);
|
||||||
|
if (redirectUrl) {
|
||||||
|
window.location.href = redirectUrl;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a user is currently authenticated.
|
||||||
|
* @returns {boolean} True if a user is authenticated.
|
||||||
|
*/
|
||||||
|
isAuthenticated: () => {
|
||||||
|
return !!auth.currentUser;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Core Integrations Module ---
|
||||||
|
const coreIntegrationsModule = {
|
||||||
|
/**
|
||||||
|
* Sends an email.
|
||||||
|
* @param {object} params - { to, subject, body }
|
||||||
|
* @returns {Promise<object>} API response.
|
||||||
|
*/
|
||||||
|
SendEmail: async (params) => {
|
||||||
|
const { data } = await apiClient.post('/sendEmail', params);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes a large language model.
|
||||||
|
* @param {object} params - { prompt, response_json_schema, file_urls }
|
||||||
|
* @returns {Promise<object>} API response.
|
||||||
|
*/
|
||||||
|
InvokeLLM: async (params) => {
|
||||||
|
const { data } = await apiClient.post('/invokeLLM', params);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a public file.
|
||||||
|
* @param {File} file - The file to upload.
|
||||||
|
* @returns {Promise<object>} API response with file_url.
|
||||||
|
*/
|
||||||
|
UploadFile: async ({ file }) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
const { data } = await apiClient.post('/uploadFile', formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a private file.
|
||||||
|
* @param {File} file - The file to upload.
|
||||||
|
* @returns {Promise<object>} API response with file_uri.
|
||||||
|
*/
|
||||||
|
UploadPrivateFile: async ({ file }) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
const { data } = await apiClient.post('/uploadPrivateFile', formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a temporary signed URL for a private file.
|
||||||
|
* @param {object} params - { file_uri, expires_in }
|
||||||
|
* @returns {Promise<object>} API response with signed_url.
|
||||||
|
*/
|
||||||
|
CreateFileSignedUrl: async (params) => {
|
||||||
|
const { data } = await apiClient.post('/createSignedUrl', params);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataconnectEntityConfig = {
|
||||||
|
User: {
|
||||||
|
list: 'listUsers',
|
||||||
|
get: 'getUserById',
|
||||||
|
create: 'createUser',
|
||||||
|
update: 'updateUser',
|
||||||
|
delete: 'deleteUser',
|
||||||
|
filter: 'filterUsers',
|
||||||
|
},
|
||||||
|
Event: {
|
||||||
|
list: 'listEvents',
|
||||||
|
create: 'createEvent',
|
||||||
|
get: 'getEventById',
|
||||||
|
update: 'updateEvent',
|
||||||
|
delete: 'deleteEvent',
|
||||||
|
filter: 'filterEvents',
|
||||||
|
},
|
||||||
|
|
||||||
|
Staff: {
|
||||||
|
list: 'listStaff',
|
||||||
|
create: 'createStaff',
|
||||||
|
get: 'getStaffById',
|
||||||
|
update: 'updateStaff',
|
||||||
|
delete: 'deleteStaff',
|
||||||
|
filter: 'filterStaff',
|
||||||
|
},
|
||||||
|
|
||||||
|
Vendor: {
|
||||||
|
list: 'listVendor',
|
||||||
|
get: 'getVendorById',
|
||||||
|
create: 'createVendor',
|
||||||
|
update: 'updateVendor',
|
||||||
|
delete: 'deleteVendor',
|
||||||
|
filter: 'filterVendors',
|
||||||
|
},
|
||||||
|
|
||||||
|
VendorRate: {
|
||||||
|
list: 'listVendorRate',
|
||||||
|
get: 'getVendorRateById',
|
||||||
|
create: 'createVendorRate',
|
||||||
|
update: 'updateVendorRate',
|
||||||
|
delete: 'deleteVendorRate',
|
||||||
|
filter: 'filterVendorRates',
|
||||||
|
},
|
||||||
|
|
||||||
|
VendorDefaultSetting:{
|
||||||
|
list: 'listVendorDefaultSettings',
|
||||||
|
get: 'getVendorDefaultSettingById',
|
||||||
|
create: 'createVendorDefaultSetting',
|
||||||
|
update: 'updateVendorDefaultSetting',
|
||||||
|
delete: 'deleteVendorDefaultSetting',
|
||||||
|
filter: 'filterVendorDefaultSettings',
|
||||||
|
},
|
||||||
|
|
||||||
|
Invoice:{
|
||||||
|
list: 'listInvoice',
|
||||||
|
get: 'getInvoiceById',
|
||||||
|
create: 'createInvoice',
|
||||||
|
update: 'updateInvoice',
|
||||||
|
delete: 'deleteInvoice',
|
||||||
|
filter: 'filterInvoices',
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
Business:{
|
||||||
|
list: 'listBusiness',
|
||||||
|
get: 'getBusinessById',
|
||||||
|
create: 'createBusiness',
|
||||||
|
update: 'updateBusiness',
|
||||||
|
delete: 'deleteBusiness',
|
||||||
|
filter: 'filterBusiness',
|
||||||
|
},
|
||||||
|
|
||||||
|
Certification:{
|
||||||
|
list: 'listCertification',
|
||||||
|
get: 'getCertificationById',
|
||||||
|
create: 'createCertification',
|
||||||
|
update: 'updateCertification',
|
||||||
|
delete: 'deleteCertification',
|
||||||
|
filter: 'filterCertification',
|
||||||
|
},
|
||||||
|
|
||||||
|
Team:{
|
||||||
|
list: 'listTeam',
|
||||||
|
get: 'getTeamById',
|
||||||
|
create: 'createTeam',
|
||||||
|
update: 'updateTeam',
|
||||||
|
delete: 'deleteTeam',
|
||||||
|
filter: 'filterTeam',
|
||||||
|
},
|
||||||
|
|
||||||
|
TeamMember: {
|
||||||
|
list: 'listTeamMember',
|
||||||
|
get: 'getTeamMemberById',
|
||||||
|
create: 'createTeamMember',
|
||||||
|
update: 'updateTeamMember',
|
||||||
|
delete: 'deleteTeamMember',
|
||||||
|
filter: 'filterTeamMember',
|
||||||
|
},
|
||||||
|
|
||||||
|
TeamHub: {
|
||||||
|
list: 'listTeamHub',
|
||||||
|
get: 'getTeamHubById',
|
||||||
|
create: 'createTeamHub',
|
||||||
|
update: 'updateTeamHub',
|
||||||
|
delete: 'deleteTeamHub',
|
||||||
|
filter: 'filterTeamHub',
|
||||||
|
},
|
||||||
|
|
||||||
|
TeamMemberInvite: {
|
||||||
|
list: 'listTeamMemberInvite',
|
||||||
|
get: 'getTeamMemberInviteById',
|
||||||
|
create: 'createTeamMemberInvite',
|
||||||
|
update: 'updateTeamMemberInvite',
|
||||||
|
delete: 'deleteTeamMemberInvite',
|
||||||
|
filter: 'filterTeamMemberInvite',
|
||||||
|
},
|
||||||
|
|
||||||
|
Conversation:{
|
||||||
|
list: 'listConversation',
|
||||||
|
get: 'getConversationById',
|
||||||
|
create: 'createConversation',
|
||||||
|
update: 'updateConversation',
|
||||||
|
delete: 'deleteConversation',
|
||||||
|
filter: 'filterConversation',
|
||||||
|
},
|
||||||
|
|
||||||
|
Message:{
|
||||||
|
list: 'listMessage',
|
||||||
|
get: 'getMessageById',
|
||||||
|
create: 'createMessage',
|
||||||
|
update: 'updateMessage',
|
||||||
|
delete: 'deleteMessage',
|
||||||
|
filter: 'filterMessage',
|
||||||
|
},
|
||||||
|
|
||||||
|
ActivityLog:{
|
||||||
|
list: 'listActivityLog',
|
||||||
|
get: 'getActivityLogById',
|
||||||
|
create: 'createActivityLog',
|
||||||
|
update: 'updateActivityLog',
|
||||||
|
delete: 'deleteActivityLog',
|
||||||
|
filter: 'filterActivityLog',
|
||||||
|
},
|
||||||
|
|
||||||
|
Enterprise:{
|
||||||
|
list: 'listEnterprise',
|
||||||
|
get: 'getEnterpriseById',
|
||||||
|
create: 'createEnterprise',
|
||||||
|
update: 'updateEnterprise',
|
||||||
|
delete: 'deleteEnterprise',
|
||||||
|
filter: 'filterEnterprise',
|
||||||
|
},
|
||||||
|
|
||||||
|
Sector:{
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
Partner:{
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
Order:{
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
Shift:{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper for methods not implemented
|
||||||
|
const notImplemented = (entityName, method) => async () => {
|
||||||
|
throw new Error(`${entityName}.${method} is not implemented yet for Data Connect`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Entities Module ( Data Connect, without REST Base44) ---
|
||||||
|
const entitiesModule = {};
|
||||||
|
|
||||||
|
Object.entries(dataconnectEntityConfig).forEach(([entityName, ops]) => {
|
||||||
|
entitiesModule[entityName] = {
|
||||||
|
|
||||||
|
get: notImplemented(entityName, 'get'),
|
||||||
|
update: notImplemented(entityName, 'update'),
|
||||||
|
delete: notImplemented(entityName, 'delete'),
|
||||||
|
filter: notImplemented(entityName, 'filter'),
|
||||||
|
list: notImplemented(entityName, 'list'),
|
||||||
|
create: notImplemented(entityName, 'create'),
|
||||||
|
|
||||||
|
// list
|
||||||
|
...(ops.list && {
|
||||||
|
list: async (params) => {
|
||||||
|
const fn = dcSdk[ops.list];
|
||||||
|
if (typeof fn !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
`Data Connect operation "${ops.list}" not found for entity "${entityName}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(dataConnect);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
// create
|
||||||
|
...(ops.create && {
|
||||||
|
create: async (params) => {
|
||||||
|
const fn = dcSdk[ops.create];
|
||||||
|
if (typeof fn !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
`Data Connect operation "${ops.create}" not found for entity "${entityName}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = params ?? {};
|
||||||
|
if (!data) {
|
||||||
|
throw new Error(
|
||||||
|
`${entityName}.create expects a payload like { data: { ...fields } }`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(dataConnect, data);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
//get
|
||||||
|
...(ops.get && {
|
||||||
|
get: async (params) => {
|
||||||
|
const fn = dcSdk[ops.get];
|
||||||
|
if (typeof fn !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
`Data Connect operation "${ops.get}" not found for entity "${entityName}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params || typeof params !== 'object') {
|
||||||
|
throw new Error(`${entityName}.get expects an object of variables (e.g. { id })`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(dataConnect, params);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
//update
|
||||||
|
...(ops.update && {
|
||||||
|
update: async (params) => {
|
||||||
|
const fn = dcSdk[ops.update];
|
||||||
|
if (typeof fn !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
`Data Connect operation "${ops.update}" not found for entity "${entityName}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params || typeof params !== 'object') {
|
||||||
|
throw new Error(
|
||||||
|
`${entityName}.update expects an object of variables matching the GraphQL mutation`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, data } = params;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
throw new Error(`${entityName}.update requires an "id" field`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || typeof data !== 'object') {
|
||||||
|
throw new Error(
|
||||||
|
`${entityName}.update requires a "data" object with the fields to update`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const vars = { id, ...data };
|
||||||
|
|
||||||
|
return fn(dataConnect, vars);
|
||||||
|
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
|
||||||
|
// delete
|
||||||
|
...(ops.delete && {
|
||||||
|
delete: async (params) => {
|
||||||
|
const fn = dcSdk[ops.delete];
|
||||||
|
if (typeof fn !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
`Data Connect operation "${ops.delete}" not found for entity "${entityName}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params || typeof params !== 'object') {
|
||||||
|
throw new Error(
|
||||||
|
`${entityName}.delete expects an object like { id }`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = params;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
throw new Error(`${entityName}.delete requires an "id" field`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data Connect solo espera { id } como variables
|
||||||
|
return fn(dataConnect, { id });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
|
||||||
|
// filter
|
||||||
|
...(ops.filter && {
|
||||||
|
filter: async (params) => {
|
||||||
|
const fn = dcSdk[ops.filter];
|
||||||
|
if (typeof fn !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
`Data Connect operation "${ops.filter}" not found for entity "${entityName}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params) {
|
||||||
|
if (ops.list) {//if no params, call to list()
|
||||||
|
const listFn = dcSdk[ops.list];
|
||||||
|
if (typeof listFn !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
`Data Connect operation "${ops.list}" not found for entity "${entityName}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return listFn(dataConnect);
|
||||||
|
}
|
||||||
|
throw new Error(`${entityName}.filter expects params or a list operation`);
|
||||||
|
}
|
||||||
|
const rawFilters = params.filters ?? params;
|
||||||
|
const variables = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(rawFilters)) {//cleaning undefined/null/'' values
|
||||||
|
if (value !== undefined && value !== null && value !== '') {
|
||||||
|
variables[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no valid filters, call to list()
|
||||||
|
if (Object.keys(variables).length === 0) {
|
||||||
|
if (ops.list) {
|
||||||
|
const listFn = dcSdk[ops.list];
|
||||||
|
if (typeof listFn !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
`Data Connect operation "${ops.list}" not found for entity "${entityName}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return listFn(dataConnect);
|
||||||
|
}
|
||||||
|
throw new Error(`${entityName}.filter received no valid filters and no list operation`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(dataConnect, variables);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// --- Main SDK Export ---
|
||||||
|
export const krowSDK = {
|
||||||
|
auth: authModule,
|
||||||
|
integrations: {
|
||||||
|
Core: coreIntegrationsModule,
|
||||||
|
},
|
||||||
|
entities: entitiesModule,
|
||||||
|
};
|
||||||
25
frontend-web/src/components/auth/ProtectedRoute.jsx
Normal file
25
frontend-web/src/components/auth/ProtectedRoute.jsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function ProtectedRoute({ children }) {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100">
|
||||||
|
<div className="text-center">
|
||||||
|
<Loader2 className="w-12 h-12 text-[#0A39DF] animate-spin mx-auto mb-4" />
|
||||||
|
<p className="text-slate-600 font-medium">Loading...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return <Navigate to="/login" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
25
frontend-web/src/components/auth/PublicRoute.jsx
Normal file
25
frontend-web/src/components/auth/PublicRoute.jsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function PublicRoute({ children }) {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100">
|
||||||
|
<div className="text-center">
|
||||||
|
<Loader2 className="w-12 h-12 text-[#0A39DF] animate-spin mx-auto mb-4" />
|
||||||
|
<p className="text-slate-600 font-medium">Loading...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
return <Navigate to="/" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { base44 } from "@/api/base44Client";
|
import { krowSDK } from "@/api/krowSDK";
|
||||||
|
|
||||||
const TOAST_LIMIT = 5
|
const TOAST_LIMIT = 5
|
||||||
const TOAST_REMOVE_DELAY = 1000000
|
const TOAST_REMOVE_DELAY = 1000000
|
||||||
@@ -95,7 +95,12 @@ function dispatch(action) {
|
|||||||
// Helper function to create notification in ActivityLog instead of toast
|
// Helper function to create notification in ActivityLog instead of toast
|
||||||
async function createNotification(title, description, variant) {
|
async function createNotification(title, description, variant) {
|
||||||
try {
|
try {
|
||||||
const user = await base44.auth.me();
|
const user = await krowSDK.auth.me();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
console.warn("Cannot create notification: user not authenticated.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Determine icon and color based on variant and title
|
// Determine icon and color based on variant and title
|
||||||
let icon_type = "check";
|
let icon_type = "check";
|
||||||
@@ -124,7 +129,7 @@ async function createNotification(title, description, variant) {
|
|||||||
activity_type = "staff_assigned";
|
activity_type = "staff_assigned";
|
||||||
}
|
}
|
||||||
|
|
||||||
await base44.entities.ActivityLog.create({
|
const payload = {
|
||||||
title: title.replace(/✅|❌|⚠️/g, '').trim(),
|
title: title.replace(/✅|❌|⚠️/g, '').trim(),
|
||||||
description: description || "",
|
description: description || "",
|
||||||
activity_type: activity_type,
|
activity_type: activity_type,
|
||||||
@@ -132,7 +137,10 @@ async function createNotification(title, description, variant) {
|
|||||||
is_read: false,
|
is_read: false,
|
||||||
icon_type: icon_type,
|
icon_type: icon_type,
|
||||||
icon_color: icon_color,
|
icon_color: icon_color,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
await krowSDK.entities.ActivityLog.create({ data: payload });
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to create notification:", error);
|
console.error("Failed to create notification:", error);
|
||||||
}
|
}
|
||||||
|
|||||||
29
frontend-web/src/firebase.js
Normal file
29
frontend-web/src/firebase.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Import the functions you need from the SDKs you need
|
||||||
|
import { initializeApp, getApps, getApp } from "firebase/app";
|
||||||
|
import { getAuth } from "firebase/auth";
|
||||||
|
import { getDataConnect } from 'firebase/data-connect';
|
||||||
|
import { connectorConfig } from '@dataconnect/generated';
|
||||||
|
|
||||||
|
// Your web app's Firebase configuration
|
||||||
|
const firebaseConfig = {
|
||||||
|
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
|
||||||
|
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
|
||||||
|
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
|
||||||
|
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
|
||||||
|
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
|
||||||
|
appId: import.meta.env.VITE_FIREBASE_APP_ID
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize Firebase app only once
|
||||||
|
let app;
|
||||||
|
if (getApps().length === 0) { // Check if no app is already initialized
|
||||||
|
app = initializeApp(firebaseConfig);
|
||||||
|
} else {
|
||||||
|
app = getApp(); // If already initialized, use the existing app
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dataConnect = getDataConnect(app, connectorConfig);
|
||||||
|
export const auth = getAuth(app);
|
||||||
|
|
||||||
|
// For debugging purposes, if you previously added this, it can stay or be removed
|
||||||
|
// window.firebaseAuth = auth;
|
||||||
19
frontend-web/src/hooks/useAuth.js
Normal file
19
frontend-web/src/hooks/useAuth.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { onAuthStateChanged } from 'firebase/auth';
|
||||||
|
import { auth } from '@/firebase';
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
const [user, setUser] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = onAuthStateChanged(auth, (user) => {
|
||||||
|
setUser(user);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => unsubscribe();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { user, loading };
|
||||||
|
}
|
||||||
@@ -1,26 +1,90 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { base44 } from "@/api/base44Client";
|
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { createPageUrl } from "@/utils";
|
import { createPageUrl } from "@/utils";
|
||||||
|
import { krowSDK } from "@/api/krowSDK";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import StaffForm from "@/components/staff/StaffForm";
|
import StaffForm from "@/components/staff/StaffForm";
|
||||||
|
|
||||||
export default function AddStaff() {
|
export default function AddStaff() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
const createStaffMutation = useMutation({
|
const createStaffMutation = useMutation({
|
||||||
mutationFn: (staffData) => base44.entities.Staff.create(staffData),
|
mutationFn: (staffPayload) => krowSDK.entities.Staff.create({ data: staffPayload }),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['staff'] });
|
queryClient.invalidateQueries({ queryKey: ['staff'] });
|
||||||
navigate(createPageUrl("Dashboard"));
|
toast({
|
||||||
|
title: "✅ Staff Member Added",
|
||||||
|
description: "The new staff member has been successfully created.",
|
||||||
|
});
|
||||||
|
navigate(createPageUrl("StaffDirectory"));
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast({
|
||||||
|
title: "❌ Error Creating Staff",
|
||||||
|
description: error.message || "An unknown error occurred.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = (staffData) => {
|
const handleSubmit = (formData) => {
|
||||||
createStaffMutation.mutate(staffData);
|
// 1. Map snake_case from form to camelCase for GraphQL
|
||||||
|
// 2. Transform enum values to uppercase
|
||||||
|
// 3. Add required fields not in the form
|
||||||
|
// 4. Filter out fields not in the mutation
|
||||||
|
const employmentTypeMap = {
|
||||||
|
"Full Time": "FULL_TIME",
|
||||||
|
"Part Time": "PART_TIME",
|
||||||
|
"On call": "ON_CALL",
|
||||||
|
"Weekends": "WEEKENDS",
|
||||||
|
"Specific Days": "SPECIFIC_DAYS",
|
||||||
|
"Seasonal": "SEASONAL",
|
||||||
|
"Medical Leave": "MEDICAL_LEAVE",
|
||||||
|
};
|
||||||
|
|
||||||
|
const englishLevelMap = {
|
||||||
|
"Fluent": "FLUENT",
|
||||||
|
"Intermediate": "INTERMEDIATE",
|
||||||
|
"Basic": "BASIC",
|
||||||
|
"None": "NONE",
|
||||||
|
};
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
// --- Fields from error messages ---
|
||||||
|
employeeName: formData.employee_name,
|
||||||
|
employmentType: employmentTypeMap[formData.employment_type],
|
||||||
|
english: englishLevelMap[formData.english],
|
||||||
|
backgroundCheckStatus: 'NOT_REQUIRED', // Default as it's missing from form
|
||||||
|
|
||||||
|
// --- Other likely fields (from form) ---
|
||||||
|
contactNumber: formData.contact_number,
|
||||||
|
hubLocation: formData.hub_location,
|
||||||
|
profileType: formData.profile_type,
|
||||||
|
reliabilityScore: parseInt(formData.reliability_score) || 100,
|
||||||
|
|
||||||
|
// --- Fields from form that might match schema ---
|
||||||
|
email: formData.email,
|
||||||
|
position: formData.position,
|
||||||
|
department: formData.department,
|
||||||
|
manager: formData.manager,
|
||||||
|
rate: parseFloat(formData.rate) || 0,
|
||||||
|
notes: formData.notes,
|
||||||
|
rating: parseFloat(formData.rating) || 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove any keys with undefined values to keep the payload clean
|
||||||
|
Object.keys(payload).forEach(key => {
|
||||||
|
if (payload[key] === undefined || payload[key] === null) {
|
||||||
|
delete payload[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
createStaffMutation.mutate(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -29,11 +93,11 @@ export default function AddStaff() {
|
|||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => navigate(createPageUrl("Dashboard"))}
|
onClick={() => navigate(createPageUrl("StaffDirectory"))}
|
||||||
className="mb-4 hover:bg-slate-100"
|
className="mb-4 hover:bg-slate-100"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
Back to Dashboard
|
Back to Staff Directory
|
||||||
</Button>
|
</Button>
|
||||||
<h1 className="text-3xl md:text-4xl font-bold text-slate-900 mb-2">Add New Staff Member</h1>
|
<h1 className="text-3xl md:text-4xl font-bold text-slate-900 mb-2">Add New Staff Member</h1>
|
||||||
<p className="text-slate-600">Fill in the details to add a new team member</p>
|
<p className="text-slate-600">Fill in the details to add a new team member</p>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { Link, useLocation, useNavigate } from "react-router-dom";
|
|||||||
import { createPageUrl } from "@/utils";
|
import { createPageUrl } from "@/utils";
|
||||||
import { base44 } from "@/api/base44Client";
|
import { base44 } from "@/api/base44Client";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { auth } from "@/firebase";
|
||||||
|
import { signOut } from "firebase/auth";
|
||||||
import {
|
import {
|
||||||
Users, LayoutDashboard, UserPlus, Calendar, Briefcase, FileText,
|
Users, LayoutDashboard, UserPlus, Calendar, Briefcase, FileText,
|
||||||
DollarSign, Award, HelpCircle, BarChart3, Activity, Menu, MessageSquare,
|
DollarSign, Award, HelpCircle, BarChart3, Activity, Menu, MessageSquare,
|
||||||
@@ -279,7 +281,7 @@ export default function Layout({ children }) {
|
|||||||
const userInitial = userName.charAt(0).toUpperCase();
|
const userInitial = userName.charAt(0).toUpperCase();
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
base44.auth.logout();
|
signOut(auth);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
@@ -482,6 +484,10 @@ export default function Layout({ children }) {
|
|||||||
<User className="w-4 h-4 mr-2" />My Profile
|
<User className="w-4 h-4 mr-2" />My Profile
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem onClick={handleLogout} className="text-red-600 focus:text-red-600">
|
||||||
|
<LogOut className="w-4 h-4 mr-2" />
|
||||||
|
<span>Logout</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
86
frontend-web/src/pages/Login.jsx
Normal file
86
frontend-web/src/pages/Login.jsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useNavigate, Link } from "react-router-dom";
|
||||||
|
import { signInWithEmailAndPassword } from "firebase/auth";
|
||||||
|
import { auth } from "@/firebase";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleLogin = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
if (!email || !password) {
|
||||||
|
setError("Email and password are required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await signInWithEmailAndPassword(auth, email, password);
|
||||||
|
navigate("/");
|
||||||
|
} catch (error) {
|
||||||
|
setError("Invalid credentials. Please try again.");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100">
|
||||||
|
<Card className="w-[350px]">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Login</CardTitle>
|
||||||
|
<CardDescription>Enter your credentials to access your account.</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={handleLogin}>
|
||||||
|
<div className="grid w-full items-center gap-4">
|
||||||
|
<div className="flex flex-col space-y-1.5">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col space-y-1.5">
|
||||||
|
<Label htmlFor="password">Password</Label>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{error && <p className="text-red-500 text-sm">{error}</p>}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="flex-col">
|
||||||
|
<Button onClick={handleLogin} disabled={loading} className="w-full">
|
||||||
|
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : "Login"}
|
||||||
|
</Button>
|
||||||
|
<p className="mt-4 text-sm text-slate-600">
|
||||||
|
Don't have an account?{" "}
|
||||||
|
<Link to="/register" className="text-blue-600 hover:underline">
|
||||||
|
Register
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
128
frontend-web/src/pages/Register.jsx
Normal file
128
frontend-web/src/pages/Register.jsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useNavigate, Link } from "react-router-dom";
|
||||||
|
import { createUserWithEmailAndPassword } from "firebase/auth";
|
||||||
|
import { auth } from "@/firebase";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
|
export default function Register() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const validatePassword = (password) => {
|
||||||
|
if (password.length < 6) {
|
||||||
|
return "Password must be at least 6 characters long.";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateEmail = (email) => {
|
||||||
|
const re =
|
||||||
|
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
if (!re.test(String(email).toLowerCase())) {
|
||||||
|
return "Invalid email address.";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRegister = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
if (!email || !password) {
|
||||||
|
setError("Email and password are required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailError = validateEmail(email);
|
||||||
|
if (emailError) {
|
||||||
|
setError(emailError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordError = validatePassword(password);
|
||||||
|
if (passwordError) {
|
||||||
|
setError(passwordError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await createUserWithEmailAndPassword(auth, email, password);
|
||||||
|
navigate("/");
|
||||||
|
} catch (error) {
|
||||||
|
setError(error.message || "Something went wrong. Please try again.");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100">
|
||||||
|
<Card className="w-[350px]">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Register</CardTitle>
|
||||||
|
<CardDescription>Create a new account.</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={handleRegister}>
|
||||||
|
<div className="grid w-full items-center gap-4">
|
||||||
|
<div className="flex flex-col space-y-1.5">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
autoComplete="email"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col space-y-1.5">
|
||||||
|
<Label htmlFor="password">Password</Label>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
autoComplete="new-password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && <p className="text-red-500 text-sm">{error}</p>}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
|
||||||
|
<CardFooter className="flex-col">
|
||||||
|
<Button onClick={handleRegister} disabled={loading} className="w-full">
|
||||||
|
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : "Register"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<p className="mt-4 text-sm text-slate-600">
|
||||||
|
Already have an account?{" "}
|
||||||
|
<Link to="/login" className="text-blue-600 hover:underline">
|
||||||
|
Login
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { base44 } from "@/api/base44Client";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { createPageUrl } from "@/utils";
|
import { createPageUrl } from "@/utils";
|
||||||
|
import { krowSDK } from "@/api/krowSDK";
|
||||||
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { UserPlus, Users, LayoutGrid, List as ListIcon, Phone, MapPin, Calendar, Star } from "lucide-react";
|
import { UserPlus, Users, LayoutGrid, List as ListIcon, Phone, MapPin, Calendar, Star } from "lucide-react";
|
||||||
import FilterBar from "@/components/staff/FilterBar";
|
import FilterBar from "@/components/staff/FilterBar";
|
||||||
import StaffCard from "@/components/staff/StaffCard";
|
|
||||||
import EmployeeCard from "@/components/staff/EmployeeCard";
|
import EmployeeCard from "@/components/staff/EmployeeCard";
|
||||||
import PageHeader from "@/components/common/PageHeader";
|
import PageHeader from "@/components/common/PageHeader";
|
||||||
|
|
||||||
@@ -18,48 +18,59 @@ export default function StaffDirectory() {
|
|||||||
const [locationFilter, setLocationFilter] = useState("all");
|
const [locationFilter, setLocationFilter] = useState("all");
|
||||||
const [viewMode, setViewMode] = useState("grid"); // "grid" or "list"
|
const [viewMode, setViewMode] = useState("grid"); // "grid" or "list"
|
||||||
|
|
||||||
const { data: user } = useQuery({
|
const { user: authUser } = useAuth(); // Firebase auth user
|
||||||
queryKey: ['current-user'],
|
|
||||||
queryFn: () => base44.auth.me(),
|
const { data: krowUser, isLoading: isLoadingUser } = useQuery({
|
||||||
|
queryKey: ['krow-user', authUser?.uid],
|
||||||
|
queryFn: () => krowSDK.entities.User.get({ id: authUser.uid }), // Changed from .filter() to .get()
|
||||||
|
enabled: !!authUser?.uid,
|
||||||
|
select: (response) => response?.data?.user, // Adjusted to get single user object
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: staff, isLoading } = useQuery({
|
const { data: staff, isLoading: isLoadingStaff } = useQuery({
|
||||||
queryKey: ['staff'],
|
queryKey: ['staff'],
|
||||||
queryFn: () => base44.entities.Staff.list('-created_date'),
|
queryFn: () => krowSDK.entities.Staff.list(),
|
||||||
initialData: [],
|
initialData: [],
|
||||||
|
select: (response) => {
|
||||||
|
// The API returns { data: { staffs: [...] } }, so we need to access the nested array.
|
||||||
|
if (response && response.data && Array.isArray(response.data.staffs)) {
|
||||||
|
return response.data.staffs;
|
||||||
|
}
|
||||||
|
return []; // Return empty array if the structure is not as expected.
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: events } = useQuery({
|
const { data: events } = useQuery({
|
||||||
queryKey: ['events-for-staff-filter'],
|
queryKey: ['events-for-staff-filter'],
|
||||||
queryFn: () => base44.entities.Event.list(),
|
queryFn: () => krowSDK.entities.Event.list(),
|
||||||
initialData: [],
|
initialData: { data: [] },
|
||||||
enabled: !!user
|
enabled: !!krowUser,
|
||||||
|
select: (data) => data.data || [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const visibleStaff = React.useMemo(() => {
|
const visibleStaff = React.useMemo(() => {
|
||||||
const userRole = user?.user_role || user?.role;
|
if (!krowUser || !staff) return [];
|
||||||
|
const userRole = krowUser.user_role || krowUser.role;
|
||||||
|
|
||||||
if (['admin', 'procurement'].includes(userRole)) {
|
if (['admin', 'procurement', 'operator', 'sector'].includes(userRole.toLowerCase())) {
|
||||||
return staff;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (['operator', 'sector'].includes(userRole)) {
|
|
||||||
return staff;
|
return staff;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userRole === 'vendor') {
|
if (userRole === 'vendor') {
|
||||||
return staff.filter(s =>
|
return staff.filter(s =>
|
||||||
s.vendor_id === user?.id ||
|
s.vendor_id === krowUser.id ||
|
||||||
s.vendor_name === user?.company_name ||
|
s.vendor_name === krowUser.company_name ||
|
||||||
s.created_by === user?.email
|
//s.created_by === krowUser.email
|
||||||
|
e.created_by === krowUser.id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userRole === 'client') {
|
if (userRole === 'client') {
|
||||||
const clientEvents = events.filter(e =>
|
const clientEvents = events.filter(e =>
|
||||||
e.client_email === user?.email ||
|
e.client_email === krowUser.email ||
|
||||||
e.business_name === user?.company_name ||
|
e.business_name === krowUser.company_name ||
|
||||||
e.created_by === user?.email
|
//e.created_by === krowUser.email
|
||||||
|
e.created_by === krowUser.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const assignedStaffIds = new Set();
|
const assignedStaffIds = new Set();
|
||||||
@@ -81,7 +92,7 @@ export default function StaffDirectory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return staff;
|
return staff;
|
||||||
}, [staff, user, events]);
|
}, [staff, krowUser, events]);
|
||||||
|
|
||||||
const uniqueDepartments = [...new Set(visibleStaff.map(s => s.department).filter(Boolean))];
|
const uniqueDepartments = [...new Set(visibleStaff.map(s => s.department).filter(Boolean))];
|
||||||
const uniqueLocations = [...new Set(visibleStaff.map(s => s.hub_location).filter(Boolean))];
|
const uniqueLocations = [...new Set(visibleStaff.map(s => s.hub_location).filter(Boolean))];
|
||||||
@@ -98,10 +109,11 @@ export default function StaffDirectory() {
|
|||||||
return matchesSearch && matchesDepartment && matchesLocation;
|
return matchesSearch && matchesDepartment && matchesLocation;
|
||||||
});
|
});
|
||||||
|
|
||||||
const canAddStaff = ['admin', 'procurement', 'operator', 'sector', 'vendor'].includes(user?.user_role || user?.role);
|
const canAddStaff = krowUser && ['admin', 'procurement', 'operator', 'sector', 'vendor'].includes((krowUser.user_role || krowUser.role || '').toLowerCase());
|
||||||
|
const isLoading = isLoadingStaff || isLoadingUser;
|
||||||
|
|
||||||
const getPageTitle = () => {
|
const getPageTitle = () => {
|
||||||
const userRole = user?.user_role || user?.role;
|
const userRole = krowUser?.user_role || krowUser?.role;
|
||||||
if (userRole === 'vendor') return "My Staff Directory";
|
if (userRole === 'vendor') return "My Staff Directory";
|
||||||
if (userRole === 'client') return "Event Staff Directory";
|
if (userRole === 'client') return "Event Staff Directory";
|
||||||
if (userRole === 'workforce') return "Team Directory";
|
if (userRole === 'workforce') return "Team Directory";
|
||||||
@@ -109,7 +121,7 @@ export default function StaffDirectory() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getPageSubtitle = () => {
|
const getPageSubtitle = () => {
|
||||||
const userRole = user?.user_role || user?.role;
|
const userRole = krowUser?.user_role || krowUser?.role;
|
||||||
if (userRole === 'vendor') return `${filteredStaff.length} of your staff members`;
|
if (userRole === 'vendor') return `${filteredStaff.length} of your staff members`;
|
||||||
if (userRole === 'client') return `${filteredStaff.length} staff assigned to your events`;
|
if (userRole === 'client') return `${filteredStaff.length} staff assigned to your events`;
|
||||||
if (userRole === 'workforce') return `${filteredStaff.length} team members`;
|
if (userRole === 'workforce') return `${filteredStaff.length} team members`;
|
||||||
|
|||||||
@@ -1,477 +1,267 @@
|
|||||||
import Layout from "./Layout.jsx";
|
|
||||||
|
|
||||||
import Dashboard from "./Dashboard";
|
|
||||||
|
|
||||||
import StaffDirectory from "./StaffDirectory";
|
|
||||||
|
|
||||||
import AddStaff from "./AddStaff";
|
|
||||||
|
|
||||||
import EditStaff from "./EditStaff";
|
|
||||||
|
|
||||||
import Events from "./Events";
|
|
||||||
|
|
||||||
import CreateEvent from "./CreateEvent";
|
|
||||||
|
|
||||||
import EditEvent from "./EditEvent";
|
|
||||||
|
|
||||||
import EventDetail from "./EventDetail";
|
|
||||||
|
|
||||||
import Business from "./Business";
|
|
||||||
|
|
||||||
import Invoices from "./Invoices";
|
|
||||||
|
|
||||||
import Payroll from "./Payroll";
|
|
||||||
|
|
||||||
import Certification from "./Certification";
|
|
||||||
|
|
||||||
import Support from "./Support";
|
|
||||||
|
|
||||||
import Reports from "./Reports";
|
|
||||||
|
|
||||||
import Settings from "./Settings";
|
|
||||||
|
|
||||||
import ActivityLog from "./ActivityLog";
|
|
||||||
|
|
||||||
import AddBusiness from "./AddBusiness";
|
|
||||||
|
|
||||||
import EditBusiness from "./EditBusiness";
|
|
||||||
|
|
||||||
import ProcurementDashboard from "./ProcurementDashboard";
|
|
||||||
|
|
||||||
import OperatorDashboard from "./OperatorDashboard";
|
|
||||||
|
|
||||||
import VendorDashboard from "./VendorDashboard";
|
|
||||||
|
|
||||||
import WorkforceDashboard from "./WorkforceDashboard";
|
|
||||||
|
|
||||||
import Messages from "./Messages";
|
|
||||||
|
|
||||||
import ClientDashboard from "./ClientDashboard";
|
|
||||||
|
|
||||||
import Onboarding from "./Onboarding";
|
|
||||||
|
|
||||||
import ClientOrders from "./ClientOrders";
|
|
||||||
|
|
||||||
import ClientInvoices from "./ClientInvoices";
|
|
||||||
|
|
||||||
import VendorOrders from "./VendorOrders";
|
|
||||||
|
|
||||||
import VendorStaff from "./VendorStaff";
|
|
||||||
|
|
||||||
import VendorInvoices from "./VendorInvoices";
|
|
||||||
|
|
||||||
import VendorPerformance from "./VendorPerformance";
|
|
||||||
|
|
||||||
import WorkforceShifts from "./WorkforceShifts";
|
|
||||||
|
|
||||||
import WorkforceEarnings from "./WorkforceEarnings";
|
|
||||||
|
|
||||||
import WorkforceProfile from "./WorkforceProfile";
|
|
||||||
|
|
||||||
import UserManagement from "./UserManagement";
|
|
||||||
|
|
||||||
import Home from "./Home";
|
|
||||||
|
|
||||||
import VendorRateCard from "./VendorRateCard";
|
|
||||||
|
|
||||||
import Permissions from "./Permissions";
|
|
||||||
|
|
||||||
import WorkforceCompliance from "./WorkforceCompliance";
|
|
||||||
|
|
||||||
import Teams from "./Teams";
|
|
||||||
|
|
||||||
import CreateTeam from "./CreateTeam";
|
|
||||||
|
|
||||||
import TeamDetails from "./TeamDetails";
|
|
||||||
|
|
||||||
import VendorManagement from "./VendorManagement";
|
|
||||||
|
|
||||||
import PartnerManagement from "./PartnerManagement";
|
|
||||||
|
|
||||||
import EnterpriseManagement from "./EnterpriseManagement";
|
|
||||||
|
|
||||||
import VendorOnboarding from "./VendorOnboarding";
|
|
||||||
|
|
||||||
import SectorManagement from "./SectorManagement";
|
|
||||||
|
|
||||||
import AddEnterprise from "./AddEnterprise";
|
|
||||||
|
|
||||||
import AddSector from "./AddSector";
|
|
||||||
|
|
||||||
import AddPartner from "./AddPartner";
|
|
||||||
|
|
||||||
import EditVendor from "./EditVendor";
|
|
||||||
|
|
||||||
import SmartVendorOnboarding from "./SmartVendorOnboarding";
|
|
||||||
|
|
||||||
import InviteVendor from "./InviteVendor";
|
|
||||||
|
|
||||||
import VendorCompliance from "./VendorCompliance";
|
|
||||||
|
|
||||||
import EditPartner from "./EditPartner";
|
|
||||||
|
|
||||||
import EditSector from "./EditSector";
|
|
||||||
|
|
||||||
import EditEnterprise from "./EditEnterprise";
|
|
||||||
|
|
||||||
import VendorRates from "./VendorRates";
|
|
||||||
|
|
||||||
import VendorDocumentReview from "./VendorDocumentReview";
|
|
||||||
|
|
||||||
import VendorMarketplace from "./VendorMarketplace";
|
|
||||||
|
|
||||||
import RapidOrder from "./RapidOrder";
|
|
||||||
|
|
||||||
import SmartScheduler from "./SmartScheduler";
|
|
||||||
|
|
||||||
import StaffOnboarding from "./StaffOnboarding";
|
|
||||||
|
|
||||||
import NotificationSettings from "./NotificationSettings";
|
|
||||||
|
|
||||||
import TaskBoard from "./TaskBoard";
|
|
||||||
|
|
||||||
import InvoiceDetail from "./InvoiceDetail";
|
|
||||||
|
|
||||||
import InvoiceEditor from "./InvoiceEditor";
|
|
||||||
|
|
||||||
//import api-docs-raw from "./api-docs-raw";
|
|
||||||
|
|
||||||
import Tutorials from "./Tutorials";
|
|
||||||
|
|
||||||
import Schedule from "./Schedule";
|
|
||||||
|
|
||||||
import StaffAvailability from "./StaffAvailability";
|
|
||||||
|
|
||||||
import WorkerShiftProposals from "./WorkerShiftProposals";
|
|
||||||
|
|
||||||
import { BrowserRouter as Router, Route, Routes, useLocation } from 'react-router-dom';
|
import { BrowserRouter as Router, Route, Routes, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
// Auth components
|
||||||
|
import ProtectedRoute from '@/components/auth/ProtectedRoute';
|
||||||
|
import PublicRoute from '@/components/auth/PublicRoute';
|
||||||
|
|
||||||
|
// Layout and pages
|
||||||
|
import Layout from "./Layout.jsx";
|
||||||
|
import Home from "./Home";
|
||||||
|
import Login from "./Login";
|
||||||
|
import Register from "./Register";
|
||||||
|
import Dashboard from "./Dashboard";
|
||||||
|
import StaffDirectory from "./StaffDirectory";
|
||||||
|
import AddStaff from "./AddStaff";
|
||||||
|
import EditStaff from "./EditStaff";
|
||||||
|
import Events from "./Events";
|
||||||
|
import CreateEvent from "./CreateEvent";
|
||||||
|
import EditEvent from "./EditEvent";
|
||||||
|
import EventDetail from "./EventDetail";
|
||||||
|
import Business from "./Business";
|
||||||
|
import Invoices from "./Invoices";
|
||||||
|
import Payroll from "./Payroll";
|
||||||
|
import Certification from "./Certification";
|
||||||
|
import Support from "./Support";
|
||||||
|
import Reports from "./Reports";
|
||||||
|
import Settings from "./Settings";
|
||||||
|
import ActivityLog from "./ActivityLog";
|
||||||
|
import AddBusiness from "./AddBusiness";
|
||||||
|
import EditBusiness from "./EditBusiness";
|
||||||
|
import ProcurementDashboard from "./ProcurementDashboard";
|
||||||
|
import OperatorDashboard from "./OperatorDashboard";
|
||||||
|
import VendorDashboard from "./VendorDashboard";
|
||||||
|
import WorkforceDashboard from "./WorkforceDashboard";
|
||||||
|
import Messages from "./Messages";
|
||||||
|
import ClientDashboard from "./ClientDashboard";
|
||||||
|
import Onboarding from "./Onboarding";
|
||||||
|
import ClientOrders from "./ClientOrders";
|
||||||
|
import ClientInvoices from "./ClientInvoices";
|
||||||
|
import VendorOrders from "./VendorOrders";
|
||||||
|
import VendorStaff from "./VendorStaff";
|
||||||
|
import VendorInvoices from "./VendorInvoices";
|
||||||
|
import VendorPerformance from "./VendorPerformance";
|
||||||
|
import WorkforceShifts from "./WorkforceShifts";
|
||||||
|
import WorkforceEarnings from "./WorkforceEarnings";
|
||||||
|
import WorkforceProfile from "./WorkforceProfile";
|
||||||
|
import UserManagement from "./UserManagement";
|
||||||
|
import VendorRateCard from "./VendorRateCard";
|
||||||
|
import Permissions from "./Permissions";
|
||||||
|
import WorkforceCompliance from "./WorkforceCompliance";
|
||||||
|
import Teams from "./Teams";
|
||||||
|
import CreateTeam from "./CreateTeam";
|
||||||
|
import TeamDetails from "./TeamDetails";
|
||||||
|
import VendorManagement from "./VendorManagement";
|
||||||
|
import PartnerManagement from "./PartnerManagement";
|
||||||
|
import EnterpriseManagement from "./EnterpriseManagement";
|
||||||
|
import VendorOnboarding from "./VendorOnboarding";
|
||||||
|
import SectorManagement from "./SectorManagement";
|
||||||
|
import AddEnterprise from "./AddEnterprise";
|
||||||
|
import AddSector from "./AddSector";
|
||||||
|
import AddPartner from "./AddPartner";
|
||||||
|
import EditVendor from "./EditVendor";
|
||||||
|
import SmartVendorOnboarding from "./SmartVendorOnboarding";
|
||||||
|
import InviteVendor from "./InviteVendor";
|
||||||
|
import VendorCompliance from "./VendorCompliance";
|
||||||
|
import EditPartner from "./EditPartner";
|
||||||
|
import EditSector from "./EditSector";
|
||||||
|
import EditEnterprise from "./EditEnterprise";
|
||||||
|
import VendorRates from "./VendorRates";
|
||||||
|
import VendorDocumentReview from "./VendorDocumentReview";
|
||||||
|
import VendorMarketplace from "./VendorMarketplace";
|
||||||
|
import RapidOrder from "./RapidOrder";
|
||||||
|
import SmartScheduler from "./SmartScheduler";
|
||||||
|
import StaffOnboarding from "./StaffOnboarding";
|
||||||
|
import NotificationSettings from "./NotificationSettings";
|
||||||
|
import TaskBoard from "./TaskBoard";
|
||||||
|
import InvoiceDetail from "./InvoiceDetail";
|
||||||
|
import InvoiceEditor from "./InvoiceEditor";
|
||||||
|
import Tutorials from "./Tutorials";
|
||||||
|
import Schedule from "./Schedule";
|
||||||
|
import StaffAvailability from "./StaffAvailability";
|
||||||
|
import WorkerShiftProposals from "./WorkerShiftProposals";
|
||||||
|
|
||||||
const PAGES = {
|
const PAGES = {
|
||||||
|
Dashboard,
|
||||||
Dashboard: Dashboard,
|
StaffDirectory,
|
||||||
|
AddStaff,
|
||||||
StaffDirectory: StaffDirectory,
|
EditStaff,
|
||||||
|
Events,
|
||||||
AddStaff: AddStaff,
|
CreateEvent,
|
||||||
|
EditEvent,
|
||||||
EditStaff: EditStaff,
|
EventDetail,
|
||||||
|
Business,
|
||||||
Events: Events,
|
Invoices,
|
||||||
|
Payroll,
|
||||||
CreateEvent: CreateEvent,
|
Certification,
|
||||||
|
Support,
|
||||||
EditEvent: EditEvent,
|
Reports,
|
||||||
|
Settings,
|
||||||
EventDetail: EventDetail,
|
ActivityLog,
|
||||||
|
AddBusiness,
|
||||||
Business: Business,
|
EditBusiness,
|
||||||
|
ProcurementDashboard,
|
||||||
Invoices: Invoices,
|
OperatorDashboard,
|
||||||
|
VendorDashboard,
|
||||||
Payroll: Payroll,
|
WorkforceDashboard,
|
||||||
|
Messages,
|
||||||
Certification: Certification,
|
ClientDashboard,
|
||||||
|
Onboarding,
|
||||||
Support: Support,
|
ClientOrders,
|
||||||
|
ClientInvoices,
|
||||||
Reports: Reports,
|
VendorOrders,
|
||||||
|
VendorStaff,
|
||||||
Settings: Settings,
|
VendorInvoices,
|
||||||
|
VendorPerformance,
|
||||||
ActivityLog: ActivityLog,
|
WorkforceShifts,
|
||||||
|
WorkforceEarnings,
|
||||||
AddBusiness: AddBusiness,
|
WorkforceProfile,
|
||||||
|
UserManagement,
|
||||||
EditBusiness: EditBusiness,
|
Home,
|
||||||
|
VendorRateCard,
|
||||||
ProcurementDashboard: ProcurementDashboard,
|
Permissions,
|
||||||
|
WorkforceCompliance,
|
||||||
OperatorDashboard: OperatorDashboard,
|
Teams,
|
||||||
|
CreateTeam,
|
||||||
VendorDashboard: VendorDashboard,
|
TeamDetails,
|
||||||
|
VendorManagement,
|
||||||
WorkforceDashboard: WorkforceDashboard,
|
PartnerManagement,
|
||||||
|
EnterpriseManagement,
|
||||||
Messages: Messages,
|
VendorOnboarding,
|
||||||
|
SectorManagement,
|
||||||
ClientDashboard: ClientDashboard,
|
AddEnterprise,
|
||||||
|
AddSector,
|
||||||
Onboarding: Onboarding,
|
AddPartner,
|
||||||
|
EditVendor,
|
||||||
ClientOrders: ClientOrders,
|
SmartVendorOnboarding,
|
||||||
|
InviteVendor,
|
||||||
ClientInvoices: ClientInvoices,
|
VendorCompliance,
|
||||||
|
EditPartner,
|
||||||
VendorOrders: VendorOrders,
|
EditSector,
|
||||||
|
EditEnterprise,
|
||||||
VendorStaff: VendorStaff,
|
VendorRates,
|
||||||
|
VendorDocumentReview,
|
||||||
VendorInvoices: VendorInvoices,
|
VendorMarketplace,
|
||||||
|
RapidOrder,
|
||||||
VendorPerformance: VendorPerformance,
|
SmartScheduler,
|
||||||
|
StaffOnboarding,
|
||||||
WorkforceShifts: WorkforceShifts,
|
NotificationSettings,
|
||||||
|
TaskBoard,
|
||||||
WorkforceEarnings: WorkforceEarnings,
|
InvoiceDetail,
|
||||||
|
InvoiceEditor,
|
||||||
WorkforceProfile: WorkforceProfile,
|
Tutorials,
|
||||||
|
Schedule,
|
||||||
UserManagement: UserManagement,
|
StaffAvailability,
|
||||||
|
WorkerShiftProposals,
|
||||||
Home: Home,
|
};
|
||||||
|
|
||||||
VendorRateCard: VendorRateCard,
|
|
||||||
|
|
||||||
Permissions: Permissions,
|
|
||||||
|
|
||||||
WorkforceCompliance: WorkforceCompliance,
|
|
||||||
|
|
||||||
Teams: Teams,
|
|
||||||
|
|
||||||
CreateTeam: CreateTeam,
|
|
||||||
|
|
||||||
TeamDetails: TeamDetails,
|
|
||||||
|
|
||||||
VendorManagement: VendorManagement,
|
|
||||||
|
|
||||||
PartnerManagement: PartnerManagement,
|
|
||||||
|
|
||||||
EnterpriseManagement: EnterpriseManagement,
|
|
||||||
|
|
||||||
VendorOnboarding: VendorOnboarding,
|
|
||||||
|
|
||||||
SectorManagement: SectorManagement,
|
|
||||||
|
|
||||||
AddEnterprise: AddEnterprise,
|
|
||||||
|
|
||||||
AddSector: AddSector,
|
|
||||||
|
|
||||||
AddPartner: AddPartner,
|
|
||||||
|
|
||||||
EditVendor: EditVendor,
|
|
||||||
|
|
||||||
SmartVendorOnboarding: SmartVendorOnboarding,
|
|
||||||
|
|
||||||
InviteVendor: InviteVendor,
|
|
||||||
|
|
||||||
VendorCompliance: VendorCompliance,
|
|
||||||
|
|
||||||
EditPartner: EditPartner,
|
|
||||||
|
|
||||||
EditSector: EditSector,
|
|
||||||
|
|
||||||
EditEnterprise: EditEnterprise,
|
|
||||||
|
|
||||||
VendorRates: VendorRates,
|
|
||||||
|
|
||||||
VendorDocumentReview: VendorDocumentReview,
|
|
||||||
|
|
||||||
VendorMarketplace: VendorMarketplace,
|
|
||||||
|
|
||||||
RapidOrder: RapidOrder,
|
|
||||||
|
|
||||||
SmartScheduler: SmartScheduler,
|
|
||||||
|
|
||||||
StaffOnboarding: StaffOnboarding,
|
|
||||||
|
|
||||||
NotificationSettings: NotificationSettings,
|
|
||||||
|
|
||||||
TaskBoard: TaskBoard,
|
|
||||||
|
|
||||||
InvoiceDetail: InvoiceDetail,
|
|
||||||
|
|
||||||
InvoiceEditor: InvoiceEditor,
|
|
||||||
|
|
||||||
//api-docs-raw: api-docs-raw,
|
|
||||||
|
|
||||||
Tutorials: Tutorials,
|
|
||||||
|
|
||||||
Schedule: Schedule,
|
|
||||||
|
|
||||||
StaffAvailability: StaffAvailability,
|
|
||||||
|
|
||||||
WorkerShiftProposals: WorkerShiftProposals,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getCurrentPage(url) {
|
function _getCurrentPage(url) {
|
||||||
if (url.endsWith('/')) {
|
if (url.endsWith('/')) url = url.slice(0, -1);
|
||||||
url = url.slice(0, -1);
|
let last = url.split('/').pop();
|
||||||
}
|
if (last.includes('?')) last = last.split('?')[0];
|
||||||
let urlLastPart = url.split('/').pop();
|
const pageName = Object.keys(PAGES).find(p => p.toLowerCase() === last.toLowerCase());
|
||||||
if (urlLastPart.includes('?')) {
|
return pageName || 'Home'; // Default to Home
|
||||||
urlLastPart = urlLastPart.split('?')[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageName = Object.keys(PAGES).find(page => page.toLowerCase() === urlLastPart.toLowerCase());
|
|
||||||
return pageName || Object.keys(PAGES)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a wrapper component that uses useLocation inside the Router context
|
function AppRoutes() {
|
||||||
function PagesContent() {
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const currentPage = _getCurrentPage(location.pathname);
|
const currentPage = _getCurrentPage(location.pathname);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Routes>
|
||||||
|
{/* Public Routes */}
|
||||||
|
<Route path="/login" element={<PublicRoute><Login /></PublicRoute>} />
|
||||||
|
<Route path="/register" element={<PublicRoute><Register /></PublicRoute>} />
|
||||||
|
|
||||||
|
{/* Private Routes */}
|
||||||
|
<Route path="/*" element={
|
||||||
|
<ProtectedRoute>
|
||||||
<Layout currentPageName={currentPage}>
|
<Layout currentPageName={currentPage}>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="/" element={<Dashboard />} />
|
|
||||||
|
|
||||||
|
|
||||||
<Route path="/Dashboard" element={<Dashboard />} />
|
<Route path="/Dashboard" element={<Dashboard />} />
|
||||||
|
|
||||||
<Route path="/StaffDirectory" element={<StaffDirectory />} />
|
<Route path="/StaffDirectory" element={<StaffDirectory />} />
|
||||||
|
|
||||||
<Route path="/AddStaff" element={<AddStaff />} />
|
<Route path="/AddStaff" element={<AddStaff />} />
|
||||||
|
|
||||||
<Route path="/EditStaff" element={<EditStaff />} />
|
<Route path="/EditStaff" element={<EditStaff />} />
|
||||||
|
|
||||||
<Route path="/Events" element={<Events />} />
|
<Route path="/Events" element={<Events />} />
|
||||||
|
|
||||||
<Route path="/CreateEvent" element={<CreateEvent />} />
|
<Route path="/CreateEvent" element={<CreateEvent />} />
|
||||||
|
|
||||||
<Route path="/EditEvent" element={<EditEvent />} />
|
<Route path="/EditEvent" element={<EditEvent />} />
|
||||||
|
|
||||||
<Route path="/EventDetail" element={<EventDetail />} />
|
<Route path="/EventDetail" element={<EventDetail />} />
|
||||||
|
|
||||||
<Route path="/Business" element={<Business />} />
|
<Route path="/Business" element={<Business />} />
|
||||||
|
|
||||||
<Route path="/Invoices" element={<Invoices />} />
|
<Route path="/Invoices" element={<Invoices />} />
|
||||||
|
|
||||||
<Route path="/Payroll" element={<Payroll />} />
|
<Route path="/Payroll" element={<Payroll />} />
|
||||||
|
|
||||||
<Route path="/Certification" element={<Certification />} />
|
<Route path="/Certification" element={<Certification />} />
|
||||||
|
|
||||||
<Route path="/Support" element={<Support />} />
|
<Route path="/Support" element={<Support />} />
|
||||||
|
|
||||||
<Route path="/Reports" element={<Reports />} />
|
<Route path="/Reports" element={<Reports />} />
|
||||||
|
|
||||||
<Route path="/Settings" element={<Settings />} />
|
<Route path="/Settings" element={<Settings />} />
|
||||||
|
|
||||||
<Route path="/ActivityLog" element={<ActivityLog />} />
|
<Route path="/ActivityLog" element={<ActivityLog />} />
|
||||||
|
|
||||||
<Route path="/AddBusiness" element={<AddBusiness />} />
|
<Route path="/AddBusiness" element={<AddBusiness />} />
|
||||||
|
|
||||||
<Route path="/EditBusiness" element={<EditBusiness />} />
|
<Route path="/EditBusiness" element={<EditBusiness />} />
|
||||||
|
|
||||||
<Route path="/ProcurementDashboard" element={<ProcurementDashboard />} />
|
<Route path="/ProcurementDashboard" element={<ProcurementDashboard />} />
|
||||||
|
|
||||||
<Route path="/OperatorDashboard" element={<OperatorDashboard />} />
|
<Route path="/OperatorDashboard" element={<OperatorDashboard />} />
|
||||||
|
|
||||||
<Route path="/VendorDashboard" element={<VendorDashboard />} />
|
<Route path="/VendorDashboard" element={<VendorDashboard />} />
|
||||||
|
|
||||||
<Route path="/WorkforceDashboard" element={<WorkforceDashboard />} />
|
<Route path="/WorkforceDashboard" element={<WorkforceDashboard />} />
|
||||||
|
|
||||||
<Route path="/Messages" element={<Messages />} />
|
<Route path="/Messages" element={<Messages />} />
|
||||||
|
|
||||||
<Route path="/ClientDashboard" element={<ClientDashboard />} />
|
<Route path="/ClientDashboard" element={<ClientDashboard />} />
|
||||||
|
|
||||||
<Route path="/Onboarding" element={<Onboarding />} />
|
<Route path="/Onboarding" element={<Onboarding />} />
|
||||||
|
|
||||||
<Route path="/ClientOrders" element={<ClientOrders />} />
|
<Route path="/ClientOrders" element={<ClientOrders />} />
|
||||||
|
|
||||||
<Route path="/ClientInvoices" element={<ClientInvoices />} />
|
<Route path="/ClientInvoices" element={<ClientInvoices />} />
|
||||||
|
|
||||||
<Route path="/VendorOrders" element={<VendorOrders />} />
|
<Route path="/VendorOrders" element={<VendorOrders />} />
|
||||||
|
|
||||||
<Route path="/VendorStaff" element={<VendorStaff />} />
|
<Route path="/VendorStaff" element={<VendorStaff />} />
|
||||||
|
|
||||||
<Route path="/VendorInvoices" element={<VendorInvoices />} />
|
<Route path="/VendorInvoices" element={<VendorInvoices />} />
|
||||||
|
|
||||||
<Route path="/VendorPerformance" element={<VendorPerformance />} />
|
<Route path="/VendorPerformance" element={<VendorPerformance />} />
|
||||||
|
|
||||||
<Route path="/WorkforceShifts" element={<WorkforceShifts />} />
|
<Route path="/WorkforceShifts" element={<WorkforceShifts />} />
|
||||||
|
|
||||||
<Route path="/WorkforceEarnings" element={<WorkforceEarnings />} />
|
<Route path="/WorkforceEarnings" element={<WorkforceEarnings />} />
|
||||||
|
|
||||||
<Route path="/WorkforceProfile" element={<WorkforceProfile />} />
|
<Route path="/WorkforceProfile" element={<WorkforceProfile />} />
|
||||||
|
|
||||||
<Route path="/UserManagement" element={<UserManagement />} />
|
<Route path="/UserManagement" element={<UserManagement />} />
|
||||||
|
|
||||||
<Route path="/Home" element={<Home />} />
|
<Route path="/Home" element={<Home />} />
|
||||||
|
|
||||||
<Route path="/VendorRateCard" element={<VendorRateCard />} />
|
<Route path="/VendorRateCard" element={<VendorRateCard />} />
|
||||||
|
|
||||||
<Route path="/Permissions" element={<Permissions />} />
|
<Route path="/Permissions" element={<Permissions />} />
|
||||||
|
|
||||||
<Route path="/WorkforceCompliance" element={<WorkforceCompliance />} />
|
<Route path="/WorkforceCompliance" element={<WorkforceCompliance />} />
|
||||||
|
|
||||||
<Route path="/Teams" element={<Teams />} />
|
<Route path="/Teams" element={<Teams />} />
|
||||||
|
|
||||||
<Route path="/CreateTeam" element={<CreateTeam />} />
|
<Route path="/CreateTeam" element={<CreateTeam />} />
|
||||||
|
|
||||||
<Route path="/TeamDetails" element={<TeamDetails />} />
|
<Route path="/TeamDetails" element={<TeamDetails />} />
|
||||||
|
|
||||||
<Route path="/VendorManagement" element={<VendorManagement />} />
|
<Route path="/VendorManagement" element={<VendorManagement />} />
|
||||||
|
|
||||||
<Route path="/PartnerManagement" element={<PartnerManagement />} />
|
<Route path="/PartnerManagement" element={<PartnerManagement />} />
|
||||||
|
|
||||||
<Route path="/EnterpriseManagement" element={<EnterpriseManagement />} />
|
<Route path="/EnterpriseManagement" element={<EnterpriseManagement />} />
|
||||||
|
|
||||||
<Route path="/VendorOnboarding" element={<VendorOnboarding />} />
|
<Route path="/VendorOnboarding" element={<VendorOnboarding />} />
|
||||||
|
|
||||||
<Route path="/SectorManagement" element={<SectorManagement />} />
|
<Route path="/SectorManagement" element={<SectorManagement />} />
|
||||||
|
|
||||||
<Route path="/AddEnterprise" element={<AddEnterprise />} />
|
<Route path="/AddEnterprise" element={<AddEnterprise />} />
|
||||||
|
|
||||||
<Route path="/AddSector" element={<AddSector />} />
|
<Route path="/AddSector" element={<AddSector />} />
|
||||||
|
|
||||||
<Route path="/AddPartner" element={<AddPartner />} />
|
<Route path="/AddPartner" element={<AddPartner />} />
|
||||||
|
|
||||||
<Route path="/EditVendor" element={<EditVendor />} />
|
<Route path="/EditVendor" element={<EditVendor />} />
|
||||||
|
|
||||||
<Route path="/SmartVendorOnboarding" element={<SmartVendorOnboarding />} />
|
<Route path="/SmartVendorOnboarding" element={<SmartVendorOnboarding />} />
|
||||||
|
|
||||||
<Route path="/InviteVendor" element={<InviteVendor />} />
|
<Route path="/InviteVendor" element={<InviteVendor />} />
|
||||||
|
|
||||||
<Route path="/VendorCompliance" element={<VendorCompliance />} />
|
<Route path="/VendorCompliance" element={<VendorCompliance />} />
|
||||||
|
|
||||||
<Route path="/EditPartner" element={<EditPartner />} />
|
<Route path="/EditPartner" element={<EditPartner />} />
|
||||||
|
|
||||||
<Route path="/EditSector" element={<EditSector />} />
|
<Route path="/EditSector" element={<EditSector />} />
|
||||||
|
|
||||||
<Route path="/EditEnterprise" element={<EditEnterprise />} />
|
<Route path="/EditEnterprise" element={<EditEnterprise />} />
|
||||||
|
|
||||||
<Route path="/VendorRates" element={<VendorRates />} />
|
<Route path="/VendorRates" element={<VendorRates />} />
|
||||||
|
|
||||||
<Route path="/VendorDocumentReview" element={<VendorDocumentReview />} />
|
<Route path="/VendorDocumentReview" element={<VendorDocumentReview />} />
|
||||||
|
|
||||||
<Route path="/VendorMarketplace" element={<VendorMarketplace />} />
|
<Route path="/VendorMarketplace" element={<VendorMarketplace />} />
|
||||||
|
|
||||||
<Route path="/RapidOrder" element={<RapidOrder />} />
|
<Route path="/RapidOrder" element={<RapidOrder />} />
|
||||||
|
|
||||||
<Route path="/SmartScheduler" element={<SmartScheduler />} />
|
<Route path="/SmartScheduler" element={<SmartScheduler />} />
|
||||||
|
|
||||||
<Route path="/StaffOnboarding" element={<StaffOnboarding />} />
|
<Route path="/StaffOnboarding" element={<StaffOnboarding />} />
|
||||||
|
|
||||||
<Route path="/NotificationSettings" element={<NotificationSettings />} />
|
<Route path="/NotificationSettings" element={<NotificationSettings />} />
|
||||||
|
|
||||||
<Route path="/TaskBoard" element={<TaskBoard />} />
|
<Route path="/TaskBoard" element={<TaskBoard />} />
|
||||||
|
|
||||||
<Route path="/InvoiceDetail" element={<InvoiceDetail />} />
|
<Route path="/InvoiceDetail" element={<InvoiceDetail />} />
|
||||||
|
|
||||||
<Route path="/InvoiceEditor" element={<InvoiceEditor />} />
|
<Route path="/InvoiceEditor" element={<InvoiceEditor />} />
|
||||||
|
|
||||||
<Route path="/api-docs-raw" element={<api-docs-raw />} />
|
|
||||||
|
|
||||||
<Route path="/Tutorials" element={<Tutorials />} />
|
<Route path="/Tutorials" element={<Tutorials />} />
|
||||||
|
|
||||||
<Route path="/Schedule" element={<Schedule />} />
|
<Route path="/Schedule" element={<Schedule />} />
|
||||||
|
|
||||||
<Route path="/StaffAvailability" element={<StaffAvailability />} />
|
<Route path="/StaffAvailability" element={<StaffAvailability />} />
|
||||||
|
|
||||||
<Route path="/WorkerShiftProposals" element={<WorkerShiftProposals />} />
|
<Route path="/WorkerShiftProposals" element={<WorkerShiftProposals />} />
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
</Routes>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Pages() {
|
export default function Pages() {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<PagesContent />
|
<AppRoutes />
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -139,19 +139,30 @@ entityNames.forEach(entityName => {
|
|||||||
});*/
|
});*/
|
||||||
|
|
||||||
const dataconnectEntityConfig = {
|
const dataconnectEntityConfig = {
|
||||||
|
User: {
|
||||||
|
list: 'listUsers',
|
||||||
|
get: 'getUserById',
|
||||||
|
create: 'createUser',
|
||||||
|
update: 'updateUser',
|
||||||
|
delete: 'deleteUser',
|
||||||
|
filter: 'filterUsers',
|
||||||
|
},
|
||||||
Event: {
|
Event: {
|
||||||
list: 'listEvents',
|
list: 'listEvents',
|
||||||
create: 'createEvent',
|
create: 'createEvent',
|
||||||
// get: 'getEvent',
|
get: 'getEventById',
|
||||||
// update: 'updateEvent',
|
update: 'updateEvent',
|
||||||
// delete: 'deleteEvent',
|
delete: 'deleteEvent',
|
||||||
// filter: 'filterEvents',
|
filter: 'filterEvents',
|
||||||
},
|
},
|
||||||
|
|
||||||
Staff: {
|
Staff: {
|
||||||
list: 'listStaff',
|
list: 'listStaff',
|
||||||
create: 'createStaff',
|
create: 'createStaff',
|
||||||
|
get: 'getStaffById',
|
||||||
|
update: 'updateStaff',
|
||||||
|
delete: 'deleteStaff',
|
||||||
|
filter: 'filterStaff',
|
||||||
},
|
},
|
||||||
|
|
||||||
Vendor: {
|
Vendor: {
|
||||||
|
|||||||
Reference in New Issue
Block a user