Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lang/en/projects.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
'title' => '',
'activity' => '',
'comments' => '',
'tabs' => [
'equipo' => 'Team',
'actividad' => 'Activity',
'comentarios' => 'Comments',
],
],
'dashboard' => [
'title' => '',
Expand Down
5 changes: 5 additions & 0 deletions lang/es/projects.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
'title' => 'Proyecto',
'activity' => 'Actividad del proyecto',
'comments' => 'Comentarios',
'tabs' => [
'equipo' => 'Equipo',
'actividad' => 'Actividad',
'comentarios' => 'Comentarios',
],
],
'dashboard' => [
'title' => 'Dashboard',
Expand Down
35 changes: 35 additions & 0 deletions resources/js/components/ui/proyectos/ProjectRoles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,41 @@ export default function ProjectRoles({
/>
</div>
)}

{/* Team members */}
{(() => {
const members = roles.flatMap((role) =>
role.volunteers.map((v) => ({ ...v, roleTitle: role.title })),
)
if (members.length === 0) return null
return (
<div className="mt-4 border-t border-gray-200 pt-4 dark:border-white/10">
<h4 className="text-sm font-semibold text-gray-900 dark:text-white">{t('projects.team.title')}</h4>
<ul
role="list"
className="mt-4 grid grid-cols-3 gap-x-4 gap-y-6 sm:grid-cols-4 md:grid-cols-5"
>
{members.map((member) => (
<li key={member.id} className="text-center">
{member.avatarUrl ? (
<img
alt=""
src={member.avatarUrl}
className="mx-auto size-14 rounded-full object-cover outline outline-1 -outline-offset-1 outline-black/5 dark:outline-white/10"
/>
) : (
<div className="mx-auto flex size-14 items-center justify-center rounded-full bg-indigo-100 text-xl font-semibold text-indigo-600 outline outline-1 -outline-offset-1 outline-black/5 dark:bg-indigo-500/20 dark:text-indigo-400 dark:outline-white/10">
{member.name.charAt(0).toUpperCase()}
</div>
)}
<p className="mt-2 text-xs font-semibold text-gray-900 dark:text-white">{member.name}</p>
<p className="text-xs text-gray-500 dark:text-gray-400">{member.roleTitle}</p>
</li>
))}
</ul>
</div>
)
})()}
</div>

<ApplicantReviewModal
Expand Down
158 changes: 96 additions & 62 deletions resources/js/pages/proyectos/show.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import ProjectStageActions, {
type AllowedTransition,
} from '@/components/ui/proyectos/ProjectStageActions';
import { type ProjectStageValue } from '@/components/ui/proyectos/ProjectStageBadge';
import ProjectTeam from '@/components/ui/proyectos/ProjectTeam';
import ProjectTimeline from '@/components/ui/proyectos/ProjectTimeline';
import { type TimelineEntry } from '@/components/ui/proyectos/ProjectTimelineEntry';
import WelcomeSidebar from '@/components/ui/WelcomeSidebar';
import { t } from '@/lib/i18n';
import { cn } from '@/lib/utils';

interface ProjectImage {
id: number;
Expand Down Expand Up @@ -80,6 +80,8 @@ interface ProyectoShowProps {
timeline: TimelineData;
}

type TabId = 'equipo' | 'actividad' | 'comentarios';

export default function ProyectoShow({
project,
post,
Expand All @@ -91,6 +93,7 @@ export default function ProyectoShow({
const isAuthenticated = !!auth?.user;

const [sidebarOpen, setSidebarOpen] = useState(false);
const [activeTab, setActiveTab] = useState<TabId>('equipo');
const [commentsList, setCommentsList] = useState<Comment[] | null>(null);
const [commentsLoading, setCommentsLoading] = useState(false);

Expand All @@ -113,11 +116,19 @@ export default function ProyectoShow({
};

useEffect(() => {
loadComments().catch(() => {
setCommentsLoading(false);
setCommentsList([]);
});
}, []);
if (activeTab === 'comentarios') {
loadComments().catch(() => {
setCommentsLoading(false);
setCommentsList([]);
});
}
}, [activeTab]);

const tabs: { id: TabId; labelKey: string }[] = [
{ id: 'equipo', labelKey: 'projects.show.tabs.equipo' },
{ id: 'actividad', labelKey: 'projects.show.tabs.actividad' },
{ id: 'comentarios', labelKey: 'projects.show.tabs.comentarios' },
];

return (
<div className="min-h-screen bg-white dark:bg-gray-950">
Expand Down Expand Up @@ -171,71 +182,94 @@ export default function ProyectoShow({
isOwner={isOwner}
projectId={project.id}
/>
</div>

{/* Progress (with stage transition actions for owner) */}
<div className="mt-6">
<CrowdfundingProgress
coins={post.coins}
roles={project.roles}
>
{isOwner &&
project.allowedTransitions.length > 0 && (
<ProjectStageActions
projectId={project.id}
allowedTransitions={
project.allowedTransitions
}
/>
)}
</CrowdfundingProgress>
</div>
{/* Tab navigation */}
<div className="border-b border-gray-200 dark:border-white/10">
<nav className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
<ul role="list" className="-mb-px flex gap-x-6">
{tabs.map((tab) => (
<li key={tab.id}>
<button
type="button"
onClick={() => setActiveTab(tab.id)}
className={cn(
tab.id === activeTab
? 'border-indigo-600 text-indigo-600 dark:border-indigo-500 dark:text-indigo-400'
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-600 dark:hover:text-gray-200',
'border-b-2 px-1 py-3 text-sm font-semibold whitespace-nowrap transition-colors',
)}
>
{t(tab.labelKey)}
</button>
</li>
))}
</ul>
</nav>
</div>

{/* Roles */}
<ProjectRoles
projectId={project.id}
projectStage={project.stage}
roles={project.roles}
isOwner={isOwner}
isAuthenticated={isAuthenticated}
currentUserApplication={currentUserApplication}
/>
<div className="mx-auto max-w-3xl px-4 py-6 sm:px-6 lg:px-8">
{/* Tab: Equipo */}
{activeTab === 'equipo' && (
<div className="space-y-6">
{/* Progress (with stage transition actions for owner) */}
<CrowdfundingProgress
coins={post.coins}
roles={project.roles}
>
{isOwner &&
project.allowedTransitions.length > 0 && (
<ProjectStageActions
projectId={project.id}
allowedTransitions={
project.allowedTransitions
}
/>
)}
</CrowdfundingProgress>

{/* Team */}
<ProjectTeam roles={project.roles} />

{/* Timeline */}
<div className="mt-6 border-t border-gray-200 pt-6 dark:border-white/10">
<h2 className="text-sm font-semibold text-gray-900 dark:text-white">
{t('projects.show.activity')}
</h2>
<ProjectTimeline
projectId={project.id}
initialEntries={timeline.entries}
initialNextCursor={timeline.nextCursor}
isOwner={isOwner}
/>
</div>
{/* Roles buscados */}
<ProjectRoles
projectId={project.id}
projectStage={project.stage}
roles={project.roles}
isOwner={isOwner}
isAuthenticated={isAuthenticated}
currentUserApplication={currentUserApplication}
/>

{/* Comments */}
<div className="mt-6 border-t border-gray-200 pt-6 dark:border-white/10">
<h2 className="text-sm font-semibold text-gray-900 dark:text-white">
{t('projects.show.comments')}
</h2>
{commentsLoading ? (
<div className="mt-4 space-y-3">
<div className="h-4 w-3/4 animate-pulse rounded bg-gray-200 dark:bg-white/10" />
<div className="h-4 w-1/2 animate-pulse rounded bg-gray-200 dark:bg-white/10" />
</div>
) : commentsList !== null ? (
<div className="mt-4">
</div>
)}

{/* Tab: Actividad */}
{activeTab === 'actividad' && (
<div>
<ProjectTimeline
projectId={project.id}
initialEntries={timeline.entries}
initialNextCursor={timeline.nextCursor}
isOwner={isOwner}
/>
</div>
)}

{/* Tab: Comentarios */}
{activeTab === 'comentarios' && (
<div>
{commentsLoading ? (
<div className="mt-4 space-y-3">
<div className="h-4 w-3/4 animate-pulse rounded bg-gray-200 dark:bg-white/10" />
<div className="h-4 w-1/2 animate-pulse rounded bg-gray-200 dark:bg-white/10" />
</div>
) : commentsList !== null ? (
<PostComments
storeUrl={projectStore.url(project.id)}
comments={commentsList}
onCommentAdded={refreshComments}
/>
</div>
) : null}
</div>
) : null}
</div>
)}
</div>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions tests/Browser/ProjectTimelineComposerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
->press('Iniciar sesión');

visit("/projects/{$post->id}")
->click('Actividad')
->click('Actualización')
->fill('body', 'shipping the timeline composer')
->press('Publicar')
Expand Down
Loading