Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

57 design add inventory page #68

Merged
merged 11 commits into from
Mar 7, 2024
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@types/react-dates": "^21.8.6",
"@types/textarea-caret": "^3.0.3",
"framer-motion": "^10.16.4",
"hangul-js": "^0.2.6",
"immer": "^10.0.3",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-dates": "^21.8.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.15.0",
"textarea-caret": "^3.1.0"
Expand Down
5 changes: 5 additions & 0 deletions src/assets/icon-complete-edit-text.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icon-edit-text.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icon-list.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 15 additions & 11 deletions src/components/@base/Stack.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { CSSProperties, HTMLAttributes } from 'react';
import { CSSProperties, HTMLAttributes, forwardRef, useRef } from 'react';

export interface StackProps extends HTMLAttributes<HTMLDivElement> {
justify?: CSSProperties['justifyContent'];
align?: CSSProperties['alignItems'];
spacing?: CSSProperties['gap'];
}

function Stack({
children,
justify: justifyContent = 'center',
align: alignItems = 'stretch',
spacing: gap,
...props
}: StackProps) {
export default forwardRef<HTMLDivElement, StackProps>(function Stack(
{
children,
justify: justifyContent = 'center',
align: alignItems = 'stretch',
spacing: gap,
...props
}: StackProps,
ref,
) {
const localRef = useRef(null);
const inputRef = ref || localRef;
return (
<div
css={{
Expand All @@ -22,11 +27,10 @@ function Stack({
alignItems,
gap,
}}
ref={inputRef}
{...props}
>
{children}
</div>
);
}

export default Stack;
});
2 changes: 1 addition & 1 deletion src/pages/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const mockTagData = [
{ value: 'ingr-5', label: '# 재료 5' },
];

const mockRecipeData: RecipeProps[] = Array(14).fill({
export const mockRecipeData: RecipeProps[] = Array(14).fill({
author: '해피밀',
contents: `소스 : 스리라차 소스, 요거트 소스
반죽 : 밀가루, 계란
Expand Down
288 changes: 288 additions & 0 deletions src/pages/inventory/components/IngrInputBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
import { ReactComponent as IconBack } from '@/assets/icon-back.svg';
import { ReactComponent as IconDelete } from '@/assets/icon-minus.svg';
import DesignSystem from '@/utils/designSystem';
import { useComposing } from '@/utils/hooks';
import globalStyles from '@/utils/styles';
import { Group, Typography } from '@base';
import { css } from '@emotion/react';
import { motion } from 'framer-motion';
import moment, { Moment } from 'moment';
import 'moment/dist/locale/ko';
import { HTMLAttributes, useEffect, useRef, useState } from 'react';
import { SingleDatePicker } from 'react-dates';
import 'react-dates/initialize';
import 'react-dates/lib/css/_datepicker.css';
import { IngredientDataType } from '..';
import '../react_dates_overrides.css';
const styles = {
box: css({
backgroundColor: DesignSystem.Color.background.gray,
width: 1080,
borderRadius: '12px',
padding: '17.5px 24px',
boxSizing: 'border-box',
}),
input: {
default: css(DesignSystem.Text.body, {
color: DesignSystem.Color.background.black,
width: 222,
height: 35,
lineHeight: '35px',
padding: 0,
'&::placeholder': { color: DesignSystem.Color.text.gray },
}),
calender: css(
globalStyles.center,
globalStyles.button,
DesignSystem.Text.subtitle,
{ width: '100%', height: 65 },
),
},
ingredients: css({
backgroundColor: DesignSystem.Color.background.white,
padding: '61px 0 61px 43px',
}),
};
interface IngrInputBoxProps extends HTMLAttributes<HTMLDivElement> {
type: string;
item?: IngredientDataType;
submit?: boolean;
handleDataChange?: (inputData: IngredientDataType) => void;
hanldeEdit?: (input: IngredientDataType) => void;
handleRemove?: () => void;
}
function IngrInputBox({
type,
item,
submit,
handleDataChange,
hanldeEdit,
handleRemove,
style,
...props
}: IngrInputBoxProps) {
moment.locale('ko');
const [registrationDate, setRegistrationDate] = useState<Moment>(
moment().locale('ko'),
);
const [expirationDate, setExpirationDate] = useState<Moment | null>(null);

const [inputData, setInputData] = useState<IngredientDataType>(
item
? {
name: item.name,
quantity: item.quantity,
registrationDate: item.registrationDate,
expirationDate: item.expirationDate,
}
: {
name: '',
quantity: '',
registrationDate: registrationDate,
expirationDate: expirationDate,
},
);
const [isComposing, composeProps] = useComposing();

const handleKeyDown =
type === 'input'
? (e: React.KeyboardEvent) => {
if (isComposing) return;

if (e.code === 'Enter' && inputData.name) {
handleDataChange && handleDataChange(inputData);
setRegistrationDate(moment().locale('ko'));
setExpirationDate(null);
setInputData({
name: '',
quantity: '',
registrationDate: registrationDate,
expirationDate: expirationDate,
});
inputRef.current?.focus();
}
}
: undefined;

useEffect(() => {
if (submit) {
hanldeEdit && hanldeEdit(inputData);
}
}, [type]);
const inputRef = useRef<HTMLInputElement>(null);
return type === 'input' || type === 'edit' ? (
<Group css={styles.box} position="apart" style={style}>
{type === 'edit' && (
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
duration: 0.2,
delay: 0.1,
}}
style={{ width: 35, height: 35 }}
>
<IconDelete
css={{
width: 35,
height: 35,
'&>rect': {
fill: DesignSystem.Color.warning.red,
},
}}
onClick={() => {
handleRemove && handleRemove();
}}
/>
</motion.div>
)}

<input
type="text"
value={inputData.name}
onChange={(e) => {
setInputData({ ...inputData, name: e.target.value });
}}
onKeyDown={handleKeyDown}
ref={inputRef}
css={styles.input.default}
placeholder="재료를 입력해주세요."
{...composeProps}
/>
<input
type="text"
value={inputData.quantity}
onChange={(e) => {
setInputData({ ...inputData, quantity: e.target.value });
}}
onKeyDown={handleKeyDown}
css={styles.input.default}
placeholder="수량을 입력해주세요."
{...composeProps}
/>
<CustomCalender
inputDate={inputData.registrationDate}
setInputDate={(date: Moment) => {
setInputData({ ...inputData, registrationDate: date });
}}
targetRef={inputRef}
>
등록일자 설정
</CustomCalender>
<CustomCalender
inputDate={inputData.expirationDate}
setInputDate={(date: Moment) => {
setInputData({ ...inputData, expirationDate: date });
}}
placeholder="유통기한 날짜를 선택해주세요."
targetRef={inputRef}
>
유통기한 설정
</CustomCalender>
</Group>
) : (
<Group css={styles.box} gap={48}>
<Typography variant="body" css={styles.input.default}>
{inputData.name}
</Typography>
<Typography variant="body" css={styles.input.default}>
{inputData.quantity}
</Typography>
<Typography variant="body" css={styles.input.default}>
{inputData.registrationDate.format('YYYY/MM/DD').toString()}
</Typography>
<Typography variant="body" css={styles.input.default}>
{inputData.expirationDate
? inputData.expirationDate.format('YYYY/MM/DD').toString()
: ''}
</Typography>
</Group>
);
}

export default IngrInputBox;

interface CustomCalenderProps extends HTMLAttributes<HTMLDivElement> {
children: string;
inputDate: Moment | null;
setInputDate: (date: Moment) => void;
targetRef: React.RefObject<HTMLInputElement>;
}

const CustomCalender = ({
children,
inputDate,
setInputDate,
targetRef,
...props
}: CustomCalenderProps) => {
const [focus, setFocus] = useState(false);
return (
<SingleDatePicker
date={inputDate}
onDateChange={(date) => {
date && setInputDate(date);
}}
focused={focus}
onFocusChange={({ focused }) => {
setFocus(focused);
}}
noBorder={true}
displayFormat="YYYY/MM/DD"
monthFormat="YYYY M월"
numberOfMonths={1}
navPrev={<IconBack css={{ position: 'absolute', top: 20, left: 79 }} />}
navNext={
<IconBack
css={{
transform: 'rotate(180deg)',
position: 'absolute',
top: 20,
right: 79,
}}
/>
}
onClose={() => {
targetRef.current?.focus();
}}
renderCalendarInfo={() => {
return (
<div
style={{
background: `${
inputDate
? DesignSystem.Color.primary.yellow
: DesignSystem.Color.background.disabled
}`,
color: `${
inputDate
? DesignSystem.Color.text.black
: DesignSystem.Color.text.gray
}`,
cursor: `${inputDate ? 'pointer' : 'default'}`,
}}
css={styles.input.calender}
onClick={
inputDate
? () => {
setFocus(false);
targetRef.current?.focus();
}
: undefined
}
>
{children}
</div>
);
}}
hideKeyboardShortcutsPanel
enableOutsideDays
keepOpenOnDateSelect
appendToBody
disableScroll
daySize={48}
{...props}
id="unique-id"
/>
);
};
Loading
Loading