-1

We have the following Panel to manually add/delete choices inside a SharePoint column named Category:-

enter image description here

and here is the related .tsx code for the above Panel:-

import * as React from "react";

import {
  Stack,
  ProgressIndicator,
  Panel,
  PanelType,
  DefaultButton,
  AnimationStyles,
  mergeStyles,
  TextField,
  PrimaryButton,
  Dropdown,
  IDropdownOption,
  MessageBar,
  MessageBarType,
  Label,
  Text,
  ILabelStyles,
  Link,
  IconButton,
} from "office-ui-fabric-react";

import { _copyAndSort } from "../../controls/helpers";
import * as moment from "moment";
import * as strings from "DocumentsViewerWebPartStrings";
import { IReadonlyTheme } from "@microsoft/sp-component-base";
import Dropzone from "../../controls/DropzoneExport";
import { IDocument } from "../../models/IDocument";

export interface ICategoriesPanelProps {
  themeVariant: IReadonlyTheme | undefined;
  showPanel: boolean;
  hidePanel: () => void;
  categories: string[];
  addCategory: (category: string) => void;
  removeCategory: (category: string) => void;
  castFiletoIDoc: (file: File) => IDocument;
}

export interface ICategoriesPanelState {
  busy: boolean;
  newCategory: string;
  uploadPlaceholders: IDocument[];
}

export default class CategoriesPanel extends React.Component<ICategoriesPanelProps, ICategoriesPanelState> {
  constructor(props: ICategoriesPanelProps) {
    super(props);

    this.state = { busy: true, newCategory: null ,uploadPlaceholders: []};
  
  }

  public componentDidMount(): void {
    this.setState({ busy: false });
  }

  private handleNewCategoryFieldChange = (e, newValue: string) => {
    this.setState({ newCategory: newValue });
  };

  private add = async () => {
    this.setState({ busy: true });

    await this.props.addCategory(this.state.newCategory);

    this.setState({ busy: false, newCategory: null });
  };

  private remove = async (category: string) => {
    this.setState({ busy: true });

    if (category) {
      this.props.removeCategory(category);
    }

    this.setState({ busy: false });
  };
  private onDrop = (moreFiles) => {
    const placeholders = [...this.state.uploadPlaceholders];

    moreFiles.forEach((file, i) => {
      const idoc = this.props.castFiletoIDoc(file);

      placeholders.push({
        ...idoc,
        key: i.toString(),
      });
    });
    this.setState({ uploadPlaceholders: [...placeholders] });

    // Upload the file
    //this.props.uploadFolderIcon(moreFiles[0], this.props.folder);
  };

  private removeDocument = (document: IDocument) => {
    this.setState({ uploadPlaceholders: [] });
  };
  public render(): React.ReactElement<ICategoriesPanelProps> {
    const appearingStyle = mergeStyles(AnimationStyles.scaleDownIn100);

    return (
      <Panel
        headerText={strings.ManageCategories}
        type={PanelType.medium}
        isOpen={this.props.showPanel}
        onDismiss={this.props.hidePanel}
        // You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
        closeButtonAriaLabel={strings.Close}
        isBlocking={true}
        hasCloseButton={true}
      >
        <Stack tokens={{ childrenGap: 15 }}>
        <Stack.Item>
            <Dropzone
              themeVariant={this.props.themeVariant}
              onDrop={this.onDrop}
              uploadPlaceholders={this.state.uploadPlaceholders}
              removeDocument={this.removeDocument}
            />
            {/* <PrimaryButton
              text={strings.StartUpload}
              onClick={this.uploadDocuments}
              disabled={this.state.uploading || this.state.uploadFiles.length === 0}
            /> */}
          </Stack.Item>
          <Stack.Item align="end">
            {this.props.categories.length} {strings.Categories.toLowerCase()}
          </Stack.Item>

          <Stack.Item>
            <Stack tokens={{ childrenGap: 24 }}>
              <Stack.Item
                styles={{
                  root: {
                    padding: "10px 20px",
                    backgroundColor: this.props.themeVariant.palette.neutralLight,
                  },
                }}
              >
                <Stack tokens={{ childrenGap: 4 }}>
                  <Stack.Item>
                    {this.props.categories.map((category, i) => (
                      <Stack
                        tokens={{ childrenGap: 6 }}
                        horizontal
                        horizontalAlign="space-between"
                        styles={{
                          root: {
                            alignItems: "center",
                          },
                        }}
                        className={appearingStyle}
                      >
                        <Stack.Item>{category}</Stack.Item>
                        <IconButton
                          iconProps={{ iconName: "Delete" }}
                          title={`${strings.Remove} ${category}`}
                          onClick={() => this.remove(category)}
                          disabled={this.state.busy}
                        />
                      </Stack>
                    ))}
                  </Stack.Item>
                  <Stack.Item>
                    <Stack
                      tokens={{ childrenGap: 6 }}
                      horizontal
                      horizontalAlign="space-between"
                      styles={{
                        root: {
                          alignItems: "center",
                        },
                      }}
                      className={appearingStyle}
                    >
                      <Stack.Item>
                        <TextField
                          label={strings.AddNewCategory}
                          name="newCategory"
                          value={this.state.newCategory}
                          onChange={this.handleNewCategoryFieldChange}
                          disabled={this.state.busy}
                          styles={{ root: { width: 300 } }}
                        />
                      </Stack.Item>
                      <IconButton
                        iconProps={{ iconName: "Add" }}
                        title={`${strings.Add} ${this.state.newCategory}`}
                        onClick={this.add}
                        disabled={this.state.busy}
                      />
                    </Stack>
                  </Stack.Item>
                </Stack>
              </Stack.Item>
            </Stack>
          </Stack.Item>
        </Stack>
      </Panel>
    );
  }
}

currently the SPFx allow to manually add/edit the choices, but my question is how we can read the uploaded excel sheet file (which will contain the choices) inside the DropZone, loop through the choices >> remove existing choices and add the ones inside the sheet? Can anyone advice please?

Here is the DropZoneExport.tsx:-

import * as React from "react";

import { Stack, IStyle } from "office-ui-fabric-react";
import { IReadonlyTheme } from "@microsoft/sp-component-base";
import * as strings from "DocumentsViewerWebPartStrings";
import { IDocument } from "../models/IDocument";
import DocumentRow from "./DocumentRow";
import { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";

export interface IDropzoneExportProps {
  themeVariant: IReadonlyTheme | undefined;
  onDrop: (files) => void;
  uploadPlaceholders: IDocument[];
  removeDocument: (document: IDocument) => void;
}

export interface IDocumentsDropzoneExportState {
  files: any[];
}

export default function DropzoneExport(props: IDropzoneExportProps) {
  // https://www.npmjs.com/package/react-dropzone
  const onDrop = useCallback(async (acceptedFiles) => {
    // Do something with the files
    console.log("something dropped");

    props.onDrop(acceptedFiles);
  }, []);

  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    onDrop,
    maxFiles: 1,
    accept: {
      "text/csv*": [".csv"],
      //acceptedFiles={[".csv, text/csv, application/vnd.ms-excel, application/csv, text/x-csv, application/x-csv, text/comma-separated-values, text/x-comma-separated-values"]}
    },
  });

  const dropBoxStyle: IStyle = {
    border: "1px dashed",
    borderColor: props.themeVariant.semanticColors.inputBorder,
    padding: "0.5rem 1rem",
    marginBottom: ".5rem",
    backgroundColor: props.themeVariant.palette.neutralQuaternary,
  };

  return (
    <Stack>
      <Stack.Item styles={{ root: dropBoxStyle }}>
        <div {...getRootProps()} style={{ outline: "none" }}>
          <input {...getInputProps()} />
          {isDragActive ? <p>{strings.Item_DropHere}</p> : <p>{strings.Item_DropInfo}</p>}

          <div
            onClick={(e) => {
              e.preventDefault();
              e.stopPropagation();
            }}
          >
            {props.uploadPlaceholders.map((placeholder) => {
              return <DocumentRow document={placeholder} themeVariant={props.themeVariant} removeDocument={props.removeDocument} />;
            })}
          </div>
        </div>
      </Stack.Item>
    </Stack>
  );
}
John John
  • 1
  • 72
  • 238
  • 501

1 Answers1

0

You can do that, but you may need to use a third-party library to read the excel sheet in the browser. A common solution for that is sheetjs library. There are no built-in helpers in the SPFx framework to parse Excel files, as far as I know.

But you should be able to install sheetjs using npm and then use it by import.

Nikolay
  • 10,752
  • 2
  • 23
  • 51