Skip to content
Open
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
11 changes: 11 additions & 0 deletions frontend/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SignupPage from '../features/auth/pages/Signup';
import NovaIniciativaPage from '../features/iniciativas/pages/CadastroIniciativas'
import DetalheIniciativaPage from '../features/iniciativas/pages/DetalheIniciativa';
import InitivativePage from '../features/iniciativas/pages/MinhasIniciativas';
import IdeasPage from '../features/ideias/pages/IdeasPage';

import { ThemeProvider, createTheme, CssBaseline } from '@mui/material';

Expand Down Expand Up @@ -74,6 +75,16 @@ const router = createBrowserRouter([
</RequireAuth>
),
},
{
path: '/forum ideias',
element: (
<RequireAuth>
<ProfileGate>
<IdeasPage />
</ProfileGate>
</RequireAuth>
),
},
{
path: '/profile',
element: (
Expand Down
67 changes: 67 additions & 0 deletions frontend/src/features/ideias/components/CommentsSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
Box,
Typography,
IconButton ,
Avatar,
TextField,
CardContent,
} from "@mui/material";
import { UserHeader } from '../../../shared/components/UserHeader';
import { TextContent } from '../../../shared/components/TextContent';
import { useState } from "react";
import SendIcon from "@mui/icons-material/Send";

interface Comment {
id: number;
user: { avatarUrl: string; name: string; username: string };
content: { title: string; description: string };
}

export default function CommentsSection({ comments }: { comments: Comment[] }) {
const [text, setText] = useState("");
return (
<CardContent>
<Box sx={{ display: "flex", alignItems: "flex-end", width: "100%", position: "relative" }}>
<TextField
fullWidth
placeholder="Adicionar um comentário"
variant="outlined"
size="small"
multiline
minRows={1}
maxRows={6}
value={text}
onChange={(e) => setText(e.target.value)}
sx={{
"& textarea": {
overflowY: "auto",
pr: 1
}
}}
InputProps={{
endAdornment: text.length > 0 && (
<IconButton
size="small"
onClick={() => setText("")}
sx={{ alignSelf: "flex-end", pl: 3 }}
>
<SendIcon fontSize="small" />
</IconButton>
)
}}
/>
</Box>

<Typography variant="subtitle1" gutterBottom sx={{ mt: 4 }}>
Todos os comentários
</Typography>

{comments.map((c) => (
<Box key={c.id} mt={6}>
<UserHeader {...c.user} />
<TextContent {...c.content} />
</Box>
))}
</CardContent>
);
}
70 changes: 70 additions & 0 deletions frontend/src/features/ideias/components/IdeaCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
Card,
CardContent,
Divider,
Typography,
Box,
Button,
} from "@mui/material";
import { useState } from "react";
import { UserHeader } from '../../../shared/components/UserHeader';
import { TextContent } from '../../../shared/components/TextContent';
import CommentsSection from "./CommentsSection";

interface IdeaCardProps {
id: number;
user: { avatarUrl: string; name: string; username: string };
content: { title: string; description: string };
likes: number;
comments: {
id: number;
user: { avatarUrl: string; name: string; username: string };
content: { title: string; description: string };
}[];
}

export default function IdeaCard({ user, content, likes, comments }: IdeaCardProps) {
const [expanded, setExpanded] = useState(false);

return (
<Card sx={{ p: 3 } }>
<CardContent>
<UserHeader {...user} />
<TextContent {...content} />
</CardContent>

<Divider />

{/* Likes e comentários count */}
<Box p={2} display="flex" justifyContent="flex-end">
<Typography variant="body2">
{likes} curtidas • {comments.length} comentários
</Typography>
</Box>

<Divider />

{/* Botões */}
<Box display="flex" justifyContent="center" gap={20} p={2}>
<Button
variant="text"
size="large"
sx={{ color: "black", fontWeight: "bold", textTransform: "none" }}
>
X Gostei
</Button>

<Button
variant="text"
size="large"
sx={{ color: "black", fontWeight: "bold", textTransform: "none" }}
onClick={() => setExpanded(!expanded)}
>
X Comentar
</Button>
</Box>

{expanded && <CommentsSection comments={comments} />}
</Card>
);
}
171 changes: 171 additions & 0 deletions frontend/src/features/ideias/components/IdeasFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import {
Card,
CardContent,
CardHeader,
IconButton,
FormControlLabel,
Checkbox,
Typography,
Divider,
TextField,
Grid,
Box
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { useState } from "react";
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
import { ptBR } from "date-fns/locale";

export interface IdeasFilterProps {
categories?: string[];
onFilterChange: (filters: {
categories: string[];
startDate: Date | null;
endDate: Date | null;
minLikes: number | null;
}) => void;
}

export const IdeasFilter: React.FC<IdeasFilterProps> = ({
categories = [],
onFilterChange
}) => {
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
const [selectedPopularities, setSelectedPopularities] = useState<string[]>([]);
const [startDate, setStartDate] = useState<Date | null>(null);
const [endDate, setEndDate] = useState<Date | null>(null);

const popularities = ["Mais populares", "Recentes"];

const updateFilters = (updates: Partial<{
categories: string[];
popularities: string[];
startDate: Date | null;
endDate: Date | null;
}>) => {
const newCategories = updates.categories ?? selectedCategories;
const newPopularities = updates.popularities ?? selectedPopularities;

const newFilters = {
categories: newCategories,
startDate: updates.startDate ?? startDate,
endDate: updates.endDate ?? endDate,
minLikes: newPopularities.includes("Mais populares") ? 5 : null, // exemplo: >=5 curtidas
};

onFilterChange(newFilters);
};

const toggle = (
value: string,
selected: string[],
setSelected: (v: string[]) => void,
key: "categories" | "popularities"
) => {
const updated = selected.includes(value)
? selected.filter((v) => v !== value)
: [...selected, value];
setSelected(updated);

if (key === "categories") updateFilters({ categories: updated });
else updateFilters({ popularities: updated });
};

const clearFilters = () => {
setSelectedCategories([]);
setSelectedPopularities([]);
setStartDate(null);
setEndDate(null);
onFilterChange({
categories: [],
startDate: null,
endDate: null,
minLikes: null
});
};

const renderTwoColumnCheckboxes = (
items: string[],
selected: string[],
setSelected: (v: string[]) => void,
key: "categories" | "popularities"
) => (
<Grid container>
{items.map((item) => (
<Grid item xs={6} key={item}>
<FormControlLabel
control={
<Checkbox
checked={selected.includes(item)}
onChange={() => toggle(item, selected, setSelected, key)}
/>
}
label={item}
/>
</Grid>
))}
</Grid>
);

return (
<Card sx={{ width: "100%", backgroundColor: "#fff" }}>
<CardHeader
title={<Typography variant="h6" sx={{ fontWeight: "bold" }}>Filtrar</Typography>}
action={
<IconButton onClick={clearFilters}>
<CloseIcon />
</IconButton>
}
/>
<Divider />
<CardContent>
{/* Categorias */}
<Box mb={3}>
<Typography variant="subtitle1" sx={{ fontWeight: "bold" }} gutterBottom>
Categorias
</Typography>
{renderTwoColumnCheckboxes(categories, selectedCategories, setSelectedCategories, "categories")}
</Box>

{/* Popularidade */}
<Box mb={3}>
<Typography variant="subtitle1" sx={{ fontWeight: "bold" }} gutterBottom>
Popularidade
</Typography>
{renderTwoColumnCheckboxes(popularities, selectedPopularities, setSelectedPopularities, "popularities")}
</Box>

{/* Data */}
<Box>
<Typography variant="subtitle1" sx={{ fontWeight: "bold" }} gutterBottom>
Data
</Typography>
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={ptBR}>
<DatePicker
label="Data inicial"
value={startDate}
sx={{ mr: 1, mb: 1 }}
onChange={(date) => {
setStartDate(date);
updateFilters({ startDate: date });
}}
renderInput={(params: any) => <TextField fullWidth {...params} sx={{ mb: 1 }} />}
/>
<DatePicker
label="Data final"
value={endDate}
onChange={(date) => {
setEndDate(date);
updateFilters({ endDate: date });
}}
renderInput={(params: any) => <TextField fullWidth {...params} />}
/>
</LocalizationProvider>
</Box>
</CardContent>
</Card>
);
};

export default IdeasFilter;
30 changes: 30 additions & 0 deletions frontend/src/features/ideias/components/NewIdeaCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Card, CardContent, Avatar, Button, Box } from "@mui/material";

export default function NewIdeaCard() {
return (
<Card sx={{ p: 3 }}>
<CardContent sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Avatar src="/meu-avatar.png" sx={{ width: 48, height: 48, mr: 2 }} />
<Button
fullWidth
variant="outlined"
size="medium"
sx={{
justifyContent: "flex-start",
textAlign: "left",
textTransform: "none",
borderColor: "#D9D9D9",
color: "#2B2727",
"&:hover": {
borderColor: "#D9D9D9",
backgroundColor: "#f5f5f5", // opcional, pode remover se não quiser cor de fundo
color: "#2B2727"
}
}}
>
Poste uma ideia
</Button>
</CardContent>
</Card>
);
}
Loading