Skip to content

Commit

Permalink
Merge branch 'develop' into api-expenses-summary
Browse files Browse the repository at this point in the history
  • Loading branch information
apoorv1316 authored Mar 8, 2024
2 parents 6746f6c + c8f6fb9 commit 3cb6760
Show file tree
Hide file tree
Showing 34 changed files with 1,593 additions and 5 deletions.
18 changes: 17 additions & 1 deletion app/controllers/internal_api/v1/expenses_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

class InternalApi::V1::ExpensesController < ApplicationController
before_action :set_expense, only: :show
before_action :set_expense, only: [:show, :update, :destroy]

def index
authorize Expense
Expand All @@ -27,6 +27,22 @@ def show
render :show, locals: { expense: Expense::ShowPresenter.new(@expense).process }
end

def update
authorize @expense

@expense.update!(expense_params)

render json: { notice: I18n.t("expenses.update") }, status: :ok
end

def destroy
authorize @expense

@expense.destroy!

render json: { notice: I18n.t("expenses.destroy") }, status: :ok
end

private

def expense_params
Expand Down
27 changes: 27 additions & 0 deletions app/javascript/src/apis/expenses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import axios from "./api";

const path = "/expenses";

const index = async () => await axios.get(path);

const create = async payload => await axios.post(path, payload);

const show = async id => await axios.get(`${path}/${id}`);

const update = async (id, payload) => axios.patch(`${path}/${id}`, payload);

const destroy = async id => axios.delete(`${path}/${id}`);

const createCategory = async payload =>
axios.post("/expense_categories", payload);

const expensesApi = {
index,
create,
show,
update,
destroy,
createCategory,
};

export default expensesApi;
138 changes: 138 additions & 0 deletions app/javascript/src/common/CustomCreatableSelect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* eslint-disable import/exports-last */
import React from "react";

import CreatableSelect from "react-select/creatable";

import {
customErrStyles,
customStyles,
CustomValueContainer,
} from "common/CustomReactSelectStyle";
import { useUserContext } from "context/UserContext";

type CustomCreatableSelectProps = {
id?: string;
styles?: any;
components?: any;
classNamePrefix?: string;
label?: string;
isErr?: any;
isSearchable?: boolean;
isDisabled?: boolean;
ignoreDisabledFontColor?: boolean;
hideDropdownIndicator?: boolean;
handleOnClick?: (e?: any) => void; // eslint-disable-line
handleOnChange?: (e?: any) => void; // eslint-disable-line
handleonFocus?: (e?: any) => void; // eslint-disable-line
onBlur?: (e?: any) => void; // eslint-disable-line
defaultValue?: object;
onMenuClose?: (e?: any) => void; // eslint-disable-line
onMenuOpen?: (e?: any) => void; // eslint-disable-line
className?: string;
autoFocus?: boolean;
value?: object;
getOptionLabel?: (e?: any) => any; // eslint-disable-line
wrapperClassName?: string;
options?: Array<any>;
name?: string;
};

export const CustomCreatableSelect = ({
id,
isSearchable,
classNamePrefix,
options,
label,
handleOnChange,
handleonFocus,
handleOnClick,
name,
value,
isErr,
isDisabled,
styles,
components,
onMenuClose,
onMenuOpen,
ignoreDisabledFontColor,
hideDropdownIndicator,
className,
autoFocus,
onBlur,
defaultValue,
getOptionLabel,
wrapperClassName,
}: CustomCreatableSelectProps) => {
const { isDesktop } = useUserContext();

const getStyle = () => {
if (isErr) {
return customErrStyles(isDesktop);
}

return customStyles(
isDesktop,
ignoreDisabledFontColor,
hideDropdownIndicator
);
};

return (
<div
className={`outline relative ${wrapperClassName}`}
onClick={handleOnClick}
>
<CreatableSelect
autoFocus={autoFocus}
className={className}
classNamePrefix={classNamePrefix}
defaultValue={defaultValue}
getOptionLabel={getOptionLabel}
id={id || name}
isDisabled={isDisabled}
isSearchable={isSearchable}
name={name}
options={options}
placeholder={label}
styles={styles || getStyle()}
value={value}
components={{
...components,
ValueContainer: CustomValueContainer,
IndicatorSeparator: () => null,
}}
onBlur={onBlur}
onChange={handleOnChange}
onFocus={handleonFocus}
onMenuClose={onMenuClose}
onMenuOpen={onMenuOpen}
/>
</div>
);
};

CustomCreatableSelect.defaultProps = {
id: "",
styles: null,
components: null,
classNamePrefix: "react-select-filter",
label: "Select",
isErr: false,
isSearchable: true,
isDisabled: false,
ignoreDisabledFontColor: false,
hideDropdownIndicator: false,
handleOnClick: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
handleOnChange: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
handleonFocus: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
onBlur: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
defaultValue: null,
onMenuClose: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
onMenuOpen: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
className: "",
autoFocus: false,
value: null,
wrapperClassName: "",
};

export default CustomCreatableSelect;
14 changes: 14 additions & 0 deletions app/javascript/src/common/Mobile/AddEditModalHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";

import { XIcon } from "miruIcons";

const AddEditModalHeader = ({ title, handleOnClose }) => (
<div className="flex items-center justify-between bg-miru-han-purple-1000 p-3 text-white">
<span className="w-full pl-6 text-center text-base font-medium leading-5 text-white">
{title}
</span>
<XIcon className="text-white" size={16} onClick={handleOnClose} />
</div>
);

export default AddEditModalHeader;
60 changes: 60 additions & 0 deletions app/javascript/src/components/Expenses/Details/Expense.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from "react";

import { currencyFormat } from "helpers";

const Expense = ({ expense, currency }) => (
<div className="mt-8 flex flex-col px-4 lg:px-0">
<div className="flex flex-col">
<span className="text-xs font-medium text-miru-dark-purple-1000">
Amount
</span>
<span className="text-4xl font-normal text-miru-dark-purple-1000">
{currencyFormat(currency, expense?.amount)}
</span>
</div>
<div className="my-10 flex w-full flex-wrap items-center justify-between gap-y-10">
<div className="flex w-1/2 flex-col lg:w-1/4">
<span className="text-xs font-medium text-miru-dark-purple-1000">
Date
</span>
<span className="text-base font-medium text-miru-dark-purple-1000">
{expense?.date || "-"}
</span>
</div>
<div className="flex w-1/2 flex-col lg:w-1/4">
<span className="text-xs font-medium text-miru-dark-purple-1000">
Vendor
</span>
<span className="text-base font-medium text-miru-dark-purple-1000">
{expense?.vendorName || "-"}
</span>
</div>
<div className="flex w-1/2 flex-col lg:w-1/4">
<span className="text-xs font-medium text-miru-dark-purple-1000">
Type
</span>
<span className="text-base font-medium text-miru-dark-purple-1000">
{expense?.type || "-"}
</span>
</div>
<div className="flex w-1/2 flex-col lg:w-1/4">
<span className="text-xs font-medium text-miru-dark-purple-1000">
Receipt
</span>
<span className="text-base font-medium text-miru-dark-purple-1000">
{expense?.receipt || "-"}
</span>
</div>
</div>
<div className="flex flex-col">
<span className="text-xs font-medium text-miru-dark-purple-1000">
Description
</span>
<span className="text-base font-medium text-miru-dark-purple-1000">
{expense?.description || "-"}
</span>
</div>
</div>
);

export default Expense;
64 changes: 64 additions & 0 deletions app/javascript/src/components/Expenses/Details/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from "react";

import { ArrowLeftIcon, EditIcon, DeleteIcon } from "miruIcons";
import { useNavigate } from "react-router-dom";
import { Button } from "StyledComponents";

const Header = ({ expense, handleEdit, handleDelete }) => {
const navigate = useNavigate();

return (
<div className="flex h-12 items-center justify-between px-4 shadow-c1 lg:my-6 lg:h-auto lg:px-0 lg:shadow-none">
<div className="flex items-center">
<Button
className="mr-2"
style="ternary"
onClick={() => navigate("/expenses")}
>
<ArrowLeftIcon
className="text-miru-dark-purple-1000"
size={20}
weight="bold"
/>
</Button>
<span className="text-base font-bold text-miru-dark-purple-1000 lg:text-32">
{expense?.categoryName}
</span>
</div>
<div className="hidden lg:flex">
<Button
className="mr-4 flex w-24 items-center justify-center rounded p-2"
style="secondary"
onClick={handleEdit}
>
<EditIcon className="mr-2" size={16} weight="bold" />
<span className="text-base font-medium"> Edit</span>
</Button>
<Button
className="flex w-24 items-center justify-center rounded border border-miru-red-400 p-2 text-miru-red-400"
style="ternary"
onClick={handleDelete}
>
<DeleteIcon
className="mr-2 text-miru-red-400"
size={16}
weight="bold"
/>
<span className="text-base font-medium text-miru-red-400">
Delete
</span>
</Button>
</div>
<div className="flex lg:hidden">
<Button className="p-2" style="ternary" onClick={handleEdit}>
<EditIcon size={16} weight="bold" />
</Button>
<Button className="p-2" style="ternary" onClick={handleDelete}>
<DeleteIcon className="text-miru-red-400" size={16} weight="bold" />
</Button>
</div>
</div>
);
};

export default Header;
Loading

0 comments on commit 3cb6760

Please sign in to comment.