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
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { TextField } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import type { ChangeEvent, CSSProperties, JSX } from "react";

import SimpleFormCard from "@shared/ui/project-insert/SimpleFormCard";

interface ProjectDetailDescriptionCardProps {
value: string;
onChange: (value: string) => void;
large?: boolean;
style?: CSSProperties;
}

const ProjectDetailDescriptionCard = ({
value,
onChange,
large,
style,
}: ProjectDetailDescriptionCardProps): JSX.Element => {
const theme = useTheme();

const handleChange = (e: ChangeEvent<HTMLTextAreaElement>): void => {
onChange(e.target.value);
};

return (
<SimpleFormCard
title="프로젝트 상세 설명"
description="프로젝트에 대해 자세히 설명해주세요. 목표, 주요 기능, 기대 효과 등을 포함해주세요!"
helpText="마크다운 형식으로 작성하면 더 보기 좋아요!"
large={large}
style={style}
>
<TextField
value={value}
onChange={handleChange}
placeholder={`## 🎯 프로젝트 목표
AI 기술을 활용하여 개인별 학습 패턴을 분석하고, 최적화된 학습 계획을 제공하는 모바일 애플리케이션을 개발합니다.

## ✨ 주요 기능
- 개인 맞춤형 학습 계획 생성
- 학습 진도 및 성취도 추적
- AI 기반 문제 추천 시스템

## 🚀 기대 효과
- 개인화된 학습으로 학습 효율성 30% 향상
- 학습 동기 부여 및 지속성 증대`}
multiline // 여러 줄 입력 가능
minRows={large ? 12 : 8}
maxRows={large ? 20 : 15}
fullWidth
variant="outlined"
sx={{
"& .MuiOutlinedInput-root": {
fontSize: large
? theme.typography.h5.fontSize
: theme.typography.body1.fontSize,
fontFamily: "monospace",
lineHeight: 1.6,
padding: 0,
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.text.primary,
},
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.primary.main,
borderWidth: "2px",
},
},
"& .MuiOutlinedInput-input": {
padding: large ? theme.spacing(2.5) : theme.spacing(2),
resize: "vertical",
},
}}
/>
</SimpleFormCard>
);
};

export default ProjectDetailDescriptionCard;
154 changes: 154 additions & 0 deletions src/entities/projects/ui/project-insert/ProjectPreferentialCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import AddIcon from "@mui/icons-material/Add";
import { Box, Button } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import type {
ChangeEvent,
CSSProperties,
JSX,
FocusEvent,
MouseEvent,
} from "react";
import { useState } from "react";

import SimpleFormCard from "@shared/ui/project-insert/SimpleFormCard";

interface ProjectPreferentialCardProps {
value: string[];
onChange: (value: string[]) => void;
large?: boolean;
style?: CSSProperties;
}

const ProjectPreferentialCard = ({
value,
onChange,
large,
style,
}: ProjectPreferentialCardProps): JSX.Element => {
const theme = useTheme();
const [newPreferential, setNewPreferential] = useState("");

const addPreferential = (): void => {
if (newPreferential.trim() && !value.includes(newPreferential.trim())) {
onChange([...value, newPreferential.trim()]);
setNewPreferential("");
}
};

const removePreferential = (preferentialToRemove: string): void => {
onChange(value.filter((pref) => pref !== preferentialToRemove));
};

return (
<SimpleFormCard
title="우대사항"
description="있으면 좋은 기술이나 경험이 있나요?"
helpText="우대사항은 선택사항입니다. 있으면 더 좋은 조건들을 적어주세요"
large={large}
style={style}
>
{/* 입력 + 추가 버튼 */}
<Box display="flex" gap={1} mb={2}>
<input
type="text"
value={newPreferential}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setNewPreferential(e.target.value)
}
placeholder="예: AWS, Docker, 스타트업 경험..."
style={{
flex: 1,
height: 40,
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,
fontSize: large
? theme.typography.h5.fontSize
: theme.typography.body1.fontSize,
fontFamily: theme.typography.fontFamily,
background: theme.palette.background.paper,
padding: large ? theme.spacing(2.2) : theme.spacing(1.7),
boxSizing: "border-box",
outline: "none",
transition: "border-color 0.2s ease-in-out",
}}
onFocus={(e) => {
e.target.style.borderColor = theme.palette.primary.main;
e.target.style.borderWidth = "2px";
}}
onBlur={(e: FocusEvent<HTMLInputElement>) => {
e.currentTarget.style.borderColor = theme.palette.divider;
e.currentTarget.style.borderWidth = "1px";
}}
onMouseEnter={(e: MouseEvent<HTMLInputElement>) => {
if (e.currentTarget !== document.activeElement) {
e.currentTarget.style.borderColor = "#000000";
}
}}
onMouseLeave={(e: MouseEvent<HTMLInputElement>) => {
if (e.currentTarget !== document.activeElement) {
e.currentTarget.style.borderColor = theme.palette.divider;
}
}}
/>
<Button
variant="contained"
onClick={addPreferential}
disabled={!newPreferential.trim()}
sx={{
minWidth: 48,
height: 46,
backgroundColor: "#2563EB",
"&:hover": { backgroundColor: "#1d4ed8" },
}}
>
<AddIcon />
</Button>
</Box>

{/* 우대사항 태그들 */}
{value.length > 0 && (
<Box display="flex" flexWrap="wrap" gap={1}>
{value.map((preferential, index) => (
<Box
key={index}
sx={{
display: "inline-flex",
alignItems: "center",
gap: 1,
px: 2,
py: 1,
backgroundColor: theme.palette.grey[100],
borderRadius: "999px",
fontSize: "1.5rem",
color: theme.palette.text.primary,
}}
>
<span>{preferential}</span>
<Box
component="button"
onClick={() => removePreferential(preferential)}
sx={{
width: 18,
height: 18,
borderRadius: "100%",
border: "none",
color: theme.palette.text.primary,
backgroundColor: "transparent",
cursor: "pointer",
fontSize: "18px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
×
</Box>
</Box>
))}
</Box>
)}
</SimpleFormCard>
);
};

export default ProjectPreferentialCard;
141 changes: 141 additions & 0 deletions src/entities/projects/ui/project-insert/ProjectRequirementsCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import AddIcon from "@mui/icons-material/Add";
import DeleteIcon from "@mui/icons-material/Delete";
import { Box, IconButton, TextField, Button } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import type { ChangeEvent, CSSProperties, JSX } from "react";

import SimpleFormCard from "@shared/ui/project-insert/SimpleFormCard";

interface ProjectRequirementsCardProps {
value: string[];
onChange: (value: string[]) => void;
large?: boolean;
style?: CSSProperties;
}

const ProjectRequirementsCard = ({
value,
onChange,
large,
style,
}: ProjectRequirementsCardProps): JSX.Element => {
const theme = useTheme();

const displayValue = value.length === 0 ? [""] : value;

const handleInputChange = (index: number, newValue: string): void => {
const newArr = [...displayValue];
newArr[index] = newValue;
onChange(newArr);
};

const addInput = (): void => {
onChange([...displayValue, ""]);
};

const removeInput = (index: number): void => {
if (displayValue.length <= 1) return;
const newArr = displayValue.filter((_, i) => i !== index);
onChange(newArr);
};

return (
<SimpleFormCard
title="지원 요구사항"
description="팀원에게 바라는 필수 조건들을 적어주세요"
helpText="필수 조건들을 하나씩 추가해주세요 (최소 1개 이상)"
large={large}
style={style}
>
<Box display="flex" flexDirection="column" gap={2}>
{displayValue.map((requirement, index) => (
<Box
key={index}
sx={{
display: "grid",
gridTemplateColumns: "1fr auto",
alignItems: "center",
gap: 1,
px: 0,
py: 0,
}}
>
<TextField
value={requirement}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleInputChange(index, e.target.value)
}
placeholder="예: 주 2-3회 온라인 미팅 참여 가능"
fullWidth
variant="outlined"
size="small"
sx={{
"& .MuiOutlinedInput-root": {
height: 40,
fontSize: large
? theme.typography.h5.fontSize
: theme.typography.body1.fontSize,
fontFamily: theme.typography.fontFamily,
background: "none",
border: "none",

// 호버 시 테두리 검정색
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.text.primary,
},
// 포커스 시 테두리 primary 색상
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.primary.main,
borderWidth: "2px",
},
},
"& .MuiOutlinedInput-input": {
padding: large ? theme.spacing(2.2) : theme.spacing(1.7),
},
}}
/>
<IconButton
onClick={() => removeInput(index)}
color="error"
size="small"
disabled={displayValue.length <= 1}
sx={{
ml: 0.5,
"&:hover": {
backgroundColor: "transparent",
},
}}
>
<DeleteIcon fontSize="large" />
</IconButton>
</Box>
))}
<Box display="flex" justifyContent="center">
<Button
variant="text"
startIcon={<AddIcon />}
onClick={addInput}
sx={{
px: 2.5,
py: 1,
borderRadius: 2,
borderStyle: "dashed",
borderWidth: "2px",
borderColor: theme.palette.primary.main,
color: theme.palette.primary.main,
fontWeight: 600,
"&:hover": {
backgroundColor: `${theme.palette.primary.main}08`,
transform: "scale(1.02)",
},
}}
>
입력란 추가
</Button>
</Box>
</Box>
</SimpleFormCard>
);
};

export default ProjectRequirementsCard;
Loading