fix(ui): Team member form and status badge improvements

1. Status badge: Render HTML in DataTable cell using {@html}
2. Form alignment: Consistent horizontal layout for all fields
   - Labels on left (w-28), inputs on right (flex-1)
   - Added dollar sign prefix for hourly rate
   - Wider modal (max-w-lg)
3. API docs: Regenerated with scribe:generate
This commit is contained in:
2026-02-18 23:03:55 -05:00
parent a8eecc7900
commit 32b524bff0
11 changed files with 2894 additions and 70 deletions

View File

@@ -1,4 +1,4 @@
# GENERATED. YOU SHOULDN'T MODIFY OR DELETE THIS FILE.
# Scribe uses this file to know when you change something manually in your docs.
.scribe/intro.md=63d14186b9cbbb0a80ee87cd913db091
.scribe/auth.md=5c5a140c89034600ae349aede2a22ec8
.scribe/intro.md=4bf90470e636417926ae5d9227747d45
.scribe/auth.md=9bee2b1ef8a238b2e58613fa636d5f39

View File

@@ -1,7 +1,3 @@
# Authenticating requests
To authenticate requests, include an **`Authorization`** header with the value **`"Bearer Bearer {token}"`**.
All authenticated endpoints are marked with a `requires authentication` badge in the documentation below.
Get tokens from `POST /api/auth/login`, send access token as `Bearer {token}`, and renew with `POST /api/auth/refresh` before access token expiry.
This API is not authenticated.

View File

@@ -0,0 +1,214 @@
## Autogenerated by Scribe. DO NOT MODIFY.
name: Authentication
description: |-
Endpoints for JWT authentication and session lifecycle.
endpoints:
-
custom: []
httpMethods:
- POST
uri: api/auth/login
metadata:
custom: []
groupName: Authentication
groupDescription: |-
Endpoints for JWT authentication and session lifecycle.
subgroup: ''
subgroupDescription: ''
title: 'Login and get tokens'
description: 'Authenticate with email and password to receive an access token and refresh token.'
authenticated: false
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters: []
cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
bodyParameters:
email:
custom: []
name: email
description: 'User email address.'
required: true
example: user@example.com
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
password:
custom: []
name: password
description: 'User password.'
required: true
example: secret123
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanBodyParameters:
email: user@example.com
password: secret123
fileParameters: []
responses:
-
custom: []
status: 200
content: |-
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh_token": "abc123def456",
"token_type": "bearer",
"expires_in": 3600,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Alice Johnson",
"email": "user@example.com",
"role": "manager"
}
}
headers: []
description: ''
-
custom: []
status: 401
content: '{"message":"Invalid credentials"}'
headers: []
description: ''
-
custom: []
status: 403
content: '{"message":"Account is inactive"}'
headers: []
description: ''
-
custom: []
status: 422
content: '{"errors":{"email":["The email field is required."],"password":["The password field is required."]}}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- POST
uri: api/auth/refresh
metadata:
custom: []
groupName: Authentication
groupDescription: |-
Endpoints for JWT authentication and session lifecycle.
subgroup: ''
subgroupDescription: ''
title: 'Refresh access token'
description: 'Exchange a valid refresh token for a new access token and refresh token pair.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters: []
cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
bodyParameters:
refresh_token:
custom: []
name: refresh_token
description: 'Refresh token returned by login.'
required: true
example: abc123def456
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanBodyParameters:
refresh_token: abc123def456
fileParameters: []
responses:
-
custom: []
status: 200
content: |-
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh_token": "newtoken123",
"token_type": "bearer",
"expires_in": 3600
}
headers: []
description: ''
-
custom: []
status: 401
content: '{"message":"Invalid or expired refresh token"}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- POST
uri: api/auth/logout
metadata:
custom: []
groupName: Authentication
groupDescription: |-
Endpoints for JWT authentication and session lifecycle.
subgroup: ''
subgroupDescription: ''
title: 'Logout current session'
description: 'Invalidate a refresh token and end the active authenticated session.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters: []
cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
bodyParameters:
refresh_token:
custom: []
name: refresh_token
description: 'Optional refresh token to invalidate immediately.'
required: false
example: abc123def456
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanBodyParameters:
refresh_token: abc123def456
fileParameters: []
responses:
-
custom: []
status: 200
content: '{"message":"Logged out successfully"}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null

View File

@@ -0,0 +1,443 @@
## Autogenerated by Scribe. DO NOT MODIFY.
name: 'Team Members'
description: |-
Endpoints for managing team members.
endpoints:
-
custom: []
httpMethods:
- GET
uri: api/team-members
metadata:
custom: []
groupName: 'Team Members'
groupDescription: |-
Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
title: 'List all team members'
description: 'Get a list of all team members with optional filtering by active status.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters: []
cleanUrlParameters: []
queryParameters:
active:
custom: []
name: active
description: 'Filter by active status.'
required: false
example: true
type: boolean
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanQueryParameters:
active: true
bodyParameters: []
cleanBodyParameters: []
fileParameters: []
responses:
-
custom: []
status: 200
content: |-
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"role_id": 1,
"role": {
"id": 1,
"name": "Backend Developer"
},
"hourly_rate": "150.00",
"active": true,
"created_at": "2024-01-15T10:00:00.000000Z",
"updated_at": "2024-01-15T10:00:00.000000Z"
}
]
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- POST
uri: api/team-members
metadata:
custom: []
groupName: 'Team Members'
groupDescription: |-
Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
title: 'Create a new team member'
description: 'Create a new team member with name, role, and hourly rate.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters: []
cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
bodyParameters:
name:
custom: []
name: name
description: 'Team member name.'
required: true
example: 'John Doe'
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
role_id:
custom: []
name: role_id
description: 'Role ID.'
required: true
example: 1
type: integer
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
hourly_rate:
custom: []
name: hourly_rate
description: 'Hourly rate (must be > 0).'
required: true
example: '150.00'
type: numeric
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
active:
custom: []
name: active
description: 'Active status (defaults to true).'
required: false
example: true
type: boolean
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanBodyParameters:
name: 'John Doe'
role_id: 1
hourly_rate: '150.00'
active: true
fileParameters: []
responses:
-
custom: []
status: 201
content: |-
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"role_id": 1,
"role": {
"id": 1,
"name": "Backend Developer"
},
"hourly_rate": "150.00",
"active": true,
"created_at": "2024-01-15T10:00:00.000000Z",
"updated_at": "2024-01-15T10:00:00.000000Z"
}
headers: []
description: ''
-
custom: []
status: 422
content: '{"message":"Validation failed","errors":{"name":["The name field is required."],"hourly_rate":["Hourly rate must be greater than 0"]}}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- GET
uri: 'api/team-members/{id}'
metadata:
custom: []
groupName: 'Team Members'
groupDescription: |-
Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
title: 'Get a single team member'
description: 'Get details of a specific team member by ID.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters:
id:
custom: []
name: id
description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanUrlParameters:
id: 550e8400-e29b-41d4-a716-446655440000
queryParameters: []
cleanQueryParameters: []
bodyParameters: []
cleanBodyParameters: []
fileParameters: []
responses:
-
custom: []
status: 200
content: |-
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"role_id": 1,
"role": {
"id": 1,
"name": "Backend Developer"
},
"hourly_rate": "150.00",
"active": true,
"created_at": "2024-01-15T10:00:00.000000Z",
"updated_at": "2024-01-15T10:00:00.000000Z"
}
headers: []
description: ''
-
custom: []
status: 404
content: '{"message":"Team member not found"}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- PUT
- PATCH
uri: 'api/team-members/{id}'
metadata:
custom: []
groupName: 'Team Members'
groupDescription: |-
Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
title: 'Update a team member'
description: 'Update details of an existing team member.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters:
id:
custom: []
name: id
description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanUrlParameters:
id: 550e8400-e29b-41d4-a716-446655440000
queryParameters: []
cleanQueryParameters: []
bodyParameters:
name:
custom: []
name: name
description: 'Team member name.'
required: false
example: 'John Doe'
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
role_id:
custom: []
name: role_id
description: 'Role ID.'
required: false
example: 1
type: integer
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
hourly_rate:
custom: []
name: hourly_rate
description: 'Hourly rate (must be > 0).'
required: false
example: '175.00'
type: numeric
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
active:
custom: []
name: active
description: 'Active status.'
required: false
example: false
type: boolean
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanBodyParameters:
name: 'John Doe'
role_id: 1
hourly_rate: '175.00'
active: false
fileParameters: []
responses:
-
custom: []
status: 200
content: |-
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"role_id": 1,
"role": {
"id": 1,
"name": "Backend Developer"
},
"hourly_rate": "175.00",
"active": false,
"created_at": "2024-01-15T10:00:00.000000Z",
"updated_at": "2024-01-15T11:00:00.000000Z"
}
headers: []
description: ''
-
custom: []
status: 404
content: '{"message":"Team member not found"}'
headers: []
description: ''
-
custom: []
status: 422
content: '{"message":"Validation failed","errors":{"hourly_rate":["Hourly rate must be greater than 0"]}}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- DELETE
uri: 'api/team-members/{id}'
metadata:
custom: []
groupName: 'Team Members'
groupDescription: |-
Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
title: 'Delete a team member'
description: 'Delete a team member. Cannot delete if member has allocations or actuals.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters:
id:
custom: []
name: id
description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanUrlParameters:
id: 550e8400-e29b-41d4-a716-446655440000
queryParameters: []
cleanQueryParameters: []
bodyParameters: []
cleanBodyParameters: []
fileParameters: []
responses:
-
custom: []
status: 200
content: '{"message":"Team member deleted successfully"}'
headers: []
description: ''
-
custom: []
status: 404
content: '{"message":"Team member not found"}'
headers: []
description: ''
-
custom: []
status: 422
content: '{"message":"Cannot delete team member with active allocations","suggestion":"Consider deactivating the team member instead"}'
headers: []
description: ''
-
custom: []
status: 422
content: '{"message":"Cannot delete team member with historical data","suggestion":"Consider deactivating the team member instead"}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null

View File

@@ -0,0 +1,212 @@
name: Authentication
description: |-
Endpoints for JWT authentication and session lifecycle.
endpoints:
-
custom: []
httpMethods:
- POST
uri: api/auth/login
metadata:
custom: []
groupName: Authentication
groupDescription: |-
Endpoints for JWT authentication and session lifecycle.
subgroup: ''
subgroupDescription: ''
title: 'Login and get tokens'
description: 'Authenticate with email and password to receive an access token and refresh token.'
authenticated: false
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters: []
cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
bodyParameters:
email:
custom: []
name: email
description: 'User email address.'
required: true
example: user@example.com
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
password:
custom: []
name: password
description: 'User password.'
required: true
example: secret123
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanBodyParameters:
email: user@example.com
password: secret123
fileParameters: []
responses:
-
custom: []
status: 200
content: |-
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh_token": "abc123def456",
"token_type": "bearer",
"expires_in": 3600,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Alice Johnson",
"email": "user@example.com",
"role": "manager"
}
}
headers: []
description: ''
-
custom: []
status: 401
content: '{"message":"Invalid credentials"}'
headers: []
description: ''
-
custom: []
status: 403
content: '{"message":"Account is inactive"}'
headers: []
description: ''
-
custom: []
status: 422
content: '{"errors":{"email":["The email field is required."],"password":["The password field is required."]}}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- POST
uri: api/auth/refresh
metadata:
custom: []
groupName: Authentication
groupDescription: |-
Endpoints for JWT authentication and session lifecycle.
subgroup: ''
subgroupDescription: ''
title: 'Refresh access token'
description: 'Exchange a valid refresh token for a new access token and refresh token pair.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters: []
cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
bodyParameters:
refresh_token:
custom: []
name: refresh_token
description: 'Refresh token returned by login.'
required: true
example: abc123def456
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanBodyParameters:
refresh_token: abc123def456
fileParameters: []
responses:
-
custom: []
status: 200
content: |-
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh_token": "newtoken123",
"token_type": "bearer",
"expires_in": 3600
}
headers: []
description: ''
-
custom: []
status: 401
content: '{"message":"Invalid or expired refresh token"}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- POST
uri: api/auth/logout
metadata:
custom: []
groupName: Authentication
groupDescription: |-
Endpoints for JWT authentication and session lifecycle.
subgroup: ''
subgroupDescription: ''
title: 'Logout current session'
description: 'Invalidate a refresh token and end the active authenticated session.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters: []
cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
bodyParameters:
refresh_token:
custom: []
name: refresh_token
description: 'Optional refresh token to invalidate immediately.'
required: false
example: abc123def456
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanBodyParameters:
refresh_token: abc123def456
fileParameters: []
responses:
-
custom: []
status: 200
content: '{"message":"Logged out successfully"}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null

View File

@@ -0,0 +1,441 @@
name: 'Team Members'
description: |-
Endpoints for managing team members.
endpoints:
-
custom: []
httpMethods:
- GET
uri: api/team-members
metadata:
custom: []
groupName: 'Team Members'
groupDescription: |-
Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
title: 'List all team members'
description: 'Get a list of all team members with optional filtering by active status.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters: []
cleanUrlParameters: []
queryParameters:
active:
custom: []
name: active
description: 'Filter by active status.'
required: false
example: true
type: boolean
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanQueryParameters:
active: true
bodyParameters: []
cleanBodyParameters: []
fileParameters: []
responses:
-
custom: []
status: 200
content: |-
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"role_id": 1,
"role": {
"id": 1,
"name": "Backend Developer"
},
"hourly_rate": "150.00",
"active": true,
"created_at": "2024-01-15T10:00:00.000000Z",
"updated_at": "2024-01-15T10:00:00.000000Z"
}
]
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- POST
uri: api/team-members
metadata:
custom: []
groupName: 'Team Members'
groupDescription: |-
Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
title: 'Create a new team member'
description: 'Create a new team member with name, role, and hourly rate.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters: []
cleanUrlParameters: []
queryParameters: []
cleanQueryParameters: []
bodyParameters:
name:
custom: []
name: name
description: 'Team member name.'
required: true
example: 'John Doe'
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
role_id:
custom: []
name: role_id
description: 'Role ID.'
required: true
example: 1
type: integer
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
hourly_rate:
custom: []
name: hourly_rate
description: 'Hourly rate (must be > 0).'
required: true
example: '150.00'
type: numeric
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
active:
custom: []
name: active
description: 'Active status (defaults to true).'
required: false
example: true
type: boolean
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanBodyParameters:
name: 'John Doe'
role_id: 1
hourly_rate: '150.00'
active: true
fileParameters: []
responses:
-
custom: []
status: 201
content: |-
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"role_id": 1,
"role": {
"id": 1,
"name": "Backend Developer"
},
"hourly_rate": "150.00",
"active": true,
"created_at": "2024-01-15T10:00:00.000000Z",
"updated_at": "2024-01-15T10:00:00.000000Z"
}
headers: []
description: ''
-
custom: []
status: 422
content: '{"message":"Validation failed","errors":{"name":["The name field is required."],"hourly_rate":["Hourly rate must be greater than 0"]}}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- GET
uri: 'api/team-members/{id}'
metadata:
custom: []
groupName: 'Team Members'
groupDescription: |-
Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
title: 'Get a single team member'
description: 'Get details of a specific team member by ID.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters:
id:
custom: []
name: id
description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanUrlParameters:
id: 550e8400-e29b-41d4-a716-446655440000
queryParameters: []
cleanQueryParameters: []
bodyParameters: []
cleanBodyParameters: []
fileParameters: []
responses:
-
custom: []
status: 200
content: |-
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"role_id": 1,
"role": {
"id": 1,
"name": "Backend Developer"
},
"hourly_rate": "150.00",
"active": true,
"created_at": "2024-01-15T10:00:00.000000Z",
"updated_at": "2024-01-15T10:00:00.000000Z"
}
headers: []
description: ''
-
custom: []
status: 404
content: '{"message":"Team member not found"}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- PUT
- PATCH
uri: 'api/team-members/{id}'
metadata:
custom: []
groupName: 'Team Members'
groupDescription: |-
Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
title: 'Update a team member'
description: 'Update details of an existing team member.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters:
id:
custom: []
name: id
description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanUrlParameters:
id: 550e8400-e29b-41d4-a716-446655440000
queryParameters: []
cleanQueryParameters: []
bodyParameters:
name:
custom: []
name: name
description: 'Team member name.'
required: false
example: 'John Doe'
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
role_id:
custom: []
name: role_id
description: 'Role ID.'
required: false
example: 1
type: integer
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
hourly_rate:
custom: []
name: hourly_rate
description: 'Hourly rate (must be > 0).'
required: false
example: '175.00'
type: numeric
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
active:
custom: []
name: active
description: 'Active status.'
required: false
example: false
type: boolean
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanBodyParameters:
name: 'John Doe'
role_id: 1
hourly_rate: '175.00'
active: false
fileParameters: []
responses:
-
custom: []
status: 200
content: |-
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"role_id": 1,
"role": {
"id": 1,
"name": "Backend Developer"
},
"hourly_rate": "175.00",
"active": false,
"created_at": "2024-01-15T10:00:00.000000Z",
"updated_at": "2024-01-15T11:00:00.000000Z"
}
headers: []
description: ''
-
custom: []
status: 404
content: '{"message":"Team member not found"}'
headers: []
description: ''
-
custom: []
status: 422
content: '{"message":"Validation failed","errors":{"hourly_rate":["Hourly rate must be greater than 0"]}}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null
-
custom: []
httpMethods:
- DELETE
uri: 'api/team-members/{id}'
metadata:
custom: []
groupName: 'Team Members'
groupDescription: |-
Endpoints for managing team members.
subgroup: ''
subgroupDescription: ''
title: 'Delete a team member'
description: 'Delete a team member. Cannot delete if member has allocations or actuals.'
authenticated: true
deprecated: false
headers:
Content-Type: application/json
Accept: application/json
urlParameters:
id:
custom: []
name: id
description: 'Team member UUID.'
required: true
example: 550e8400-e29b-41d4-a716-446655440000
type: string
enumValues: []
exampleWasSpecified: true
nullable: false
deprecated: false
cleanUrlParameters:
id: 550e8400-e29b-41d4-a716-446655440000
queryParameters: []
cleanQueryParameters: []
bodyParameters: []
cleanBodyParameters: []
fileParameters: []
responses:
-
custom: []
status: 200
content: '{"message":"Team member deleted successfully"}'
headers: []
description: ''
-
custom: []
status: 404
content: '{"message":"Team member not found"}'
headers: []
description: ''
-
custom: []
status: 422
content: '{"message":"Cannot delete team member with active allocations","suggestion":"Consider deactivating the team member instead"}'
headers: []
description: ''
-
custom: []
status: 422
content: '{"message":"Cannot delete team member with historical data","suggestion":"Consider deactivating the team member instead"}'
headers: []
description: ''
responseFields: []
auth: []
controller: null
method: null
route: null

View File

@@ -1,12 +1,13 @@
# Introduction
Resource planning and capacity management API
<aside>
<strong>Base URL</strong>: <code>http://localhost/api</code>
<strong>Base URL</strong>: <code>http://localhost</code>
</aside>
Authenticate by sending `Authorization: Bearer {access_token}` on protected endpoints.
This documentation aims to provide all the information you need to work with our API.
Access tokens are valid for 60 minutes. Use `/api/auth/refresh` with your refresh token to obtain a new access token and refresh token pair.
<aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>

View File

@@ -1,24 +1,32 @@
<?php
use Knuckles\Scribe\Config\AuthIn;
use Knuckles\Scribe\Config\Defaults;
use Knuckles\Scribe\Extracting\Strategies;
use function Knuckles\Scribe\Config\configureStrategy;
use function Knuckles\Scribe\Config\removeStrategies;
// Only the most common configs are shown. See the https://scribe.knuckles.wtf/laravel/reference/config for all.
return [
// The HTML <title> for the generated documentation.
'title' => 'Headroom API',
'title' => config('app.name').' API Documentation',
// A short description of your API. Will be included in the docs webpage, Postman collection and OpenAPI spec.
'description' => 'Resource planning and capacity management API',
'description' => '',
// Text to place in the "Introduction" section, right after the `description`. Markdown and HTML are supported.
'intro_text' => <<<'INTRO'
Authenticate by sending `Authorization: Bearer {access_token}` on protected endpoints.
This documentation aims to provide all the information you need to work with our API.
Access tokens are valid for 60 minutes. Use `/api/auth/refresh` with your refresh token to obtain a new access token and refresh token pair.
<aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>
INTRO,
// The base URL displayed in the docs.
// If you're using `laravel` type, you can set this to a dynamic string, like '{{ config("app.tenant_url") }}' to get a dynamic base URL.
'base_url' => rtrim(config('app.url'), '/').'/api',
'base_url' => config('app.url'),
// Routes to include in the docs
'routes' => [
@@ -38,7 +46,7 @@ return [
// Exclude these routes even if they matched the rules above.
'exclude' => [
'api/user',
// 'GET /health', 'admin.*'
],
],
],
@@ -64,7 +72,7 @@ return [
// URL path to use for the docs endpoint (if `add_routes` is true).
// By default, `/docs` opens the HTML page, `/docs.postman` opens the Postman collection, and `/docs.openapi` the OpenAPI spec.
'docs_url' => '/api/documentation',
'docs_url' => '/docs',
// Directory within `public` in which to store CSS and JS assets.
// By default, assets are stored in `public/vendor/scribe`.
@@ -97,28 +105,28 @@ return [
// How is your API authenticated? This information will be used in the displayed docs, generated examples and response calls.
'auth' => [
// Set this to true if ANY endpoints in your API use authentication.
'enabled' => true,
'enabled' => false,
// Set this to true if your API should be authenticated by default. If so, you must also set `enabled` (above) to true.
// You can then use @unauthenticated or @authenticated on individual endpoints to change their status from the default.
'default' => true,
'default' => false,
// Where is the auth value meant to be sent in a request?
'in' => 'bearer',
'in' => AuthIn::BEARER->value,
// The name of the auth parameter (e.g. token, key, apiKey) or header (e.g. Authorization, Api-Key).
'name' => 'Authorization',
'name' => 'key',
// The value of the parameter to be used by Scribe to authenticate response calls.
// This will NOT be included in the generated documentation. If empty, Scribe will use a random value.
'use_value' => 'Bearer {token}',
'use_value' => env('SCRIBE_AUTH_KEY'),
// Placeholder your users will see for the auth parameter in the example requests.
// Set this to null if you want Scribe to use a random value as placeholder instead.
'placeholder' => 'Bearer {token}',
'placeholder' => '{YOUR_AUTH_KEY}',
// Any extra authentication-related info for your users. Markdown and HTML are supported.
'extra_info' => 'Get tokens from `POST /api/auth/login`, send access token as `Bearer {token}`, and renew with `POST /api/auth/refresh` before access token expiry.',
'extra_info' => 'You can retrieve your token by visiting your dashboard and clicking <b>Generate API token</b>.',
],
// Example requests for each endpoint will be shown in each of these languages.
@@ -205,18 +213,38 @@ return [
// Use configureStrategy() to specify settings for a strategy in the list.
// Use removeStrategies() to remove an included strategy.
'strategies' => [
'metadata' => [],
'metadata' => [
...Defaults::METADATA_STRATEGIES,
],
'headers' => [
[
...Defaults::HEADERS_STRATEGIES,
Strategies\StaticData::withSettings(data: [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
]),
],
'urlParameters' => [
...Defaults::URL_PARAMETERS_STRATEGIES,
],
'queryParameters' => [
...Defaults::QUERY_PARAMETERS_STRATEGIES,
],
'bodyParameters' => [
...Defaults::BODY_PARAMETERS_STRATEGIES,
],
'responses' => configureStrategy(
Defaults::RESPONSES_STRATEGIES,
Strategies\Responses\ResponseCalls::withSettings(
only: ['GET *'],
// Recommended: disable debug mode in response calls to avoid error stack traces in responses
config: [
'app.debug' => false,
]
)
),
'responseFields' => [
...Defaults::RESPONSE_FIELDS_STRATEGIES,
],
'urlParameters' => [],
'queryParameters' => [],
'bodyParameters' => [],
'responses' => [],
'responseFields' => [],
],
// For response calls, API resource responses and transformer responses,

File diff suppressed because it is too large Load Diff

View File

@@ -106,7 +106,7 @@
{#each row.getVisibleCells() as cell}
<td>
{#if typeof cell.column.columnDef.cell === 'function'}
{cell.column.columnDef.cell(cell.getContext())}
{@html cell.column.columnDef.cell(cell.getContext())}
{:else}
{cell.getValue()}
{/if}

View File

@@ -248,7 +248,7 @@
<!-- Create/Edit Modal -->
{#if showModal}
<div class="modal modal-open">
<div class="modal-box max-w-md">
<div class="modal-box max-w-lg">
<div class="flex justify-between items-center mb-4">
<h3 class="font-bold text-lg">{editingMember ? 'Edit Team Member' : 'Add Team Member'}</h3>
<button class="btn btn-ghost btn-sm btn-circle" onclick={closeModal}>
@@ -264,27 +264,29 @@
{/if}
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
<div class="form-control mb-4">
<label class="label" for="name">
<span class="label-text">Name</span>
<!-- Name Field - Horizontal Layout -->
<div class="form-control mb-4 flex flex-row items-center gap-4">
<label class="label w-28 shrink-0" for="name">
<span class="label-text font-medium">Name</span>
</label>
<input
type="text"
id="name"
class="input input-bordered"
class="input input-bordered flex-1"
bind:value={formData.name}
placeholder="Enter name"
required
/>
</div>
<div class="form-control mb-4">
<label class="label" for="role">
<span class="label-text">Role</span>
<!-- Role Field - Horizontal Layout -->
<div class="form-control mb-4 flex flex-row items-center gap-4">
<label class="label w-28 shrink-0" for="role">
<span class="label-text font-medium">Role</span>
</label>
<select
id="role"
class="select select-bordered"
class="select select-bordered flex-1"
bind:value={formData.role_id}
required
>
@@ -294,14 +296,17 @@
</select>
</div>
<div class="form-control mb-4">
<label class="label" for="hourly_rate">
<span class="label-text">Hourly Rate ($)</span>
<!-- Hourly Rate Field - Horizontal Layout -->
<div class="form-control mb-4 flex flex-row items-center gap-4">
<label class="label w-28 shrink-0" for="hourly_rate">
<span class="label-text font-medium">Hourly Rate</span>
</label>
<div class="flex-1 relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-base-content/50">$</span>
<input
type="number"
id="hourly_rate"
class="input input-bordered"
class="input input-bordered w-full pl-7"
bind:value={formData.hourly_rate}
placeholder="0.00"
min="0.01"
@@ -309,15 +314,20 @@
required
/>
</div>
</div>
<div class="form-control mb-6">
<label class="label cursor-pointer justify-start gap-3">
<!-- Active Status -->
<div class="form-control mb-6 flex flex-row items-center gap-4">
<label class="label w-28 shrink-0">
<span class="label-text font-medium">Status</span>
</label>
<label class="label cursor-pointer justify-start gap-3 flex-1">
<input
type="checkbox"
class="checkbox"
class="checkbox checkbox-sm"
bind:checked={formData.active}
/>
<span class="label-text">Active</span>
<span class="label-text">{formData.active ? 'Active' : 'Inactive'}</span>
</label>
</div>