diff --git a/src/App.jsx b/src/App.jsx index f5429f3..55ba702 100755 --- a/src/App.jsx +++ b/src/App.jsx @@ -17,6 +17,7 @@ import CreateInvoice from './pages/team/CreateInvoice'; import InvoiceDetail from './pages/team/InvoiceDetail'; import Settings from './pages/Settings'; +import ClientDashboard from './pages/client/ClientDashboard'; import MyCompany from './pages/client/MyCompany'; import MyRequests from './pages/client/MyRequests'; import MyProjects from './pages/client/MyProjects'; @@ -46,6 +47,7 @@ export default function App() { } /> + } /> } /> } /> } /> diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx index 1324cf8..78f47da 100755 --- a/src/components/Layout.jsx +++ b/src/components/Layout.jsx @@ -25,6 +25,7 @@ function ClientNav({ onNav }) {
My Work
{[ + { to: '/my-dashboard', label: 'Dashboard' }, { to: '/my-projects', label: 'Projects' }, { to: '/my-invoices', label: 'Invoices' }, { to: '/my-company', label: 'Company' }, diff --git a/src/components/ProtectedRoute.jsx b/src/components/ProtectedRoute.jsx index 9833bec..5e9f415 100755 --- a/src/components/ProtectedRoute.jsx +++ b/src/components/ProtectedRoute.jsx @@ -5,7 +5,7 @@ export default function ProtectedRoute({ children, role }) { const { currentUser } = useAuth(); if (!currentUser) return ; if (role && currentUser.role !== role) { - return ; + return ; } return children; } diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 501d9ac..512a083 100755 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -14,7 +14,7 @@ export default function Login() { useEffect(() => { if (currentUser) { - navigate(currentUser.role === 'team' ? '/dashboard' : '/my-company', { replace: true }); + navigate(currentUser.role === 'team' ? '/dashboard' : '/my-dashboard', { replace: true }); } }, [currentUser, navigate]); diff --git a/src/pages/client/ClientDashboard.jsx b/src/pages/client/ClientDashboard.jsx new file mode 100644 index 0000000..6328233 --- /dev/null +++ b/src/pages/client/ClientDashboard.jsx @@ -0,0 +1,110 @@ +import { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import Layout from '../../components/Layout'; +import StatusBadge from '../../components/StatusBadge'; +import { supabase } from '../../lib/supabase'; +import { useAuth } from '../../context/AuthContext'; + +export default function ClientDashboard() { + const { currentUser } = useAuth(); + const company = currentUser?.company; + const [projects, setProjects] = useState([]); + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!company?.id) { setLoading(false); return; } + async function load() { + const { data: p } = await supabase + .from('projects').select('*').eq('company_id', company.id).order('created_at', { ascending: false }); + const projectList = p || []; + setProjects(projectList); + + if (projectList.length > 0) { + const { data: t } = await supabase + .from('tasks').select('*') + .in('project_id', projectList.map(pr => pr.id)); + setTasks(t || []); + } + setLoading(false); + } + load(); + }, [company?.id]); + + if (loading) return

Loading...

; + + const notStarted = tasks.filter(t => t.status === 'not_started').length; + const inProgress = tasks.filter(t => ['in_progress', 'on_hold'].includes(t.status)).length; + const awaitingReview = tasks.filter(t => t.status === 'client_review').length; + const completed = tasks.filter(t => t.status === 'client_approved').length; + + return ( + +
+
+
Dashboard
+
Welcome back, {currentUser?.name?.split(' ')[0]}.
+
+ + New Request +
+ +
+
+
{notStarted}
+
Not Started
+
+
+
{inProgress}
+
In Progress
+
+
+
0 ? 'var(--accent)' : undefined }}>{awaitingReview}
+
Awaiting Review
+
+
+
{completed}
+
Completed
+
+
+ + {projects.length === 0 ? ( +
+

No projects yet

+

Submit a request to get started.

+ + New Request +
+ ) : ( + <> +
Projects
+
+ {projects.map(project => { + const projectTasks = tasks.filter(t => t.project_id === project.id); + const pendingReview = projectTasks.filter(t => t.status === 'client_review').length; + const active = projectTasks.filter(t => ['in_progress', 'on_hold', 'not_started'].includes(t.status)).length; + return ( + +
0 ? 'var(--accent)' : 'var(--border)'}`, + borderRadius: 8, background: 'var(--card-bg)', + display: 'flex', alignItems: 'center', justifyContent: 'space-between', + }}> +
+
{project.name}
+
+ {projectTasks.length} job{projectTasks.length !== 1 ? 's' : ''} + {active > 0 && <> · {active} active} + {pendingReview > 0 && · {pendingReview} awaiting review} +
+
+ +
+ + ); + })} +
+ + )} +
+ ); +} diff --git a/src/pages/client/MyCompany.jsx b/src/pages/client/MyCompany.jsx index 3256427..a40ed27 100644 --- a/src/pages/client/MyCompany.jsx +++ b/src/pages/client/MyCompany.jsx @@ -1,7 +1,5 @@ import { useState, useEffect } from 'react'; -import { Link } from 'react-router-dom'; import Layout from '../../components/Layout'; -import StatusBadge from '../../components/StatusBadge'; import { supabase } from '../../lib/supabase'; import { useAuth } from '../../context/AuthContext'; @@ -9,110 +7,102 @@ export default function MyCompany() { const { currentUser } = useAuth(); const company = currentUser?.company; const [members, setMembers] = useState([]); - const [projects, setProjects] = useState([]); - const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); + const [editing, setEditing] = useState(false); + const [form, setForm] = useState({ name: '', phone: '', address: '' }); + const [saving, setSaving] = useState(false); useEffect(() => { if (!company?.id) { setLoading(false); return; } + setForm({ name: company.name || '', phone: company.phone || '', address: company.address || '' }); async function load() { - const [{ data: m }, { data: p }] = await Promise.all([ - supabase.from('profiles').select('id, name, email').eq('company_id', company.id).eq('role', 'client'), - supabase.from('projects').select('*').eq('company_id', company.id).order('created_at', { ascending: false }), - ]); + const { data: m } = await supabase + .from('profiles').select('id, name, email').eq('company_id', company.id).eq('role', 'client'); setMembers(m || []); - const projectList = p || []; - setProjects(projectList); - - if (projectList.length > 0) { - const { data: t } = await supabase - .from('tasks').select('*') - .in('project_id', projectList.map(pr => pr.id)); - setTasks(t || []); - } setLoading(false); } load(); }, [company?.id]); - if (loading) return

Loading...

; + const handleSave = async (e) => { + e.preventDefault(); + setSaving(true); + await supabase.from('companies').update({ + name: form.name.trim(), + phone: form.phone.trim(), + address: form.address.trim(), + }).eq('id', company.id); + setSaving(false); + setEditing(false); + }; - const notStarted = tasks.filter(t => t.status === 'not_started').length; - const inProgress = tasks.filter(t => ['in_progress', 'on_hold'].includes(t.status)).length; - const awaitingReview = tasks.filter(t => t.status === 'client_review').length; - const completed = tasks.filter(t => t.status === 'client_approved').length; + if (loading) return

Loading...

; return (
-
{company?.name || 'Your Company'}
+
{form.name || company?.name}
{[company?.phone, company?.address].filter(Boolean).join(' · ') || 'No contact info on file'}
+ {!editing && ( + + )}
- {/* Stats */} -
-
-
{notStarted}
-
Not Started
-
-
-
{inProgress}
-
In Progress
-
-
-
0 ? 'var(--accent)' : undefined }}>{awaitingReview}
-
Awaiting Review
-
-
-
{completed}
-
Completed
-
-
- - {/* Projects */} - {projects.length > 0 && ( -
-
Projects
-
- {projects.map(project => { - const projectTasks = tasks.filter(t => t.project_id === project.id); - const pendingReview = projectTasks.filter(t => t.status === 'client_review').length; - const active = projectTasks.filter(t => ['in_progress', 'on_hold', 'not_started'].includes(t.status)).length; - return ( - -
0 ? 'var(--accent)' : 'var(--border)'}`, borderRadius: 8, background: 'var(--card-bg)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> -
-
{project.name}
-
- {projectTasks.length} job{projectTasks.length !== 1 ? 's' : ''} - {active > 0 && <> · {active} active} - {pendingReview > 0 && · {pendingReview} awaiting review} -
-
- -
- - ); - })} -
+ {editing && ( +
+
Edit Company Info
+
+
+ + setForm(f => ({ ...f, name: e.target.value }))} + required + /> +
+
+
+ + setForm(f => ({ ...f, phone: e.target.value }))} + /> +
+
+ + setForm(f => ({ ...f, address: e.target.value }))} + /> +
+
+
+ + +
+
)} - {/* Team members */}
People
{members.length === 0 ? (

No members found.

) : ( -
+
{members.map((member, i) => (