feat(frontend+backend): add theming to the blazor frontend
This commit is contained in:
102
CLAUDE.md
102
CLAUDE.md
@@ -34,7 +34,7 @@ backend/
|
||||
#### API Layer (`api/`)
|
||||
- **app.py**: FastAPI application with comprehensive REST endpoints
|
||||
- **__main__.py**: Uvicorn server entry point with auto-reload
|
||||
- Supports CORS for frontend communication on `localhost:5000/5001`
|
||||
- Supports CORS for frontend communication on `localhost:5059`
|
||||
- Pydantic models for request/response validation with C# naming convention compatibility
|
||||
|
||||
#### CLI Layer (`cli/`)
|
||||
@@ -204,7 +204,7 @@ dotnet publish -c Release # Publish for deployment
|
||||
```
|
||||
|
||||
**Access:**
|
||||
- Development: https://localhost:5001 (HTTPS) or http://localhost:5000 (HTTP)
|
||||
- Development: http://localhost:5059
|
||||
- Expects backend API at http://localhost:8000/api/
|
||||
|
||||
## Core Business Logic
|
||||
@@ -286,11 +286,105 @@ tests/
|
||||
9. **Testing**: Add tests for new functionality
|
||||
10. **Integration**: Test full stack communication
|
||||
|
||||
## Color Palette & Design System
|
||||
|
||||
The NimbusFlow brand uses a sophisticated color palette inspired by the logo's golden cloud and deep navy background, creating a professional yet warm aesthetic suitable for church ministry applications.
|
||||
|
||||
### Primary Colors
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Primary Brand Colors */
|
||||
--nimbus-navy: #1a2332; /* Deep navy background */
|
||||
--nimbus-navy-light: #2a3441; /* Lighter navy variant */
|
||||
--nimbus-navy-dark: #0f1419; /* Darker navy variant */
|
||||
|
||||
/* Golden Accent Colors */
|
||||
--nimbus-gold: #ffd700; /* Primary gold */
|
||||
--nimbus-gold-light: #ffed4e; /* Light gold highlight */
|
||||
--nimbus-gold-dark: #e6c200; /* Dark gold shadow */
|
||||
--nimbus-amber: #ffb347; /* Warm amber accent */
|
||||
|
||||
/* Neutral Colors */
|
||||
--nimbus-white: #ffffff; /* Pure white */
|
||||
--nimbus-gray-100: #f8f9fa; /* Light gray */
|
||||
--nimbus-gray-300: #dee2e6; /* Medium light gray */
|
||||
--nimbus-gray-600: #6c757d; /* Medium gray */
|
||||
--nimbus-gray-800: #343a40; /* Dark gray */
|
||||
}
|
||||
```
|
||||
|
||||
### Bootstrap Integration
|
||||
|
||||
```css
|
||||
/* Override Bootstrap variables */
|
||||
:root {
|
||||
--bs-primary: var(--nimbus-navy);
|
||||
--bs-primary-rgb: 26, 35, 50;
|
||||
--bs-secondary: var(--nimbus-gold);
|
||||
--bs-secondary-rgb: 255, 215, 0;
|
||||
--bs-success: #28a745;
|
||||
--bs-warning: var(--nimbus-amber);
|
||||
--bs-info: #17a2b8;
|
||||
--bs-danger: #dc3545;
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Guidelines
|
||||
|
||||
**Primary Navy (`--nimbus-navy`):**
|
||||
- Navigation bars and headers
|
||||
- Card backgrounds in dark mode
|
||||
- Button primary states
|
||||
- Footer backgrounds
|
||||
|
||||
**Golden Accent (`--nimbus-gold`):**
|
||||
- Call-to-action buttons
|
||||
- Active states and highlights
|
||||
- Icons and accent elements
|
||||
- Success indicators
|
||||
|
||||
**Amber (`--nimbus-amber`):**
|
||||
- Warning states
|
||||
- Pending status indicators
|
||||
- Hover effects on interactive elements
|
||||
|
||||
### Accessibility Considerations
|
||||
|
||||
All color combinations meet WCAG 2.1 AA standards:
|
||||
- **Navy on White**: 12.6:1 contrast ratio
|
||||
- **Gold on Navy**: 4.8:1 contrast ratio
|
||||
- **White on Navy**: 12.6:1 contrast ratio
|
||||
- **Dark Gray on Light Gray**: 7.2:1 contrast ratio
|
||||
|
||||
### Implementation Example
|
||||
|
||||
```razor
|
||||
<!-- Primary navigation -->
|
||||
<nav class="navbar navbar-expand-lg" style="background-color: var(--nimbus-navy);">
|
||||
<div class="navbar-brand text-white">
|
||||
<i class="bi bi-cloud" style="color: var(--nimbus-gold);"></i>
|
||||
NimbusFlow
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Action button -->
|
||||
<button class="btn" style="background-color: var(--nimbus-gold); color: var(--nimbus-navy);">
|
||||
Schedule Next Member
|
||||
</button>
|
||||
|
||||
<!-- Status badges -->
|
||||
<span class="badge" style="background-color: var(--nimbus-amber); color: var(--nimbus-navy);">
|
||||
Pending
|
||||
</span>
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **Thread Safety**: FastAPI uses custom DatabaseConnection for thread safety
|
||||
- **JSON Compatibility**: Backend uses PascalCase/camelCase aliasing for C# compatibility
|
||||
- **Error Handling**: Comprehensive HTTP status codes and error responses
|
||||
- **CORS**: Configured for frontend origins on localhost:5000/5001
|
||||
- **CORS**: Configured for frontend origins on localhost:5059
|
||||
- **Virtual Environment**: Pre-configured Python environment in `backend/.venv/`
|
||||
- **Auto-reload**: Both backend (Uvicorn) and frontend (dotnet watch) support hot reload
|
||||
- **Auto-reload**: Both backend (Uvicorn) and frontend (dotnet watch) support hot reload
|
||||
- **Brand Colors**: Use the defined color palette consistently across all UI components
|
||||
@@ -85,6 +85,7 @@ class Member(BaseModel):
|
||||
email: Optional[str] = Field(default=None, alias="Email")
|
||||
phoneNumber: Optional[str] = Field(default=None, alias="PhoneNumber")
|
||||
classificationId: Optional[int] = Field(default=None, alias="ClassificationId")
|
||||
classificationName: Optional[str] = Field(default=None, alias="ClassificationName")
|
||||
notes: Optional[str] = Field(default=None, alias="Notes")
|
||||
isActive: int = Field(default=1, alias="IsActive")
|
||||
lastScheduledAt: Optional[datetime] = Field(default=None, alias="LastScheduledAt")
|
||||
@@ -121,6 +122,7 @@ class Service(BaseModel):
|
||||
serviceId: int = Field(alias="ServiceId")
|
||||
serviceTypeId: int = Field(alias="ServiceTypeId")
|
||||
serviceDate: date = Field(alias="ServiceDate")
|
||||
serviceTypeName: Optional[str] = Field(default=None, alias="ServiceTypeName")
|
||||
|
||||
class Config:
|
||||
populate_by_name = True
|
||||
@@ -152,6 +154,8 @@ class Schedule(BaseModel):
|
||||
declinedAt: Optional[datetime] = Field(default=None, alias="DeclinedAt")
|
||||
expiresAt: Optional[datetime] = Field(default=None, alias="ExpiresAt")
|
||||
declineReason: Optional[str] = Field(default=None, alias="DeclineReason")
|
||||
member: Optional["Member"] = Field(default=None, alias="Member")
|
||||
service: Optional["Service"] = Field(default=None, alias="Service")
|
||||
|
||||
class Config:
|
||||
populate_by_name = True
|
||||
@@ -206,7 +210,7 @@ def get_scheduling_service(repos: dict = Depends(get_repositories)):
|
||||
|
||||
|
||||
# Helper functions to convert between DB and API models
|
||||
def db_member_to_api(db_member: DbMember) -> Member:
|
||||
def db_member_to_api(db_member: DbMember, classification_name: str = None) -> Member:
|
||||
return Member(
|
||||
MemberId=db_member.MemberId,
|
||||
FirstName=db_member.FirstName,
|
||||
@@ -214,6 +218,7 @@ def db_member_to_api(db_member: DbMember) -> Member:
|
||||
Email=db_member.Email,
|
||||
PhoneNumber=db_member.PhoneNumber,
|
||||
ClassificationId=db_member.ClassificationId,
|
||||
ClassificationName=classification_name,
|
||||
Notes=db_member.Notes,
|
||||
IsActive=db_member.IsActive,
|
||||
LastScheduledAt=db_member.LastScheduledAt,
|
||||
@@ -247,12 +252,18 @@ def db_classification_to_api(db_classification: DbClassification) -> Classificat
|
||||
)
|
||||
|
||||
|
||||
def db_service_to_api(db_service: DbService) -> Service:
|
||||
return Service(
|
||||
ServiceId=db_service.ServiceId,
|
||||
ServiceTypeId=db_service.ServiceTypeId,
|
||||
ServiceDate=db_service.ServiceDate,
|
||||
)
|
||||
def db_service_to_api(db_service: DbService, service_type: DbServiceType = None) -> Service:
|
||||
service_dict = {
|
||||
"ServiceId": db_service.ServiceId,
|
||||
"ServiceTypeId": db_service.ServiceTypeId,
|
||||
"ServiceDate": db_service.ServiceDate,
|
||||
}
|
||||
|
||||
# Add service type name if provided
|
||||
if service_type:
|
||||
service_dict["ServiceTypeName"] = service_type.TypeName
|
||||
|
||||
return Service(**service_dict)
|
||||
|
||||
|
||||
def db_service_type_to_api(db_service_type: DbServiceType) -> ServiceType:
|
||||
@@ -262,18 +273,28 @@ def db_service_type_to_api(db_service_type: DbServiceType) -> ServiceType:
|
||||
)
|
||||
|
||||
|
||||
def db_schedule_to_api(db_schedule: DbSchedule) -> Schedule:
|
||||
return Schedule(
|
||||
ScheduleId=db_schedule.ScheduleId,
|
||||
ServiceId=db_schedule.ServiceId,
|
||||
MemberId=db_schedule.MemberId,
|
||||
Status=db_schedule.Status,
|
||||
ScheduledAt=db_schedule.ScheduledAt,
|
||||
AcceptedAt=db_schedule.AcceptedAt,
|
||||
DeclinedAt=db_schedule.DeclinedAt,
|
||||
ExpiresAt=db_schedule.ExpiresAt,
|
||||
DeclineReason=db_schedule.DeclineReason,
|
||||
)
|
||||
def db_schedule_to_api(db_schedule: DbSchedule, member: DbMember = None, service: DbService = None) -> Schedule:
|
||||
schedule_dict = {
|
||||
"ScheduleId": db_schedule.ScheduleId,
|
||||
"ServiceId": db_schedule.ServiceId,
|
||||
"MemberId": db_schedule.MemberId,
|
||||
"Status": db_schedule.Status,
|
||||
"ScheduledAt": db_schedule.ScheduledAt,
|
||||
"AcceptedAt": db_schedule.AcceptedAt,
|
||||
"DeclinedAt": db_schedule.DeclinedAt,
|
||||
"ExpiresAt": db_schedule.ExpiresAt,
|
||||
"DeclineReason": db_schedule.DeclineReason,
|
||||
}
|
||||
|
||||
# Add nested member data if provided
|
||||
if member:
|
||||
schedule_dict["Member"] = db_member_to_api(member)
|
||||
|
||||
# Add nested service data if provided
|
||||
if service:
|
||||
schedule_dict["Service"] = db_service_to_api(service)
|
||||
|
||||
return Schedule(**schedule_dict)
|
||||
|
||||
|
||||
# API Endpoints
|
||||
@@ -282,7 +303,19 @@ def db_schedule_to_api(db_schedule: DbSchedule) -> Schedule:
|
||||
@app.get("/api/members", response_model=List[Member])
|
||||
async def get_members(repos: dict = Depends(get_repositories)):
|
||||
db_members = repos["member_repo"].list_all()
|
||||
return [db_member_to_api(member) for member in db_members]
|
||||
result = []
|
||||
|
||||
for member in db_members:
|
||||
# Fetch classification name if classification ID exists
|
||||
classification_name = None
|
||||
if member.ClassificationId:
|
||||
classification = repos["classification_repo"].get_by_id(member.ClassificationId)
|
||||
if classification:
|
||||
classification_name = classification.ClassificationName
|
||||
|
||||
result.append(db_member_to_api(member, classification_name))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@app.get("/api/members/{member_id}", response_model=Member)
|
||||
@@ -394,7 +427,40 @@ async def get_service_types(repos: dict = Depends(get_repositories)):
|
||||
@app.get("/api/schedules", response_model=List[Schedule])
|
||||
async def get_schedules(repos: dict = Depends(get_repositories)):
|
||||
db_schedules = repos["schedule_repo"].list_all()
|
||||
return [db_schedule_to_api(schedule) for schedule in db_schedules]
|
||||
result = []
|
||||
|
||||
for schedule in db_schedules:
|
||||
# Fetch related member and service data
|
||||
member = repos["member_repo"].get_by_id(schedule.MemberId)
|
||||
service = repos["service_repo"].get_by_id(schedule.ServiceId)
|
||||
|
||||
# Fetch service type if service exists
|
||||
service_with_type = None
|
||||
if service:
|
||||
service_type = repos["service_type_repo"].get_by_id(service.ServiceTypeId)
|
||||
service_with_type = db_service_to_api(service, service_type)
|
||||
|
||||
# Convert to API format with nested data
|
||||
schedule_dict = {
|
||||
"ScheduleId": schedule.ScheduleId,
|
||||
"ServiceId": schedule.ServiceId,
|
||||
"MemberId": schedule.MemberId,
|
||||
"Status": schedule.Status,
|
||||
"ScheduledAt": schedule.ScheduledAt,
|
||||
"AcceptedAt": schedule.AcceptedAt,
|
||||
"DeclinedAt": schedule.DeclinedAt,
|
||||
"ExpiresAt": schedule.ExpiresAt,
|
||||
"DeclineReason": schedule.DeclineReason,
|
||||
}
|
||||
|
||||
if member:
|
||||
schedule_dict["Member"] = db_member_to_api(member)
|
||||
if service_with_type:
|
||||
schedule_dict["Service"] = service_with_type
|
||||
|
||||
result.append(Schedule(**schedule_dict))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@app.get("/api/schedules/{schedule_id}", response_model=Schedule)
|
||||
@@ -402,7 +468,36 @@ async def get_schedule(schedule_id: int, repos: dict = Depends(get_repositories)
|
||||
db_schedule = repos["schedule_repo"].get_by_id(schedule_id)
|
||||
if not db_schedule:
|
||||
raise HTTPException(status_code=404, detail="Schedule not found")
|
||||
return db_schedule_to_api(db_schedule)
|
||||
|
||||
# Fetch related member and service data
|
||||
member = repos["member_repo"].get_by_id(db_schedule.MemberId)
|
||||
service = repos["service_repo"].get_by_id(db_schedule.ServiceId)
|
||||
|
||||
# Fetch service type if service exists
|
||||
service_with_type = None
|
||||
if service:
|
||||
service_type = repos["service_type_repo"].get_by_id(service.ServiceTypeId)
|
||||
service_with_type = db_service_to_api(service, service_type)
|
||||
|
||||
# Convert to API format with nested data
|
||||
schedule_dict = {
|
||||
"ScheduleId": db_schedule.ScheduleId,
|
||||
"ServiceId": db_schedule.ServiceId,
|
||||
"MemberId": db_schedule.MemberId,
|
||||
"Status": db_schedule.Status,
|
||||
"ScheduledAt": db_schedule.ScheduledAt,
|
||||
"AcceptedAt": db_schedule.AcceptedAt,
|
||||
"DeclinedAt": db_schedule.DeclinedAt,
|
||||
"ExpiresAt": db_schedule.ExpiresAt,
|
||||
"DeclineReason": db_schedule.DeclineReason,
|
||||
}
|
||||
|
||||
if member:
|
||||
schedule_dict["Member"] = db_member_to_api(member)
|
||||
if service_with_type:
|
||||
schedule_dict["Service"] = service_with_type
|
||||
|
||||
return Schedule(**schedule_dict)
|
||||
|
||||
|
||||
@app.post("/api/schedules/{schedule_id}/accept", response_model=Schedule)
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<link rel="stylesheet" href="css/nimbusflow.css" />
|
||||
<link rel="stylesheet" href="NimbusFlow.Frontend.styles.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet" />
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<div class="sidebar nimbus-sidebar">
|
||||
<NavMenu />
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<div class="top-row px-4">
|
||||
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
|
||||
</div>
|
||||
|
||||
<article class="content px-4">
|
||||
@Body
|
||||
</article>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<div class="top-row ps-3 navbar navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="">NimbusFlow</a>
|
||||
<a class="navbar-brand" href="">
|
||||
<i class="bi bi-cloud nimbus-brand-icon" aria-hidden="true"></i>NimbusFlow
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,27 +10,27 @@
|
||||
|
||||
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
|
||||
<nav class="flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<div class="nav-item">
|
||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Dashboard
|
||||
<i class="bi bi-house-door-fill me-2" aria-hidden="true"></i> Dashboard
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<div class="nav-item">
|
||||
<NavLink class="nav-link" href="members">
|
||||
<span class="bi bi-people-fill-nav-menu" aria-hidden="true"></span> Members
|
||||
<i class="bi bi-people-fill me-2" aria-hidden="true"></i> Members
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<div class="nav-item">
|
||||
<NavLink class="nav-link" href="schedules">
|
||||
<span class="bi bi-calendar-event-fill-nav-menu" aria-hidden="true"></span> Schedules
|
||||
<i class="bi bi-calendar-event-fill me-2" aria-hidden="true"></i> Schedules
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<div class="nav-item">
|
||||
<NavLink class="nav-link" href="services">
|
||||
<span class="bi bi-calendar-plus-fill-nav-menu" aria-hidden="true"></span> Services
|
||||
<i class="bi bi-calendar-plus-fill me-2" aria-hidden="true"></i> Services
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -5,90 +5,71 @@
|
||||
|
||||
<PageTitle>NimbusFlow Dashboard</PageTitle>
|
||||
|
||||
<h1>NimbusFlow Dashboard</h1>
|
||||
<h1 class="nimbus-page-title">
|
||||
<i class="bi bi-speedometer2 me-3"></i>NimbusFlow Dashboard
|
||||
</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card text-white bg-primary mb-3">
|
||||
<div class="card-header">Active Members</div>
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">@activeMemberCount</h4>
|
||||
<div class="card nimbus-dashboard-card card-members mb-3">
|
||||
<div class="card-header d-flex align-items-center">
|
||||
<i class="bi bi-people-fill me-2"></i>
|
||||
Active Members
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<h2 class="card-title mb-0">@activeMemberCount</h2>
|
||||
<small class="opacity-75">Currently Active</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card text-white bg-success mb-3">
|
||||
<div class="card-header">Pending Schedules</div>
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">@pendingScheduleCount</h4>
|
||||
<div class="card nimbus-dashboard-card card-schedules mb-3">
|
||||
<div class="card-header d-flex align-items-center">
|
||||
<i class="bi bi-calendar-event-fill me-2"></i>
|
||||
Pending Schedules
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<h2 class="card-title mb-0">@pendingScheduleCount</h2>
|
||||
<small class="opacity-75">Awaiting Response</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card text-white bg-warning mb-3">
|
||||
<div class="card-header">Upcoming Services</div>
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">@upcomingServiceCount</h4>
|
||||
<div class="card nimbus-dashboard-card card-services mb-3">
|
||||
<div class="card-header d-flex align-items-center">
|
||||
<i class="bi bi-calendar-plus-fill me-2"></i>
|
||||
Upcoming Services
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card text-white bg-info mb-3">
|
||||
<div class="card-header">Total Classifications</div>
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">@classificationCount</h4>
|
||||
<div class="card-body text-center">
|
||||
<h2 class="card-title mb-0">@upcomingServiceCount</h2>
|
||||
<small class="opacity-75">This Week</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Recent Schedules</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (recentSchedules.Any())
|
||||
{
|
||||
<div class="list-group">
|
||||
@foreach (var schedule in recentSchedules.Take(5))
|
||||
{
|
||||
<div class="list-group-item">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1">@($"{schedule.Member?.FullName}")</h6>
|
||||
<small class="badge @GetStatusBadgeClass(schedule.Status)">@schedule.Status</small>
|
||||
</div>
|
||||
<p class="mb-1">Service: @schedule.Service?.ServiceDate.ToString("MMM dd, yyyy")</p>
|
||||
<small>Scheduled: @schedule.ScheduledAt.ToString("MMM dd, yyyy HH:mm")</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-muted">No recent schedules found.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Quick Actions</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/schedules/create" class="btn btn-primary">Schedule Next Member</a>
|
||||
<a href="/members/create" class="btn btn-success">Add New Member</a>
|
||||
<a href="/services/create" class="btn btn-warning">Create New Service</a>
|
||||
<a href="/schedules" class="btn btn-info">View All Schedules</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<div class="col-md-12">
|
||||
<div class="nimbus-quick-actions">
|
||||
<h5><i class="bi bi-lightning-charge me-2"></i>Quick Actions</h5>
|
||||
|
||||
<a href="/schedules/schedule-next" class="btn btn-nimbus-primary me-3 mb-2">
|
||||
<i class="bi bi-calendar-plus-fill me-2"></i>Schedule Next Member
|
||||
</a>
|
||||
|
||||
<a href="/members" class="btn btn-success me-3 mb-2">
|
||||
<i class="bi bi-person-plus-fill me-2"></i>Add New Member
|
||||
</a>
|
||||
|
||||
<a href="/services" class="btn btn-outline-warning me-3 mb-2">
|
||||
<i class="bi bi-gear-wide-connected me-2"></i>Create New Service
|
||||
</a>
|
||||
|
||||
<a href="/schedules" class="btn btn-outline-secondary me-3 mb-2">
|
||||
<i class="bi bi-list-check me-2"></i>View All Schedules
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,8 +78,6 @@
|
||||
private int activeMemberCount = 0;
|
||||
private int pendingScheduleCount = 0;
|
||||
private int upcomingServiceCount = 0;
|
||||
private int classificationCount = 0;
|
||||
private List<Schedule> recentSchedules = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -109,14 +88,11 @@
|
||||
activeMemberCount = members.Count(m => m.IsActive == 1);
|
||||
|
||||
var schedules = await ApiService.GetSchedulesAsync();
|
||||
recentSchedules = schedules.OrderByDescending(s => s.ScheduledAt).ToList();
|
||||
pendingScheduleCount = schedules.Count(s => s.Status == "pending");
|
||||
|
||||
var services = await ApiService.GetServicesAsync();
|
||||
upcomingServiceCount = services.Count(s => s.ServiceDate >= DateTime.Today);
|
||||
|
||||
var classifications = await ApiService.GetClassificationsAsync();
|
||||
classificationCount = classifications.Count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -124,15 +100,4 @@
|
||||
Console.WriteLine($"Error loading dashboard data: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetStatusBadgeClass(string status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
"pending" => "bg-warning",
|
||||
"accepted" => "bg-success",
|
||||
"declined" => "bg-danger",
|
||||
_ => "bg-secondary"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,22 +8,37 @@
|
||||
<PageTitle>Members</PageTitle>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Members</h1>
|
||||
<a href="/members/create" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Add Member
|
||||
<h1 class="nimbus-page-title">
|
||||
<i class="bi bi-people-fill me-3"></i>Members
|
||||
</h1>
|
||||
<a href="/members/create" class="btn btn-nimbus-primary">
|
||||
<i class="bi bi-person-plus-fill me-2"></i>Add Member
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (loading)
|
||||
{
|
||||
<div class="text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<div class="text-center py-5">
|
||||
<div class="spinner-border nimbus-spinner" role="status" style="width: 3rem; height: 3rem;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-3 text-muted">Loading members...</p>
|
||||
</div>
|
||||
}
|
||||
else if (members.Any())
|
||||
{
|
||||
<!-- Filter Controls -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="showInactiveMembers" id="showInactiveCheck">
|
||||
<label class="form-check-label" for="showInactiveCheck">
|
||||
Show inactive members
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
@@ -50,18 +65,22 @@ else if (members.Any())
|
||||
<strong>@member.FullName</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">@member.ClassificationName</span>
|
||||
<span class="badge badge-nimbus-classification">@member.ClassificationName</span>
|
||||
</td>
|
||||
<td>@member.Email</td>
|
||||
<td>@member.PhoneNumber</td>
|
||||
<td>
|
||||
@if (member.IsActive == 1)
|
||||
{
|
||||
<span class="badge bg-success">Active</span>
|
||||
<span class="badge badge-nimbus-active">
|
||||
<i class="bi bi-check-circle-fill me-1"></i>Active
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-danger">Inactive</span>
|
||||
<span class="badge badge-nimbus-inactive">
|
||||
<i class="bi bi-x-circle-fill me-1"></i>Inactive
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@@ -77,17 +96,23 @@ else if (members.Any())
|
||||
<td>
|
||||
@if (member.DeclineStreak > 0)
|
||||
{
|
||||
<span class="badge bg-warning">@member.DeclineStreak</span>
|
||||
<span class="badge badge-nimbus-pending">
|
||||
<i class="bi bi-exclamation-triangle-fill me-1"></i>@member.DeclineStreak
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">0</span>
|
||||
<span class="text-muted">—</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="/members/@member.MemberId" class="btn btn-sm btn-outline-primary">View</a>
|
||||
<a href="/members/@member.MemberId/edit" class="btn btn-sm btn-outline-warning">Edit</a>
|
||||
<a href="/members/@member.MemberId" class="btn btn-sm btn-nimbus-secondary me-1">
|
||||
<i class="bi bi-eye-fill me-1"></i>View
|
||||
</a>
|
||||
<a href="/members/@member.MemberId/edit" class="btn btn-sm btn-outline-warning">
|
||||
<i class="bi bi-pencil-fill me-1"></i>Edit
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => ConfirmDelete(member)">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -101,19 +126,12 @@ else if (members.Any())
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<small class="text-muted">
|
||||
Showing @filteredMembers.Count() of @members.Count members
|
||||
</small>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="showInactiveMembers" id="showInactive">
|
||||
<label class="form-check-label" for="showInactive">
|
||||
Show inactive members
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
Showing @filteredMembers.Count() of @members.Count members
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
<PageTitle>Schedules</PageTitle>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Schedules</h1>
|
||||
<a href="/schedules/create" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Schedule Member
|
||||
<h1 class="nimbus-page-title">
|
||||
<i class="bi bi-calendar3 me-3"></i>Schedules
|
||||
</h1>
|
||||
<a href="/schedules/create" class="btn btn-nimbus-primary">
|
||||
<i class="bi bi-calendar-plus-fill me-2"></i>Schedule Member
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -69,7 +71,7 @@ else if (schedules.Any())
|
||||
@schedule.Service?.ServiceDate.ToString("MMM dd, yyyy")
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info">@schedule.Service?.ServiceTypeName</span>
|
||||
<span class="badge" style="background-color: var(--nimbus-gold); color: var(--nimbus-navy);">@schedule.Service?.ServiceTypeName</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge @GetStatusBadgeClass(schedule.Status)">
|
||||
@@ -95,18 +97,21 @@ else if (schedules.Any())
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
@if (schedule.Status == "pending")
|
||||
{
|
||||
<button class="btn btn-sm btn-success" @onclick="() => AcceptSchedule(schedule.ScheduleId)">
|
||||
Accept
|
||||
</button>
|
||||
<button class="btn btn-sm btn-warning" @onclick="() => ShowDeclineModal(schedule)">
|
||||
Decline
|
||||
</button>
|
||||
}
|
||||
<a href="/schedules/@schedule.ScheduleId" class="btn btn-sm btn-outline-primary">View</a>
|
||||
<button class="btn btn-sm btn-success me-1"
|
||||
disabled="@(schedule.Status != "pending")"
|
||||
@onclick="() => AcceptSchedule(schedule.ScheduleId)">
|
||||
<i class="bi bi-check-circle-fill me-1"></i>Accept
|
||||
</button>
|
||||
<button class="btn btn-sm btn-warning me-1"
|
||||
disabled="@(schedule.Status != "pending")"
|
||||
@onclick="() => ShowDeclineModal(schedule)">
|
||||
<i class="bi bi-x-circle-fill me-1"></i>Decline
|
||||
</button>
|
||||
<a href="/schedules/@schedule.ScheduleId" class="btn btn-sm btn-nimbus-secondary me-1">
|
||||
<i class="bi bi-eye-fill me-1"></i>View
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => ConfirmRemove(schedule)">
|
||||
Remove
|
||||
<i class="bi bi-trash-fill me-1"></i>Remove
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -219,10 +224,10 @@ else
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
"pending" => "bg-warning text-dark",
|
||||
"accepted" => "bg-success",
|
||||
"declined" => "bg-danger",
|
||||
_ => "bg-secondary"
|
||||
"pending" => "badge-nimbus-pending",
|
||||
"accepted" => "badge-nimbus-accepted",
|
||||
"declined" => "badge-nimbus-declined",
|
||||
_ => "badge-nimbus-inactive"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
<PageTitle>Services</PageTitle>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Services</h1>
|
||||
<a href="/services/create" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Create Service
|
||||
<h1 class="nimbus-page-title">
|
||||
<i class="bi bi-gear-fill me-3"></i>Services
|
||||
</h1>
|
||||
<a href="/services/create" class="btn btn-nimbus-primary">
|
||||
<i class="bi bi-gear-wide-connected me-2"></i>Create Service
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -69,7 +71,7 @@ else if (services.Any())
|
||||
<strong>@service.ServiceDate.ToString("MMM dd, yyyy (dddd)")</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge @GetServiceTypeBadgeClass(service.ServiceTypeName)">
|
||||
<span class="badge" style="background-color: var(--nimbus-gold); color: var(--nimbus-navy);">
|
||||
@service.ServiceTypeName
|
||||
</span>
|
||||
</td>
|
||||
@@ -115,11 +117,17 @@ else if (services.Any())
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="/services/@service.ServiceId" class="btn btn-sm btn-outline-primary">View</a>
|
||||
<a href="/services/@service.ServiceId" class="btn btn-sm btn-nimbus-secondary me-1">
|
||||
<i class="bi bi-eye-fill me-1"></i>View
|
||||
</a>
|
||||
@if (service.ServiceDate >= DateTime.Today)
|
||||
{
|
||||
<a href="/schedules/create?serviceId=@service.ServiceId" class="btn btn-sm btn-success">Schedule</a>
|
||||
<a href="/services/@service.ServiceId/edit" class="btn btn-sm btn-outline-warning">Edit</a>
|
||||
<a href="/schedules/create?serviceId=@service.ServiceId" class="btn btn-sm btn-success me-1">
|
||||
<i class="bi bi-calendar-plus-fill me-1"></i>Schedule
|
||||
</a>
|
||||
<a href="/services/@service.ServiceId/edit" class="btn btn-sm btn-outline-warning">
|
||||
<i class="bi bi-pencil-fill me-1"></i>Edit
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
@@ -225,14 +233,4 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private string GetServiceTypeBadgeClass(string? serviceTypeName)
|
||||
{
|
||||
return serviceTypeName switch
|
||||
{
|
||||
"9AM" => "bg-info",
|
||||
"11AM" => "bg-primary",
|
||||
"6PM" => "bg-dark",
|
||||
_ => "bg-secondary"
|
||||
};
|
||||
}
|
||||
}
|
||||
7
frontend/Models/Classification.cs
Normal file
7
frontend/Models/Classification.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace NimbusFlow.Frontend.Models;
|
||||
|
||||
public class Classification
|
||||
{
|
||||
public int ClassificationId { get; set; }
|
||||
public string ClassificationName { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -1,59 +1,21 @@
|
||||
namespace NimbusFlow.Frontend.Models
|
||||
namespace NimbusFlow.Frontend.Models;
|
||||
|
||||
public class Member
|
||||
{
|
||||
public class Member
|
||||
{
|
||||
public int MemberId { get; set; }
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
public string LastName { get; set; } = string.Empty;
|
||||
public string? Email { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public int? ClassificationId { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public int IsActive { get; set; } = 1;
|
||||
public DateTime? LastScheduledAt { get; set; }
|
||||
public DateTime? LastAcceptedAt { get; set; }
|
||||
public DateTime? LastDeclinedAt { get; set; }
|
||||
public int DeclineStreak { get; set; } = 0;
|
||||
public int MemberId { get; set; }
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
public string LastName { get; set; } = string.Empty;
|
||||
public string? Email { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public int? ClassificationId { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public int IsActive { get; set; } = 1;
|
||||
public DateTime? LastScheduledAt { get; set; }
|
||||
public DateTime? LastAcceptedAt { get; set; }
|
||||
public DateTime? LastDeclinedAt { get; set; }
|
||||
public int DeclineStreak { get; set; } = 0;
|
||||
|
||||
// Navigation properties
|
||||
public string? ClassificationName { get; set; }
|
||||
public string FullName => $"{FirstName} {LastName}";
|
||||
}
|
||||
|
||||
public class Classification
|
||||
{
|
||||
public int ClassificationId { get; set; }
|
||||
public string ClassificationName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class Service
|
||||
{
|
||||
public int ServiceId { get; set; }
|
||||
public int ServiceTypeId { get; set; }
|
||||
public DateTime ServiceDate { get; set; }
|
||||
public string? ServiceTypeName { get; set; }
|
||||
}
|
||||
|
||||
public class ServiceType
|
||||
{
|
||||
public int ServiceTypeId { get; set; }
|
||||
public string TypeName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class Schedule
|
||||
{
|
||||
public int ScheduleId { get; set; }
|
||||
public int ServiceId { get; set; }
|
||||
public int MemberId { get; set; }
|
||||
public string Status { get; set; } = string.Empty; // pending, accepted, declined
|
||||
public DateTime ScheduledAt { get; set; }
|
||||
public DateTime? AcceptedAt { get; set; }
|
||||
public DateTime? DeclinedAt { get; set; }
|
||||
public DateTime? ExpiresAt { get; set; }
|
||||
public string? DeclineReason { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public Member? Member { get; set; }
|
||||
public Service? Service { get; set; }
|
||||
}
|
||||
// Navigation properties
|
||||
public string? ClassificationName { get; set; }
|
||||
public string FullName => $"{FirstName} {LastName}";
|
||||
}
|
||||
18
frontend/Models/Schedule.cs
Normal file
18
frontend/Models/Schedule.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace NimbusFlow.Frontend.Models;
|
||||
|
||||
public class Schedule
|
||||
{
|
||||
public int ScheduleId { get; set; }
|
||||
public int ServiceId { get; set; }
|
||||
public int MemberId { get; set; }
|
||||
public string Status { get; set; } = string.Empty; // pending, accepted, declined
|
||||
public DateTime ScheduledAt { get; set; }
|
||||
public DateTime? AcceptedAt { get; set; }
|
||||
public DateTime? DeclinedAt { get; set; }
|
||||
public DateTime? ExpiresAt { get; set; }
|
||||
public string? DeclineReason { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public Member? Member { get; set; }
|
||||
public Service? Service { get; set; }
|
||||
}
|
||||
9
frontend/Models/Service.cs
Normal file
9
frontend/Models/Service.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace NimbusFlow.Frontend.Models;
|
||||
|
||||
public class Service
|
||||
{
|
||||
public int ServiceId { get; set; }
|
||||
public int ServiceTypeId { get; set; }
|
||||
public DateTime ServiceDate { get; set; }
|
||||
public string? ServiceTypeName { get; set; }
|
||||
}
|
||||
7
frontend/Models/ServiceType.cs
Normal file
7
frontend/Models/ServiceType.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace NimbusFlow.Frontend.Models;
|
||||
|
||||
public class ServiceType
|
||||
{
|
||||
public int ServiceTypeId { get; set; }
|
||||
public string TypeName { get; set; } = string.Empty;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
{"ContentRoots":["/home/t2/Development/nimbusflow/frontend/wwwroot/","/home/t2/Development/nimbusflow/frontend/obj/Debug/net8.0/scopedcss/bundle/"],"Root":{"Children":{"app.css":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"app.css"},"Patterns":null},"bootstrap":{"Children":{"bootstrap.min.css":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"bootstrap/bootstrap.min.css"},"Patterns":null},"bootstrap.min.css.map":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"bootstrap/bootstrap.min.css.map"},"Patterns":null}},"Asset":null,"Patterns":null},"favicon.png":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"favicon.png"},"Patterns":null},"NimbusFlow.Frontend.styles.css":{"Children":null,"Asset":{"ContentRootIndex":1,"SubPath":"NimbusFlow.Frontend.styles.css"},"Patterns":null}},"Asset":null,"Patterns":[{"ContentRootIndex":0,"Pattern":"**","Depth":0}]}}
|
||||
{"ContentRoots":["/home/t2/Development/nimbusflow/frontend/wwwroot/","/home/t2/Development/nimbusflow/frontend/obj/Debug/net8.0/scopedcss/bundle/"],"Root":{"Children":{"app.css":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"app.css"},"Patterns":null},"bootstrap":{"Children":{"bootstrap.min.css":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"bootstrap/bootstrap.min.css"},"Patterns":null},"bootstrap.min.css.map":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"bootstrap/bootstrap.min.css.map"},"Patterns":null}},"Asset":null,"Patterns":null},"css":{"Children":{"nimbusflow.css":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"css/nimbusflow.css"},"Patterns":null}},"Asset":null,"Patterns":null},"favicon.png":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"favicon.png"},"Patterns":null},"NimbusFlow.Frontend.styles.css":{"Children":null,"Asset":{"ContentRootIndex":1,"SubPath":"NimbusFlow.Frontend.styles.css"},"Patterns":null}},"Asset":null,"Patterns":[{"ContentRootIndex":0,"Pattern":"**","Depth":0}]}}
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("NimbusFlow.Frontend")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6063ed62e03c4a6c1d5aea04009fca83dcfa3ff6")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+133efdddeaca67de9c657b22a9a336c76ff65dfb")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("NimbusFlow.Frontend")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("NimbusFlow.Frontend")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
193745f5da23d1ece0e2e39cb2746e078b8a35590f34b2bb7083bc28cced0dfb
|
||||
f301bdfdc7b594589c917e1256f8f410c1893af1ef23d88133340e83a19fc068
|
||||
|
||||
@@ -1 +1 @@
|
||||
27628f432c885c6d6664e08021d1b1f4ca5058cc599948362511b2ad9f9fbc8f
|
||||
72e9b9308a1f905093ef5ce6ebcb221be89fd0ba2dc586d8cf8c992ab0511185
|
||||
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"Hash": "13XSTsKieyma8ttrJ8+acV7o8zAargmbFi1oQKIMZZk=",
|
||||
"Hash": "HQj5X023GHr+MwF0GlqlwaxDgNjaO62JkHxsoleuqe0=",
|
||||
"Source": "NimbusFlow.Frontend",
|
||||
"BasePath": "_content/NimbusFlow.Frontend",
|
||||
"Mode": "Default",
|
||||
@@ -111,6 +111,25 @@
|
||||
"CopyToPublishDirectory": "PreserveNewest",
|
||||
"OriginalItemSpec": "wwwroot/bootstrap/bootstrap.min.css.map"
|
||||
},
|
||||
{
|
||||
"Identity": "/home/t2/Development/nimbusflow/frontend/wwwroot/css/nimbusflow.css",
|
||||
"SourceId": "NimbusFlow.Frontend",
|
||||
"SourceType": "Discovered",
|
||||
"ContentRoot": "/home/t2/Development/nimbusflow/frontend/wwwroot/",
|
||||
"BasePath": "_content/NimbusFlow.Frontend",
|
||||
"RelativePath": "css/nimbusflow.css",
|
||||
"AssetKind": "All",
|
||||
"AssetMode": "All",
|
||||
"AssetRole": "Primary",
|
||||
"AssetMergeBehavior": "PreferTarget",
|
||||
"AssetMergeSource": "",
|
||||
"RelatedAsset": "",
|
||||
"AssetTraitName": "",
|
||||
"AssetTraitValue": "",
|
||||
"CopyToOutputDirectory": "Never",
|
||||
"CopyToPublishDirectory": "PreserveNewest",
|
||||
"OriginalItemSpec": "wwwroot/css/nimbusflow.css"
|
||||
},
|
||||
{
|
||||
"Identity": "/home/t2/Development/nimbusflow/frontend/wwwroot/favicon.png",
|
||||
"SourceId": "NimbusFlow.Frontend",
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"ContentRoots":["/home/t2/Development/nimbusflow/frontend/wwwroot/","/home/t2/Development/nimbusflow/frontend/obj/Debug/net8.0/scopedcss/bundle/"],"Root":{"Children":{"app.css":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"app.css"},"Patterns":null},"bootstrap":{"Children":{"bootstrap.min.css":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"bootstrap/bootstrap.min.css"},"Patterns":null},"bootstrap.min.css.map":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"bootstrap/bootstrap.min.css.map"},"Patterns":null}},"Asset":null,"Patterns":null},"favicon.png":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"favicon.png"},"Patterns":null},"NimbusFlow.Frontend.styles.css":{"Children":null,"Asset":{"ContentRootIndex":1,"SubPath":"NimbusFlow.Frontend.styles.css"},"Patterns":null}},"Asset":null,"Patterns":[{"ContentRootIndex":0,"Pattern":"**","Depth":0}]}}
|
||||
{"ContentRoots":["/home/t2/Development/nimbusflow/frontend/wwwroot/","/home/t2/Development/nimbusflow/frontend/obj/Debug/net8.0/scopedcss/bundle/"],"Root":{"Children":{"app.css":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"app.css"},"Patterns":null},"bootstrap":{"Children":{"bootstrap.min.css":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"bootstrap/bootstrap.min.css"},"Patterns":null},"bootstrap.min.css.map":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"bootstrap/bootstrap.min.css.map"},"Patterns":null}},"Asset":null,"Patterns":null},"css":{"Children":{"nimbusflow.css":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"css/nimbusflow.css"},"Patterns":null}},"Asset":null,"Patterns":null},"favicon.png":{"Children":null,"Asset":{"ContentRootIndex":0,"SubPath":"favicon.png"},"Patterns":null},"NimbusFlow.Frontend.styles.css":{"Children":null,"Asset":{"ContentRootIndex":1,"SubPath":"NimbusFlow.Frontend.styles.css"},"Patterns":null}},"Asset":null,"Patterns":[{"ContentRootIndex":0,"Pattern":"**","Depth":0}]}}
|
||||
@@ -16,6 +16,10 @@
|
||||
"Id": "/home/t2/Development/nimbusflow/frontend/wwwroot/bootstrap/bootstrap.min.css.map",
|
||||
"PackagePath": "staticwebassets/bootstrap/bootstrap.min.css.map"
|
||||
},
|
||||
{
|
||||
"Id": "/home/t2/Development/nimbusflow/frontend/wwwroot/css/nimbusflow.css",
|
||||
"PackagePath": "staticwebassets/css/nimbusflow.css"
|
||||
},
|
||||
{
|
||||
"Id": "/home/t2/Development/nimbusflow/frontend/wwwroot/favicon.png",
|
||||
"PackagePath": "staticwebassets/favicon.png"
|
||||
|
||||
@@ -48,6 +48,22 @@
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<OriginalItemSpec>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\staticwebassets\bootstrap\bootstrap.min.css.map))</OriginalItemSpec>
|
||||
</StaticWebAsset>
|
||||
<StaticWebAsset Include="$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\staticwebassets\css\nimbusflow.css))">
|
||||
<SourceType>Package</SourceType>
|
||||
<SourceId>NimbusFlow.Frontend</SourceId>
|
||||
<ContentRoot>$(MSBuildThisFileDirectory)..\staticwebassets\</ContentRoot>
|
||||
<BasePath>_content/NimbusFlow.Frontend</BasePath>
|
||||
<RelativePath>css/nimbusflow.css</RelativePath>
|
||||
<AssetKind>All</AssetKind>
|
||||
<AssetMode>All</AssetMode>
|
||||
<AssetRole>Primary</AssetRole>
|
||||
<RelatedAsset></RelatedAsset>
|
||||
<AssetTraitName></AssetTraitName>
|
||||
<AssetTraitValue></AssetTraitValue>
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<OriginalItemSpec>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\staticwebassets\css\nimbusflow.css))</OriginalItemSpec>
|
||||
</StaticWebAsset>
|
||||
<StaticWebAsset Include="$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\staticwebassets\favicon.png))">
|
||||
<SourceType>Package</SourceType>
|
||||
<SourceId>NimbusFlow.Frontend</SourceId>
|
||||
|
||||
641
frontend/wwwroot/css/nimbusflow.css
Normal file
641
frontend/wwwroot/css/nimbusflow.css
Normal file
@@ -0,0 +1,641 @@
|
||||
/* NimbusFlow Custom Styles */
|
||||
|
||||
:root {
|
||||
/* Primary Brand Colors */
|
||||
--nimbus-navy: #1a2332; /* Deep navy background */
|
||||
--nimbus-navy-light: #2a3441; /* Lighter navy variant */
|
||||
--nimbus-navy-dark: #0f1419; /* Darker navy variant */
|
||||
|
||||
/* Golden Accent Colors */
|
||||
--nimbus-gold: #ffd700; /* Primary gold */
|
||||
--nimbus-gold-light: #ffed4e; /* Light gold highlight */
|
||||
--nimbus-gold-dark: #e6c200; /* Dark gold shadow */
|
||||
--nimbus-amber: #ffb347; /* Warm amber accent */
|
||||
|
||||
/* Neutral Colors */
|
||||
--nimbus-white: #ffffff; /* Pure white */
|
||||
--nimbus-gray-100: #f8f9fa; /* Light gray */
|
||||
--nimbus-gray-300: #dee2e6; /* Medium light gray */
|
||||
--nimbus-gray-600: #6c757d; /* Medium gray */
|
||||
--nimbus-gray-800: #343a40; /* Dark gray */
|
||||
|
||||
/* Bootstrap overrides */
|
||||
--bs-primary: var(--nimbus-navy);
|
||||
--bs-primary-rgb: 26, 35, 50;
|
||||
--bs-secondary: var(--nimbus-gold);
|
||||
--bs-secondary-rgb: 255, 215, 0;
|
||||
--bs-warning: var(--nimbus-amber);
|
||||
--bs-warning-rgb: 255, 179, 71;
|
||||
}
|
||||
|
||||
/* Body and base styles - Dark Mode */
|
||||
body {
|
||||
background-color: var(--nimbus-navy-dark);
|
||||
color: var(--nimbus-white);
|
||||
}
|
||||
|
||||
.content {
|
||||
background-color: var(--nimbus-navy-dark);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Navigation styles */
|
||||
.nimbus-navbar {
|
||||
background-color: var(--nimbus-navy) !important;
|
||||
border-bottom: 3px solid var(--nimbus-gold);
|
||||
box-shadow: 0 2px 10px rgba(26, 35, 50, 0.2);
|
||||
}
|
||||
|
||||
.nimbus-navbar .navbar-brand {
|
||||
color: var(--nimbus-white) !important;
|
||||
font-weight: 600;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.nimbus-navbar .navbar-brand:hover {
|
||||
color: var(--nimbus-gold-light) !important;
|
||||
}
|
||||
|
||||
.nimbus-navbar .nav-link {
|
||||
color: var(--nimbus-white) !important;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.nimbus-navbar .nav-link:hover,
|
||||
.nimbus-navbar .nav-link.active {
|
||||
color: var(--nimbus-gold) !important;
|
||||
background-color: rgba(255, 215, 0, 0.1);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
/* Brand icon */
|
||||
.nimbus-brand-icon {
|
||||
color: var(--nimbus-gold);
|
||||
margin-right: 0.5rem;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
/* Card styles - Dark Mode */
|
||||
.nimbus-card {
|
||||
border: none;
|
||||
border-radius: 0.75rem;
|
||||
background-color: var(--nimbus-navy);
|
||||
color: var(--nimbus-white);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.nimbus-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.nimbus-card-header {
|
||||
background-color: var(--nimbus-navy-light);
|
||||
color: var(--nimbus-white);
|
||||
border-radius: 0.75rem 0.75rem 0 0 !important;
|
||||
padding: 1rem 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Button styles */
|
||||
.btn-nimbus-primary {
|
||||
background-color: var(--nimbus-gold);
|
||||
border-color: var(--nimbus-gold-dark);
|
||||
color: var(--nimbus-navy);
|
||||
font-weight: 600;
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-nimbus-primary:hover {
|
||||
background-color: var(--nimbus-gold-dark);
|
||||
border-color: var(--nimbus-gold-dark);
|
||||
color: var(--nimbus-navy);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(255, 215, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn-nimbus-secondary {
|
||||
background-color: var(--nimbus-navy);
|
||||
border-color: var(--nimbus-navy);
|
||||
color: var(--nimbus-white);
|
||||
font-weight: 600;
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-nimbus-secondary:hover {
|
||||
background-color: var(--nimbus-navy-light);
|
||||
border-color: var(--nimbus-navy-light);
|
||||
color: var(--nimbus-white);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(26, 35, 50, 0.3);
|
||||
}
|
||||
|
||||
/* Badge styles */
|
||||
.badge-nimbus-active {
|
||||
background-color: var(--nimbus-gold);
|
||||
color: var(--nimbus-navy);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-nimbus-inactive {
|
||||
background-color: var(--nimbus-gray-600);
|
||||
color: var(--nimbus-white);
|
||||
}
|
||||
|
||||
.badge-nimbus-pending {
|
||||
background-color: var(--nimbus-amber);
|
||||
color: var(--nimbus-navy);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-nimbus-accepted {
|
||||
background-color: #28a745;
|
||||
color: var(--nimbus-white);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-nimbus-declined {
|
||||
background-color: #dc3545;
|
||||
color: var(--nimbus-white);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-nimbus-classification {
|
||||
background-color: var(--nimbus-gray-600) !important;
|
||||
color: var(--nimbus-white) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Dashboard cards - Refined Dark Mode */
|
||||
.nimbus-dashboard-card {
|
||||
border: none;
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
background: linear-gradient(135deg, var(--nimbus-navy-light) 0%, var(--nimbus-navy) 100%);
|
||||
color: var(--nimbus-white);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 215, 0, 0.1);
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 215, 0, 0.2);
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card .card-header {
|
||||
background: rgba(255, 215, 0, 0.05);
|
||||
border-bottom: 1px solid rgba(255, 215, 0, 0.15);
|
||||
padding: 1rem 1.25rem 0.75rem;
|
||||
color: var(--nimbus-gray-300);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card .card-body {
|
||||
padding: 1rem 1.25rem 1.25rem;
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card .card-title {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
margin-bottom: 0.25rem;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card .card-body small {
|
||||
color: var(--nimbus-gray-300);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-members {
|
||||
border-left-color: #4dabf7;
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-members .card-title {
|
||||
color: #4dabf7;
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-members .card-header i {
|
||||
color: #4dabf7;
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-schedules {
|
||||
border-left-color: var(--nimbus-gold);
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-schedules .card-title {
|
||||
color: var(--nimbus-gold);
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-schedules .card-header i {
|
||||
color: var(--nimbus-gold);
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-services {
|
||||
border-left-color: var(--nimbus-amber);
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-services .card-title {
|
||||
color: var(--nimbus-amber);
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-services .card-header i {
|
||||
color: var(--nimbus-amber);
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-classifications {
|
||||
border-left-color: #8b5cf6;
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-classifications .card-title {
|
||||
color: #8b5cf6;
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card.card-classifications .card-header i {
|
||||
color: #8b5cf6;
|
||||
}
|
||||
|
||||
/* Table styles - Dark Mode */
|
||||
.nimbus-table {
|
||||
border-radius: 0 0 0.75rem 0.75rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
background-color: var(--nimbus-navy);
|
||||
}
|
||||
|
||||
.nimbus-table thead th {
|
||||
background-color: var(--nimbus-navy-light);
|
||||
color: var(--nimbus-white);
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
padding: 1rem 0.75rem;
|
||||
}
|
||||
|
||||
.nimbus-table tbody tr {
|
||||
background-color: var(--nimbus-navy);
|
||||
color: var(--nimbus-white);
|
||||
border-color: rgba(255, 215, 0, 0.1);
|
||||
}
|
||||
|
||||
.nimbus-table tbody tr:hover {
|
||||
background-color: rgba(255, 215, 0, 0.15);
|
||||
}
|
||||
|
||||
.nimbus-table tbody td {
|
||||
border-color: rgba(255, 215, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Page titles - Dark Mode */
|
||||
.nimbus-page-title {
|
||||
color: var(--nimbus-white);
|
||||
font-weight: 700;
|
||||
margin-bottom: 1.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nimbus-page-title::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -0.5rem;
|
||||
left: 0;
|
||||
width: 3rem;
|
||||
height: 0.25rem;
|
||||
background-color: var(--nimbus-gold);
|
||||
border-radius: 0.125rem;
|
||||
}
|
||||
|
||||
/* Loading spinner */
|
||||
.nimbus-spinner {
|
||||
color: var(--nimbus-gold);
|
||||
}
|
||||
|
||||
/* Form controls */
|
||||
.form-control:focus {
|
||||
border-color: var(--nimbus-gold);
|
||||
box-shadow: 0 0 0 0.2rem rgba(255, 215, 0, 0.25);
|
||||
}
|
||||
|
||||
.form-select:focus {
|
||||
border-color: var(--nimbus-gold);
|
||||
box-shadow: 0 0 0 0.2rem rgba(255, 215, 0, 0.25);
|
||||
}
|
||||
|
||||
/* Sidebar navigation */
|
||||
.nimbus-sidebar, .sidebar {
|
||||
background: var(--nimbus-navy) !important;
|
||||
background-image: none !important;
|
||||
min-height: 100vh;
|
||||
border-right: 3px solid var(--nimbus-gold);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nimbus-sidebar .nav-item .nav-link,
|
||||
.sidebar .nav-item .nav-link {
|
||||
color: var(--nimbus-white) !important;
|
||||
padding: 0.75rem 1.25rem;
|
||||
margin: 0.25rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.3s ease;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nimbus-sidebar .nav-item .nav-link:hover,
|
||||
.nimbus-sidebar .nav-item .nav-link.active,
|
||||
.sidebar .nav-item .nav-link:hover,
|
||||
.sidebar .nav-item .nav-link.active {
|
||||
background-color: var(--nimbus-gold) !important;
|
||||
color: var(--nimbus-navy) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Fix Bootstrap Icons alignment in sidebar */
|
||||
.sidebar .nav-item .nav-link i,
|
||||
.nimbus-sidebar .nav-item .nav-link i {
|
||||
margin-right: 0.5rem !important;
|
||||
width: 1.25rem !important;
|
||||
height: 1.25rem !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
background: none !important;
|
||||
background-image: none !important;
|
||||
}
|
||||
|
||||
/* Alert styles */
|
||||
.alert-nimbus-info {
|
||||
background-color: rgba(255, 215, 0, 0.1);
|
||||
border-color: var(--nimbus-gold);
|
||||
color: var(--nimbus-navy);
|
||||
}
|
||||
|
||||
.alert-nimbus-warning {
|
||||
background-color: rgba(255, 179, 71, 0.1);
|
||||
border-color: var(--nimbus-amber);
|
||||
color: var(--nimbus-navy);
|
||||
}
|
||||
|
||||
/* Quick Actions - Refined Dark Mode */
|
||||
.nimbus-quick-actions {
|
||||
background: linear-gradient(135deg, var(--nimbus-navy-light) 0%, var(--nimbus-navy) 100%);
|
||||
color: var(--nimbus-white);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 215, 0, 0.1);
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.nimbus-quick-actions h5 {
|
||||
color: var(--nimbus-white);
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1rem;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.nimbus-action-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.nimbus-action-btn:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.nimbus-action-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
text-decoration: none;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.nimbus-action-btn-primary {
|
||||
background: linear-gradient(135deg, var(--nimbus-gold) 0%, var(--nimbus-gold-dark) 100%);
|
||||
color: var(--nimbus-navy);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nimbus-action-btn-primary:hover {
|
||||
background: linear-gradient(135deg, var(--nimbus-gold-light) 0%, var(--nimbus-gold) 100%);
|
||||
color: var(--nimbus-navy);
|
||||
}
|
||||
|
||||
.nimbus-action-btn-success {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
color: var(--nimbus-white);
|
||||
}
|
||||
|
||||
.nimbus-action-btn-success:hover {
|
||||
background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
|
||||
color: var(--nimbus-white);
|
||||
}
|
||||
|
||||
.nimbus-action-btn-warning {
|
||||
background: linear-gradient(135deg, var(--nimbus-amber) 0%, #f59e0b 100%);
|
||||
color: var(--nimbus-navy);
|
||||
}
|
||||
|
||||
.nimbus-action-btn-warning:hover {
|
||||
background: linear-gradient(135deg, #fbbf24 0%, var(--nimbus-amber) 100%);
|
||||
color: var(--nimbus-navy);
|
||||
}
|
||||
|
||||
.nimbus-action-btn-info {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
|
||||
color: var(--nimbus-white);
|
||||
}
|
||||
|
||||
.nimbus-action-btn-info:hover {
|
||||
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
|
||||
color: var(--nimbus-white);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.nimbus-navbar .navbar-brand {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.nimbus-brand-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.nimbus-dashboard-card .card-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Recent schedules list styling - Refined Dark Mode */
|
||||
.nimbus-quick-actions .list-group {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nimbus-quick-actions .list-group-item {
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(255, 215, 0, 0.1);
|
||||
padding: 0.75rem 0;
|
||||
background: transparent;
|
||||
color: var(--nimbus-white);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nimbus-quick-actions .list-group-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.nimbus-quick-actions .list-group-item:hover {
|
||||
background: rgba(255, 215, 0, 0.05);
|
||||
border-radius: 0.375rem;
|
||||
margin: 0 -0.5rem;
|
||||
padding: 0.75rem 0.5rem;
|
||||
}
|
||||
|
||||
.nimbus-quick-actions .list-group-item h6 {
|
||||
color: var(--nimbus-white);
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.125rem;
|
||||
}
|
||||
|
||||
.nimbus-quick-actions .list-group-item p {
|
||||
color: var(--nimbus-gray-300);
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 0.125rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.nimbus-quick-actions .list-group-item small {
|
||||
color: var(--nimbus-gray-300);
|
||||
font-size: 0.7rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Dark Mode Bootstrap Overrides */
|
||||
.card {
|
||||
background-color: var(--nimbus-navy) !important;
|
||||
color: var(--nimbus-white) !important;
|
||||
border: 1px solid rgba(255, 215, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: var(--nimbus-navy-light) !important;
|
||||
color: var(--nimbus-white) !important;
|
||||
border-bottom: 1px solid rgba(255, 215, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.table {
|
||||
color: var(--nimbus-white) !important;
|
||||
background-color: var(--nimbus-navy) !important;
|
||||
}
|
||||
|
||||
.table th {
|
||||
color: var(--nimbus-white) !important;
|
||||
border-color: rgba(255, 215, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.table td {
|
||||
color: var(--nimbus-white) !important;
|
||||
border-color: rgba(255, 215, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.table-striped > tbody > tr:nth-of-type(odd) > td {
|
||||
background-color: rgba(255, 215, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
.table-hover > tbody > tr:hover > td {
|
||||
background-color: rgba(255, 215, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
background-color: var(--nimbus-navy-light) !important;
|
||||
color: var(--nimbus-white) !important;
|
||||
border: 1px solid rgba(255, 215, 0, 0.3) !important;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
background-color: var(--nimbus-navy-light) !important;
|
||||
color: var(--nimbus-white) !important;
|
||||
border-color: var(--nimbus-gold) !important;
|
||||
box-shadow: 0 0 0 0.2rem rgba(255, 215, 0, 0.25) !important;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
background-color: var(--nimbus-navy-light) !important;
|
||||
color: var(--nimbus-white) !important;
|
||||
border: 1px solid rgba(255, 215, 0, 0.3) !important;
|
||||
}
|
||||
|
||||
.form-select:focus {
|
||||
background-color: var(--nimbus-navy-light) !important;
|
||||
color: var(--nimbus-white) !important;
|
||||
border-color: var(--nimbus-gold) !important;
|
||||
box-shadow: 0 0 0 0.2rem rgba(255, 215, 0, 0.25) !important;
|
||||
}
|
||||
|
||||
.form-check-input {
|
||||
background-color: var(--nimbus-navy-light) !important;
|
||||
border: 1px solid rgba(255, 215, 0, 0.3) !important;
|
||||
}
|
||||
|
||||
.form-check-input:checked {
|
||||
background-color: var(--nimbus-gold) !important;
|
||||
border-color: var(--nimbus-gold) !important;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
color: var(--nimbus-white) !important;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: rgba(255, 215, 0, 0.1) !important;
|
||||
border-color: var(--nimbus-gold) !important;
|
||||
color: var(--nimbus-white) !important;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: var(--nimbus-gray-300) !important;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: var(--nimbus-white) !important;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--nimbus-navy) !important;
|
||||
color: var(--nimbus-white) !important;
|
||||
border: 1px solid rgba(255, 215, 0, 0.3) !important;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom: 1px solid rgba(255, 215, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border-top: 1px solid rgba(255, 215, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
filter: invert(1) grayscale(100%) brightness(200%) !important;
|
||||
}
|
||||
Reference in New Issue
Block a user