feat: team-based visibility, roles management, unified users, UI fixes
Deploy / deploy (push) Successful in 12s

- Add Roles table with CRUD routes and Settings page management
- Unify user management: remove Users page, enhance Team page with
  permission level + role dropdowns
- Add team-based visibility scoping to projects, campaigns, posts,
  tasks, issues, artefacts, and dashboard
- Add team_id to projects and campaigns (create + edit forms)
- Add getUserTeamIds/getUserVisibilityContext helpers
- Fix Budgets modal horizontal scroll (separate linked-to row)
- Add collapsible filter bar to PostProduction page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-04 15:55:15 +03:00
parent 7c6e8dce08
commit da161014af
14 changed files with 655 additions and 308 deletions
+17 -16
View File
@@ -24,7 +24,7 @@ const Projects = lazy(() => import('./pages/Projects'))
const ProjectDetail = lazy(() => import('./pages/ProjectDetail'))
const Tasks = lazy(() => import('./pages/Tasks'))
const Team = lazy(() => import('./pages/Team'))
const Users = lazy(() => import('./pages/Users'))
// Users page removed — unified into Team page
const Settings = lazy(() => import('./pages/Settings'))
const Brands = lazy(() => import('./pages/Brands'))
const Login = lazy(() => import('./pages/Login'))
@@ -37,18 +37,11 @@ const PublicIssueTracker = lazy(() => import('./pages/PublicIssueTracker'))
const ForgotPassword = lazy(() => import('./pages/ForgotPassword'))
const ResetPassword = lazy(() => import('./pages/ResetPassword'))
const TEAM_ROLES = [
// Permission levels (access control)
export const PERMISSION_LEVELS = [
{ value: 'superadmin', label: 'Super Admin' },
{ value: 'manager', label: 'Manager' },
{ value: 'approver', label: 'Approver' },
{ value: 'publisher', label: 'Publisher' },
{ value: 'content_creator', label: 'Content Creator' },
{ value: 'producer', label: 'Producer' },
{ value: 'designer', label: 'Designer' },
{ value: 'content_writer', label: 'Content Writer' },
{ value: 'social_media_manager', label: 'Social Media Manager' },
{ value: 'photographer', label: 'Photographer' },
{ value: 'videographer', label: 'Videographer' },
{ value: 'strategist', label: 'Strategist' },
{ value: 'contributor', label: 'Contributor' },
]
export const AppContext = createContext()
@@ -59,6 +52,7 @@ function AppContent() {
const [teamMembers, setTeamMembers] = useState([])
const [brands, setBrands] = useState([])
const [teams, setTeams] = useState([])
const [roles, setRoles] = useState([])
const [loading, setLoading] = useState(true)
const [showTutorial, setShowTutorial] = useState(false)
const [showProfilePrompt, setShowProfilePrompt] = useState(false)
@@ -115,12 +109,22 @@ function AppContent() {
}
}
const loadRoles = async () => {
try {
const data = await api.get('/roles')
setRoles(Array.isArray(data) ? data : [])
} catch (err) {
console.error('Failed to load roles:', err)
}
}
const loadInitialData = async () => {
try {
const [, brandsData] = await Promise.all([
loadTeam(),
api.get('/brands').then(d => Array.isArray(d) ? d : []).catch(() => []),
loadTeams(),
loadRoles(),
])
setBrands(brandsData)
} catch (err) {
@@ -151,7 +155,7 @@ function AppContent() {
}
return (
<AppContext.Provider value={{ currentUser: user, teamMembers, brands, loadTeam, getBrandName, teams, loadTeams }}>
<AppContext.Provider value={{ currentUser: user, teamMembers, brands, loadTeam, getBrandName, teams, loadTeams, roles, loadRoles }}>
{/* Profile completion prompt */}
{showProfilePrompt && (
<div className="fixed top-4 right-4 z-50 bg-amber-50 border-2 border-amber-400 rounded-xl shadow-lg p-4 max-w-md animate-fade-in">
@@ -312,9 +316,6 @@ function AppContent() {
{hasModule('issues') && <Route path="issues" element={<Issues />} />}
<Route path="team" element={<Team />} />
<Route path="settings" element={<Settings />} />
{user?.role === 'superadmin' && (
<Route path="users" element={<Users />} />
)}
</Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>