Skip to content

Commit

Permalink
🤏🏽 Add export CSV button (#451)
Browse files Browse the repository at this point in the history
* added initial resource breadcrumb

* fixing links and linting

* linting

* fixing bugs

* sorry jackie
  • Loading branch information
Lam7150 authored May 16, 2021
1 parent 3d12115 commit 2c04ea5
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 49 deletions.
1 change: 1 addition & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"http-errors": "~1.8.0",
"if-env": "^1.0.4",
"isomorphic-unfetch": "^3.0.0",
"json2csv": "^5.0.6",
"mongodb": "^3.3.2",
"mongoose": "^5.7.5",
"morgan": "~1.10.0"
Expand Down
72 changes: 72 additions & 0 deletions api/src/api/category.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const express = require('express');
const router = express.Router();
const { errorWrap } = require('../middleware');
const Category = require('../models/category');
const Resource = require('../models/resource');
const { Parser } = require('json2csv');

// Get all categories
router.get(
Expand All @@ -17,4 +19,74 @@ router.get(
}),
);

// Download all resources
router.get(
'/download',
errorWrap(async (req, res) => {
let resource = await Resource.find({});

const transformToReadable = (data) => {
// Transforms data to readable csv format
let res = JSON.parse(JSON.stringify(data._doc));

// Remove ids from fields
if (res.phoneNumbers)
res.phoneNumbers.forEach((phone) => delete phone._id);
if (res.financialAidDetails) delete res.financialAidDetails._id;
if (res.hoursOfOperation) {
delete res.hoursOfOperation._id;
if (res.hoursOfOperation.hoursOfOperation.length == 0)
delete res.hoursOfOperation;
else {
res.hoursOfOperation = res.hoursOfOperation.hoursOfOperation;
res.hoursOfOperation.forEach((hoo) => {
delete hoo._id;
});
}
}

// Remove brackets from Arrays and empty Objects
Object.entries(res).forEach(([key, value]) => {
if (Array.isArray(value)) res[key] = JSON.stringify(value).slice(1, -1);
else if (typeof value === 'object' && Object.keys(value).length === 0)
res[key] = '';
});

return res;
};

const opts = {
transforms: [transformToReadable],
fields: [
'category',
'subcategory',
'requiredDocuments',
'name',
'description',
'website',
'email',
'phoneNumbers',
'contacts',
'address',
'city',
'eligibilityRequirements',
'financialAidDetails',
'cost',
'internalNotes',
'hoursOfOperation',
'lastUpdated',
'addressLine2',
'aptUnitSuite',
'image',
'state',
'zip',
],
};

const json2csv = new Parser(opts);
const csv = json2csv.parse(resource);
res.type('text/csv').attachment('all_resources.csv').send(csv);
}),
);

module.exports = router;
24 changes: 24 additions & 0 deletions api/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"

commander@^6.1.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==

component-emitter@^1.2.1, component-emitter@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
Expand Down Expand Up @@ -2890,13 +2895,27 @@ json-stringify-safe@~5.0.1:
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=

json2csv@^5.0.6:
version "5.0.6"
resolved "https://registry.yarnpkg.com/json2csv/-/json2csv-5.0.6.tgz#590e0e1b9579e59baa53bda0c0d840f4d8009687"
integrity sha512-0/4Lv6IenJV0qj2oBdgPIAmFiKKnh8qh7bmLFJ+/ZZHLjSeiL3fKKGX3UryvKPbxFbhV+JcYo9KUC19GJ/Z/4A==
dependencies:
commander "^6.1.0"
jsonparse "^1.3.1"
lodash.get "^4.4.2"

json5@^2.1.2:
version "2.1.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
dependencies:
minimist "^1.2.5"

jsonparse@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=

jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
Expand Down Expand Up @@ -2974,6 +2993,11 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"

lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=

lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
Expand Down
116 changes: 72 additions & 44 deletions client/src/components/ResourceManager.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// @flow

import React, { useCallback, useEffect, useState } from 'react';
import { Menu } from 'antd';
import { DownOutlined, UpOutlined } from '@ant-design/icons';
import { Menu, Button } from 'antd';
import { DownOutlined, UpOutlined, ExportOutlined } from '@ant-design/icons';

import { getCategories, getResources } from '../utils/api';
import type { Subcategory } from '../types/models';

import ManageResourcesTable from './ManageResourcesTable';
import ResourcesBreadcrumb from './ResourcesBreadcrumb';

import '../css/ResourceManager.css';
import EditCategoryModal from './EditCategoryModal';
Expand Down Expand Up @@ -200,51 +201,78 @@ const ResourceManager = () => {

return (
<div className="resource-manager-flexbox">
<div className="resource-manager-sidebar">
<Menu
mode="inline"
selectedKeys={
selectedSubcategory === '' ? selectedCategory : selectedSubcategory
}
openKeys={openKeys}
onOpenChange={onOpenChange}
expandIcon={<div />}
>
<Menu.Item key="All Resources" onClick={categorySelectAll}>
All Resources
</Menu.Item>
{Object.keys(categories).map((categoryName) => (
<SidebarCategory
categories={categories}
categoryName={categoryName}
key={categoryName}
isOpen={categoryName === selectedCategory}
updateView={updateView}
setSelectedCategory={setSelectedCategory}
setSelectedSubcategory={setSelectedSubcategory}
<div className="resource-manager-header">
<div className="resource-manager-header-title"> Categories </div>
<div className="resource-manager-header-content">
<div className="resource-manager-header-breadcrumb">
<ResourcesBreadcrumb
categorySelected={selectedCategory}
subcategorySelected={selectedSubcategory}
tColor="#000000"
/>
))}
<Menu.Item
key="Add Category"
className="resource-manager-sidebar-category"
</div>
<Button
className="resource-manager-header-export"
icon={<ExportOutlined />}
onClick={(e) => {
e.preventDefault();
window.location.href =
'https://nawc-staging.vercel.app/api/categories/download';
}}
>
Add Category
<EditCategoryModal
modalType="add"
categoryType="category"
updateView={updateView}
/>
</Menu.Item>
</Menu>
Export
</Button>
</div>
</div>
<div className="resource-manager-table">
<ManageResourcesTable
categories={categories}
selectedCategory={selectedCategory}
selectedSubcategory={selectedSubcategory}
resources={resources}
updateView={updateView}
/>
<div className="resource-manager-content">
<div className="resource-manager-sidebar">
<Menu
mode="inline"
selectedKeys={
selectedSubcategory === ''
? selectedCategory
: selectedSubcategory
}
openKeys={openKeys}
onOpenChange={onOpenChange}
expandIcon={<div />}
>
<Menu.Item key="All Resources" onClick={categorySelectAll}>
All Resources
</Menu.Item>
{Object.keys(categories).map((categoryName) => (
<SidebarCategory
categories={categories}
categoryName={categoryName}
key={categoryName}
isOpen={categoryName === selectedCategory}
updateView={updateView}
setSelectedCategory={setSelectedCategory}
setSelectedSubcategory={setSelectedSubcategory}
/>
))}
<Menu.Item
key="Add Category"
className="resource-manager-sidebar-category"
>
Add Category
<EditCategoryModal
modalType="add"
categoryType="category"
updateView={updateView}
/>
</Menu.Item>
</Menu>
</div>
<div className="resource-manager-table">
<ManageResourcesTable
categories={categories}
selectedCategory={selectedCategory}
selectedSubcategory={selectedSubcategory}
resources={resources}
updateView={updateView}
/>
</div>
</div>
</div>
);
Expand Down
19 changes: 14 additions & 5 deletions client/src/components/ResourcesBreadcrumb.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ import { allResourcesMessage } from '../utils/messages';
import '../css/ResourcesBreadcrumb.css';

function ResourcesBreadcrumb(props) {
const { categorySelected, subcategorySelected, resourceSelected } = props;

const {
categorySelected,
subcategorySelected,
resourceSelected,
tColor,
} = props;
const breadcrumbs = [];

if (categorySelected === 'All Resources') {
breadcrumbs.push(
<span className="default-crumb" key="all">
<span className="default-crumb" style={{ color: tColor }} key="all">
<FormattedMessage {...allResourcesMessage} />
</span>,
);
Expand All @@ -27,7 +31,7 @@ function ResourcesBreadcrumb(props) {
pathname: '/resources',
}}
>
<span>
<span style={{ color: tColor }}>
<FormattedMessage {...allResourcesMessage} />
</span>
</Link>,
Expand All @@ -38,6 +42,7 @@ function ResourcesBreadcrumb(props) {
&nbsp;&gt;&nbsp;
<Link
className="link"
style={{ color: tColor }}
to={{
pathname: '/resources',
search: `?category=${categorySelected}`,
Expand All @@ -57,6 +62,7 @@ function ResourcesBreadcrumb(props) {
&nbsp;&gt;&nbsp;
<Link
className="link"
style={{ color: tColor }}
to={{
pathname: '/resources',
search:
Expand All @@ -79,7 +85,7 @@ function ResourcesBreadcrumb(props) {
);
} else {
breadcrumbs.push(
<span key="sub">
<span style={{ color: tColor }} key="sub">
&nbsp;&gt;&nbsp;
<strong>
<FormattedMessage
Expand All @@ -96,6 +102,7 @@ function ResourcesBreadcrumb(props) {
&nbsp;&gt;&nbsp;
<strong>
<FormattedMessage
style={{ color: tColor }}
id={`category-${categorySelected?.replace(/\s/g, '')}`}
defaultMessage={categorySelected}
/>
Expand All @@ -110,12 +117,14 @@ function ResourcesBreadcrumb(props) {

ResourcesBreadcrumb.defaultProps = {
resourceSelected: '',
tColor: '#FFFFFF',
};

ResourcesBreadcrumb.propTypes = {
categorySelected: PropTypes.string.isRequired,
subcategorySelected: PropTypes.string.isRequired,
resourceSelected: PropTypes.string,
tColor: PropTypes.string,
};

export default ResourcesBreadcrumb;
Loading

0 comments on commit 2c04ea5

Please sign in to comment.