diff --git a/app/Http/Controllers/Settings/ProjectSettingController.php b/app/Http/Controllers/Settings/ProjectSettingController.php
index 06e3d89..49792d6 100644
--- a/app/Http/Controllers/Settings/ProjectSettingController.php
+++ b/app/Http/Controllers/Settings/ProjectSettingController.php
@@ -14,8 +14,8 @@ class ProjectSettingController extends Controller
public function index()
{
$companies = Company::with([
- 'projects.locations' => fn ($q) => $q->orderBy('name'),
- 'projects.departments' => fn ($q) => $q->orderBy('name'),
+ 'projects.locations' => fn ($q) => $q->orderBy('name'),
+ 'departments' => fn ($q) => $q->orderBy('name'),
])->orderBy('name')->get();
$allProjects = $companies->flatMap(fn ($c) => $c->projects);
@@ -25,7 +25,7 @@ class ProjectSettingController extends Controller
'total_projects' => $allProjects->count(),
'active_projects' => $allProjects->where('is_active', true)->count(),
'total_locations' => $allProjects->sum(fn ($p) => $p->locations->count()),
- 'total_departments' => $allProjects->sum(fn ($p) => $p->departments->count()),
+ 'total_departments' => $companies->sum(fn ($c) => $c->departments->count()),
];
return view('settings.projects.index', compact('companies', 'stats'));
@@ -148,21 +148,21 @@ class ProjectSettingController extends Controller
return response()->json(['ok' => true]);
}
- public function storeDepartment(Request $request, ProjectSetting $project)
+ public function storeDepartment(Request $request, Company $company)
{
$validated = $request->validate(['name' => 'required|string|max:255']);
- $dept = $project->departments()->create(['name' => $validated['name'], 'is_active' => true]);
+ $dept = $company->departments()->create(['name' => $validated['name'], 'is_active' => true]);
return response()->json(['department' => ['id' => $dept->id, 'name' => $dept->name, 'is_active' => $dept->is_active]]);
}
- public function updateDepartment(Request $request, ProjectSetting $project, Department $department)
+ public function updateDepartment(Request $request, Company $company, Department $department)
{
$validated = $request->validate(['name' => 'required|string|max:255']);
$department->update(['name' => $validated['name'], 'is_active' => $request->boolean('is_active', true)]);
return response()->json(['department' => ['id' => $department->id, 'name' => $department->name, 'is_active' => $department->is_active]]);
}
- public function destroyDepartment(ProjectSetting $project, Department $department)
+ public function destroyDepartment(Company $company, Department $department)
{
$department->delete();
return response()->json(['ok' => true]);
diff --git a/app/Models/Settings/Company.php b/app/Models/Settings/Company.php
index 5469524..314b766 100644
--- a/app/Models/Settings/Company.php
+++ b/app/Models/Settings/Company.php
@@ -16,4 +16,9 @@ class Company extends Model
{
return $this->hasMany(ProjectSetting::class, 'company_id');
}
+
+ public function departments(): \Illuminate\Database\Eloquent\Relations\HasMany
+ {
+ return $this->hasMany(Department::class, 'company_id');
+ }
}
diff --git a/app/Models/Settings/Department.php b/app/Models/Settings/Department.php
index f13bf48..ba7fb78 100644
--- a/app/Models/Settings/Department.php
+++ b/app/Models/Settings/Department.php
@@ -8,12 +8,12 @@ class Department extends Model
{
protected $table = 'settings_departments';
- protected $fillable = ['name', 'project_id', 'is_active'];
+ protected $fillable = ['name', 'company_id', 'is_active'];
protected $casts = ['is_active' => 'boolean'];
- public function project(): \Illuminate\Database\Eloquent\Relations\BelongsTo
+ public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
- return $this->belongsTo(ProjectSetting::class, 'project_id');
+ return $this->belongsTo(Company::class, 'company_id');
}
}
diff --git a/app/Models/Settings/ProjectSetting.php b/app/Models/Settings/ProjectSetting.php
index cba90d9..de07cda 100644
--- a/app/Models/Settings/ProjectSetting.php
+++ b/app/Models/Settings/ProjectSetting.php
@@ -27,8 +27,4 @@ class ProjectSetting extends Model
return $this->hasMany(\App\Models\Settings\Location::class, 'project_id');
}
- public function departments(): \Illuminate\Database\Eloquent\Relations\HasMany
- {
- return $this->hasMany(\App\Models\Settings\Department::class, 'project_id');
- }
}
diff --git a/database/migrations/2026_05_26_152123_move_departments_from_projects_to_companies.php b/database/migrations/2026_05_26_152123_move_departments_from_projects_to_companies.php
new file mode 100644
index 0000000..277319c
--- /dev/null
+++ b/database/migrations/2026_05_26_152123_move_departments_from_projects_to_companies.php
@@ -0,0 +1,47 @@
+id();
+ $table->foreignId('company_id')->constrained('settings_companies')->onDelete('cascade');
+ $table->string('name');
+ $table->boolean('is_active')->default(true);
+ $table->timestamps();
+ });
+
+ // Migrate: map each department's project_id → that project's company_id
+ DB::statement('
+ INSERT INTO settings_departments_new (id, company_id, name, is_active, created_at, updated_at)
+ SELECT d.id, p.company_id, d.name, d.is_active, d.created_at, d.updated_at
+ FROM settings_departments d
+ LEFT JOIN settings_projects p ON p.id = d.project_id
+ WHERE p.company_id IS NOT NULL
+ ');
+
+ Schema::drop('settings_departments');
+ Schema::rename('settings_departments_new', 'settings_departments');
+ }
+
+ public function down(): void
+ {
+ Schema::create('settings_departments_old', function (Blueprint $table) {
+ $table->id();
+ $table->foreignId('project_id')->constrained('settings_projects')->onDelete('cascade');
+ $table->string('name');
+ $table->boolean('is_active')->default(true);
+ $table->timestamps();
+ });
+
+ Schema::drop('settings_departments');
+ Schema::rename('settings_departments_old', 'settings_departments');
+ }
+};
diff --git a/resources/views/settings/projects/index.blade.php b/resources/views/settings/projects/index.blade.php
index fc653a7..02619f1 100644
--- a/resources/views/settings/projects/index.blade.php
+++ b/resources/views/settings/projects/index.blade.php
@@ -117,6 +117,14 @@
$allLocsData = [];
$allDeptsData = [];
foreach ($companies as $company) {
+ foreach ($company->departments as $dept) {
+ $allDeptsData[$dept->id] = [
+ 'id' => $dept->id,
+ 'name' => $dept->name,
+ 'is_active' => $dept->is_active,
+ 'company_id' => $dept->company_id,
+ ];
+ }
foreach ($company->projects as $proj) {
foreach ($proj->locations as $loc) {
$allLocsData[$loc->id] = [
@@ -128,13 +136,6 @@ foreach ($companies as $company) {
'is_active' => $loc->is_active,
];
}
- foreach ($proj->departments as $dept) {
- $allDeptsData[$dept->id] = [
- 'id' => $dept->id,
- 'name' => $dept->name,
- 'is_active' => $dept->is_active,
- ];
- }
}
}
$allLocsJson = json_encode($allLocsData);
@@ -154,10 +155,13 @@ $allDeptsJson = json_encode($allDeptsData);
{{ $company->name }}
Inactive
{{ $company->projects->count() }} {{ Str::plural('project', $company->projects->count()) }}
+ · {{ $company->departments->count() }} {{ Str::plural('dept', $company->departments->count()) }}
+
+ class="btn-primary" style="padding:4px 12px;font-size:12px;">+ Project
+ {{-- Departments section --}}
+
+
+ Departments
+
+ {{-- Add dept inline row --}}
+
+
+
+
+
+
+ @forelse($company->departments as $dept)
+
+
+
+
+
{{ $dept->name }}
+
Inactive
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @empty
+
No departments yet.
+ @endforelse
+
+
+
{{-- Projects container --}}
@@ -227,11 +274,9 @@ $allDeptsJson = json_encode($allDeptsData);
- {{-- Project body: Locations + Departments --}}
+ {{-- Project body: Locations --}}
-
- {{-- Locations --}}
-
+
- {{-- Departments --}}
-
-
-
-
-
-
-
-
- @forelse($project->departments as $dept)
-
-
-
-
-
{{ $dept->name }}
-
Inactive
-
-
-
-
-
-
-
-
-
-
-
-
-
- @empty
-
No departments yet.
- @endforelse
-
-
-
{{-- end proj-body-inner --}}
{{-- end proj-body --}}
{{-- end proj-card --}}
@empty
@@ -832,9 +834,11 @@ function buildCompanyCard(c) {
+ '' + cName + ''
+ 'Inactive'
+ '0 projects'
+ + '· 0 depts'
+ ''
+ ''
- + ''
+ + ''
+ + ''
+ ''
+ ''
+ '
'
@@ -846,6 +850,17 @@ function buildCompanyCard(c) {
+ ''
+ ''
+ ''
+ + ''
+ + '
'
+ + 'Departments'
+ + '
'
+ + '
'
+ + ''
+ + ''
+ + ''
+ + '
'
+ + '
'
+ + '
'
+ ''
+ '
'
+ '
'
@@ -885,22 +900,10 @@ function buildProjectCard(p, companyId) {
+ '
'
+ '
'
+ ''
- + '
'
- + '
'
- + '
'
- + ''
- + '
'
- + ''
- + ''
- + ''
- + '
'
- + '
'
- + '
'
+ + '
'
+ '
'
+ '
';
@@ -949,78 +952,80 @@ function insertLocInOrder(projectId, newWrapEl) {
if (!inserted) list.appendChild(newWrapEl);
}
-// ── Department CRUD ───────────────────────────────────────────────────────────
-function openAddDept(projectId) {
- var row = document.getElementById('dept-add-row-' + projectId);
+// ── Company Department CRUD ───────────────────────────────────────────────────
+var CO_DEPT_BASE = BASE + '/companies';
+
+function openAddCoDept(companyId) {
+ var row = document.getElementById('co-dept-add-row-' + companyId);
row.classList.add('open');
- document.getElementById('dept-add-name-' + projectId).value = '';
- document.getElementById('dept-add-name-' + projectId).focus();
+ document.getElementById('co-dept-add-name-' + companyId).value = '';
+ document.getElementById('co-dept-add-name-' + companyId).focus();
}
-function closeAddDept(projectId) {
- document.getElementById('dept-add-row-' + projectId).classList.remove('open');
+function closeAddCoDept(companyId) {
+ document.getElementById('co-dept-add-row-' + companyId).classList.remove('open');
}
-function saveDeptAdd(projectId) {
- var input = document.getElementById('dept-add-name-' + projectId);
+function saveCoDeptAdd(companyId) {
+ var input = document.getElementById('co-dept-add-name-' + companyId);
var name = input.value.trim();
if (!name) { showToast('Department name is required.', 'warn'); return; }
- api(BASE + '/' + projectId + '/departments', 'POST', { name: name })
+ api(CO_DEPT_BASE + '/' + companyId + '/departments', 'POST', { name: name })
.then(function(data) {
var dept = data.department;
DEPT_DATA[dept.id] = dept;
- closeAddDept(projectId);
- var emptyEl = document.getElementById('dept-empty-' + projectId);
+ closeAddCoDept(companyId);
+ var emptyEl = document.getElementById('co-dept-empty-' + companyId);
if (emptyEl) emptyEl.remove();
- var list = document.getElementById('dept-list-' + projectId);
+ var list = document.getElementById('co-dept-list-' + companyId);
var div = document.createElement('div');
- div.innerHTML = buildDeptRow(projectId, dept);
- insertDeptInOrder(projectId, div.firstElementChild);
- updateDeptCount(projectId);
+ div.innerHTML = buildCoDeptRow(companyId, dept);
+ insertCoDeptInOrder(companyId, div.firstElementChild);
+ updateCoDeptCount(companyId);
showToast('Department "' + esc(dept.name) + '" added.', 'success');
})
.catch(function(e) { showToast(firstError(e), 'error'); });
}
-function openEditDept(deptId, projectId, name, isActive) {
- document.getElementById('dept-row-' + deptId).style.display = 'none';
- var row = document.getElementById('dept-edit-row-' + deptId);
+function openEditCoDept(deptId, companyId, name, isActive) {
+ document.getElementById('co-dept-row-' + deptId).style.display = 'none';
+ var row = document.getElementById('co-dept-edit-row-' + deptId);
row.classList.add('open');
- document.getElementById('dept-edit-name-' + deptId).value = name;
- document.getElementById('dept-edit-active-' + deptId).checked = isActive;
- document.getElementById('dept-edit-name-' + deptId).focus();
+ document.getElementById('co-dept-edit-name-' + deptId).value = name;
+ document.getElementById('co-dept-edit-active-' + deptId).checked = isActive;
+ document.getElementById('co-dept-edit-name-' + deptId).focus();
}
-function closeEditDept(deptId) {
- document.getElementById('dept-edit-row-' + deptId).classList.remove('open');
- document.getElementById('dept-row-' + deptId).style.display = '';
+function closeEditCoDept(deptId) {
+ document.getElementById('co-dept-edit-row-' + deptId).classList.remove('open');
+ document.getElementById('co-dept-row-' + deptId).style.display = '';
}
-function saveDeptEdit(deptId, projectId) {
- var name = document.getElementById('dept-edit-name-' + deptId).value.trim();
- var isActive = document.getElementById('dept-edit-active-' + deptId).checked;
+function saveEditCoDept(deptId, companyId) {
+ var name = document.getElementById('co-dept-edit-name-' + deptId).value.trim();
+ var isActive = document.getElementById('co-dept-edit-active-' + deptId).checked;
if (!name) { showToast('Department name is required.', 'warn'); return; }
- api(BASE + '/' + projectId + '/departments/' + deptId, 'PATCH', { name: name, is_active: isActive ? 1 : 0 })
+ api(CO_DEPT_BASE + '/' + companyId + '/departments/' + deptId, 'PATCH', { name: name, is_active: isActive ? 1 : 0 })
.then(function(data) {
var dept = data.department;
DEPT_DATA[dept.id] = dept;
- closeEditDept(deptId);
- document.getElementById('dept-name-' + deptId).textContent = dept.name;
- var badge = document.getElementById('dept-inactive-' + deptId);
+ closeEditCoDept(deptId);
+ document.getElementById('co-dept-name-' + deptId).textContent = dept.name;
+ var badge = document.getElementById('co-dept-inactive-' + deptId);
if (badge) badge.style.display = dept.is_active ? 'none' : '';
- var icon = document.getElementById('dept-icon-' + deptId);
- if (icon) icon.setAttribute('stroke', dept.is_active ? '#06b6d4' : '#9ca3af');
+ var icon = document.getElementById('co-dept-icon-' + deptId);
+ if (icon) icon.setAttribute('stroke', dept.is_active ? '#7c3aed' : '#9ca3af');
showToast('Department updated.', 'success');
})
.catch(function(e) { showToast(firstError(e), 'error'); });
}
-function deleteDept(projectId, deptId, name) {
+function deleteCoDept(companyId, deptId, name) {
confirmAction('Delete Department', 'Delete "' + name + '"?', function() {
- api(BASE + '/' + projectId + '/departments/' + deptId, 'DELETE')
+ api(CO_DEPT_BASE + '/' + companyId + '/departments/' + deptId, 'DELETE')
.then(function() {
delete DEPT_DATA[deptId];
- var wrap = document.getElementById('dept-wrap-' + deptId);
+ var wrap = document.getElementById('co-dept-wrap-' + deptId);
if (wrap) wrap.remove();
- updateDeptCount(projectId);
- var list = document.getElementById('dept-list-' + projectId);
- if (list && !list.querySelector('[id^="dept-wrap-"]')) {
- list.innerHTML = 'No departments yet.
';
+ updateCoDeptCount(companyId);
+ var list = document.getElementById('co-dept-list-' + companyId);
+ if (list && !list.querySelector('[id^="co-dept-wrap-"]')) {
+ list.innerHTML = 'No departments yet.
';
}
showToast('Department "' + esc(name) + '" deleted.', 'success');
})
@@ -1028,20 +1033,20 @@ function deleteDept(projectId, deptId, name) {
});
}
-function updateDeptCount(projectId) {
- var list = document.getElementById('dept-list-' + projectId);
- var count = list ? list.querySelectorAll('[id^="dept-wrap-"]').length : 0;
- var el = document.getElementById('proj-dept-count-' + projectId);
- if (el) el.textContent = count + ' ' + (count === 1 ? 'dept' : 'depts');
+function updateCoDeptCount(companyId) {
+ var list = document.getElementById('co-dept-list-' + companyId);
+ var count = list ? list.querySelectorAll('[id^="co-dept-wrap-"]').length : 0;
+ var el = document.getElementById('company-dept-count-' + companyId);
+ if (el) el.textContent = '· ' + count + ' ' + (count === 1 ? 'dept' : 'depts');
}
-function insertDeptInOrder(projectId, newWrapEl) {
- var list = document.getElementById('dept-list-' + projectId);
- var newName = (newWrapEl.querySelector('[id^="dept-name-"]').textContent || '').trim().toLowerCase();
- var wraps = list.querySelectorAll('[id^="dept-wrap-"]');
+function insertCoDeptInOrder(companyId, newWrapEl) {
+ var list = document.getElementById('co-dept-list-' + companyId);
+ var newName = (newWrapEl.querySelector('[id^="co-dept-name-"]').textContent || '').trim().toLowerCase();
+ var wraps = list.querySelectorAll('[id^="co-dept-wrap-"]');
var inserted = false;
for (var i = 0; i < wraps.length; i++) {
- var nameEl = wraps[i].querySelector('[id^="dept-name-"]');
+ var nameEl = wraps[i].querySelector('[id^="co-dept-name-"]');
if (nameEl && newName < nameEl.textContent.trim().toLowerCase()) {
list.insertBefore(newWrapEl, wraps[i]);
inserted = true; break;
@@ -1050,25 +1055,25 @@ function insertDeptInOrder(projectId, newWrapEl) {
if (!inserted) list.appendChild(newWrapEl);
}
-function buildDeptRow(projectId, dept) {
+function buildCoDeptRow(companyId, dept) {
var safeName = (dept.name || '').replace(/\\/g,'\\\\').replace(/'/g,"\\'");
- return ''
- + '
'
+ return '
'
+ + '
'
+ '
'
- + '
'
- + '
' + esc(dept.name) + ''
- + '
Inactive'
+ + '
'
+ + '
' + esc(dept.name) + ''
+ + '
Inactive'
+ '
'
+ '
'
- + ''
- + ''
+ + ''
+ + ''
+ '
'
+ '
'
- + '
';
}
diff --git a/routes/web.php b/routes/web.php
index 228dfe8..c9595f0 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -144,9 +144,9 @@ Route::middleware(['auth', 'verified'])->group(function () {
Route::post('settings/projects/{project}/locations', [ProjectSettingController::class, 'storeLocation'])->name('settings.projects.locations.store');
Route::patch('settings/projects/{project}/locations/{location}', [ProjectSettingController::class, 'updateLocation'])->name('settings.projects.locations.update');
Route::delete('settings/projects/{project}/locations/{location}', [ProjectSettingController::class, 'destroyLocation'])->name('settings.projects.locations.destroy');
- Route::post('settings/projects/{project}/departments', [ProjectSettingController::class, 'storeDepartment'])->name('settings.projects.departments.store');
- Route::patch('settings/projects/{project}/departments/{department}', [ProjectSettingController::class, 'updateDepartment'])->name('settings.projects.departments.update');
- Route::delete('settings/projects/{project}/departments/{department}', [ProjectSettingController::class, 'destroyDepartment'])->name('settings.projects.departments.destroy');
+ Route::post('settings/projects/companies/{company}/departments', [ProjectSettingController::class, 'storeDepartment'])->name('settings.projects.companies.departments.store');
+ Route::patch('settings/projects/companies/{company}/departments/{department}', [ProjectSettingController::class, 'updateDepartment'])->name('settings.projects.companies.departments.update');
+ Route::delete('settings/projects/companies/{company}/departments/{department}', [ProjectSettingController::class, 'destroyDepartment'])->name('settings.projects.companies.departments.destroy');
});
});