1.0.0 • Published 3 months ago

formmaker-dynamic-crud v1.0.0

Weekly downloads
-
License
MIT
Repository
-
Last release
3 months ago

Dynamic Form CRUD Component

A powerful and flexible CRUD (Create, Read, Update, Delete) component for React applications that supports dynamic form generation, advanced search, and various field types.

Features

  • 🎯 Dynamic form generation based on configuration
  • 🔍 Advanced search with multiple field types
  • 📱 Responsive design
  • 🌐 RTL support
  • 🔒 Permission-based access control
  • 📊 Customizable layout and styling
  • 🔄 Real-time validation
  • 📁 File upload support
  • 🎨 Multiple field types support
  • 🔄 Nested fields support

Installation

npm install @your-package/dynamic-form-crud
# or
yarn add @your-package/dynamic-form-crud

Basic Usage

import CRUDComponent from "@/components/CRUDComponent";

const formStructure = [
  {
    name: "firstName",
    title: "First Name",
    type: "text",
    isShowInList: true,
    isSearchable: true,
    required: true,
    enabled: true,
    visible: true,
    validation: {
      requiredMessage: "First name is required",
    },
  },
  // ... more fields
];

const layout = {
  direction: "rtl",
  width: "100%",
  texts: {
    addButton: "افزودن",
    editButton: "ویرایش",
    deleteButton: "حذف",
    // ... more text customizations
  },
};

export default function App() {
  return (
    <CRUDComponent
      formStructure={formStructure}
      collectionName="users"
      connectionString="your-mongodb-connection-string"
      layout={layout}
      permissions={{
        canList: true,
        canAdd: true,
        canEdit: true,
        canDelete: true,
        canGroupDelete: true,
        canAdvancedSearch: true,
        canSearchAllFields: true,
      }}
    />
  );
}

Field Types

1. Text Input

{
  name: "firstName",
  title: "First Name",
  type: "text",
  isShowInList: true,
  isSearchable: true,
  required: true,
  enabled: true,
  visible: true,
  validation: {
    requiredMessage: "First name is required",
  },
}

2. Email Input

{
  name: "email",
  title: "Email",
  type: "email",
  required: true,
  validation: {
    regex: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
    requiredMessage: "Email is required",
    validationMessage: "Please enter a valid email address",
  },
}

3. Checkbox

{
  name: "isActive",
  title: "Active Status",
  type: "checkbox",
  defaultValue: true,
}

// Multiple checkboxes
{
  name: "notifications",
  title: "Notification Settings",
  type: "checkbox",
  isMultiple: true,
  defaultValue: [],
  options: [
    { label: "Email", value: "email" },
    { label: "SMS", value: "sms" },
    { label: "Push", value: "push" },
  ],
}

4. Dropdown

{
  name: "role",
  title: "Role",
  type: "dropdown",
  required: true,
  options: [
    { label: "Admin", value: "admin" },
    { label: "User", value: "user" },
    { label: "Guest", value: "guest" },
  ],
  validation: {
    requiredMessage: "Please select a role",
  },
}

5. Radio Buttons

{
  name: "gender",
  title: "Gender",
  type: "radio",
  required: true,
  layout: "inline", // or "stacked"
  options: [
    { label: "Male", value: "male" },
    { label: "Female", value: "female" },
    { label: "Other", value: "other" },
  ],
}

6. Switch

{
  name: "darkMode",
  title: "Dark Mode",
  type: "switch",
  defaultValue: false,
  switchStyle: {
    size: "md",
    color: "blue",
    thumbColor: "white",
  },
}

7. ToggleGroup

{
  name: "interests",
  title: "Interests",
  type: "togglegroup",
  required: true,
  defaultValue: [],
  layout: "stacked",
  options: [
    { value: "sports", label: "Sports" },
    { value: "music", label: "Music" },
    { value: "reading", label: "Reading" },
  ],
}

8. DatePicker

{
  name: "birthDate",
  title: "Birth Date",
  type: "datepicker",
  required: true,
  datepickerStyle: {
    format: "YYYY/MM/DD",
    calendar: "persian",
    locale: "fa",
    calendarPosition: "bottom",
  },
  // Multiple dates
  isMultiple: true,
}

9. Autocomplete

{
  name: "skills",
  title: "Skills",
  type: "autocomplete",
  required: true,
  isMultiple: true,
  options: [
    { label: "JavaScript", value: "js" },
    { label: "TypeScript", value: "ts" },
    { label: "React", value: "react" },
  ],
  autocompleteStyle: {
    allowNew: true,
    maxTags: 10,
    minLength: 1,
  },
}

10. File Upload

{
  name: "avatar",
  title: "Profile Picture",
  type: "file",
  required: false,
  fileConfig: {
    allowedTypes: ["image/*"],
    maxSize: 5 * 1024 * 1024, // 5MB
    directory: "avatars",
    multiple: false,
  },
}

// Multiple files
{
  name: "documents",
  title: "Documents",
  type: "file",
  isMultiple: true,
  fileConfig: {
    allowedTypes: ["application/pdf", "application/msword"],
    maxSize: 10 * 1024 * 1024, // 10MB
    directory: "documents",
    multiple: true,
  },
}

11. Nested Fields

{
  name: "address",
  title: "Address",
  type: "text",
  fields: [
    {
      name: "street",
      title: "Street",
      type: "text",
      required: true,
    },
    {
      name: "city",
      title: "City",
      type: "text",
      required: true,
    },
    {
      name: "country",
      title: "Country",
      type: "dropdown",
      options: [
        { label: "USA", value: "usa" },
        { label: "Canada", value: "canada" },
      ],
    },
  ],
  orientation: "horizontal",
}

12. Array Fields

{
  name: "phones",
  title: "Phone Numbers",
  type: "text",
  nestedType: "array",
  fields: [
    {
      name: "type",
      title: "Type",
      type: "dropdown",
      options: [
        { label: "Home", value: "home" },
        { label: "Work", value: "work" },
        { label: "Mobile", value: "mobile" },
      ],
    },
    {
      name: "number",
      title: "Number",
      type: "text",
      required: true,
    },
  ],
  orientation: "horizontal",
}

Form Structure Fields

Each field in the form structure can have the following properties:

Common Properties

PropertyTypeRequiredDescription
namestringYesUnique identifier for the field
titlestringYesDisplay label for the field
typestringYesType of the field (text, email, checkbox, etc.)
isShowInListbooleanNoWhether to show this field in the list view
isSearchablebooleanNoWhether this field can be used in search
requiredbooleanNoWhether this field is required
enabledbooleanNoWhether this field is enabled/editable
visiblebooleanNoWhether this field is visible
readonlybooleanNoWhether this field is read-only
defaultValueanyNoDefault value for the field
validationValidationRulesNoValidation rules for the field

Validation Rules

const validation = {
  requiredMessage: "This field is required",
  regex: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
  validationMessage: "Invalid format",
  minLength: 3,
  maxLength: 50,
  min: 0,
  max: 100,
  custom: (value: any) => boolean | string,
};

Field-Specific Properties

Text Input

{
  type: "text",
  placeholder: "Enter text",
  maxLength: 100,
  minLength: 3,
  pattern: "^[a-zA-Z0-9]+$",
}

Email Input

{
  type: "email",
  validation: {
    regex: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
    validationMessage: "Please enter a valid email address",
  },
}

Checkbox

{
  type: "checkbox",
  isMultiple: boolean,
  options: Array<{ label: string, value: any }>,
  layout: "inline" | "stacked",
}

Dropdown

{
  type: "dropdown",
  options: Array<{ label: string, value: any }>,
  dataSource: {
    collectionName: string,
    labelField: string,
    valueField: string,
    sortField: string,
    sortOrder: "asc" | "desc",
  },
}

File Upload

{
  type: "file",
  isMultiple: boolean,
  fileConfig: {
    allowedTypes: string[],
    maxSize: number,
    directory: string,
    multiple: boolean,
  },
}

Filtering Mechanisms

The component supports three types of filters:

1. Hardcoded Filters

Hardcoded filters are predefined filters that are always applied to the list view:

const hardcodedFilter = {
  isActive: true, // Only show active users
  role: "admin", // Only show admin users
  status: "approved", // Only show approved items
};

<CRUDComponent
  // ... other props
  initialFilter={hardcodedFilter}
/>;

2. Posted Filters

Posted filters are filters that are sent to the server with each request:

const postedFilter = {
  search: "john",
  status: ["active", "pending"],
  dateRange: {
    start: "2024-01-01",
    end: "2024-12-31",
  },
};

<CRUDComponent
  // ... other props
  postedFilter={postedFilter}
/>;

3. Query String Filters

Query string filters are filters that are encoded in the URL, allowing for shareable filtered views:

// Example URL with filters
// /users?filter=eyJzZWFyY2giOiJqb2huIiwic3RhdHVzIjpbImFjdGl2ZSIsInBlbmRpbmciXX0=

// Implementation
import { useInitialFilter } from "@/hooks/useInitialFilter";
import { encryptFilter } from "@/utils/encryption";

export default function App() {
  const initialFilter = useInitialFilter({
    postedFilter,
    hardcodedFilter,
  });

  // Function to update URL with encrypted filter
  const updateFilterInURL = (filter: Record<string, unknown>) => {
    const encryptedFilter = encryptFilter(filter);
    const newURL = new URL(window.location.href);
    newURL.searchParams.set("filter", encryptedFilter);
    router.push(newURL.toString());
  };

  return (
    <CRUDComponent
      // ... other props
      initialFilter={initialFilter}
    />
  );
}

Filter Examples

// Filter helper functions
export const filterExamples = {
  // Show only admin users
  adminUsers: () => ({
    role: "admin",
  }),

  // Show only active users
  activeUsers: () => ({
    isActive: true,
  }),

  // Show active admin users
  activeAdmins: () => ({
    role: "admin",
    isActive: true,
  }),

  // Show users in a specific city
  usersInCity: (city: string) => ({
    "address.city": city,
  }),

  // Show users with specific skills
  usersWithSkills: (skills: string[]) => ({
    skills: { $in: skills },
  }),

  // Advanced filter with multiple conditions
  advancedFilter: () => ({
    $and: [
      { isActive: true },
      { role: "admin" },
      { "address.country": "USA" },
      { skills: { $in: ["react", "typescript"] } },
    ],
  }),
};

Filter Usage Examples

// Apply filter and navigate
const applyFilter = (filterUrl: string) => {
  router.push(filterUrl);
};

// Share with combined filters
const shareWithFilters = (rowId: string) => {
  const combinedFilter = {
    ...hardcodedFilter,
    _id: rowId,
  };
  updateFilterInURL(combinedFilter);
};

// Example buttons
<button onClick={() => applyFilter(filterExamples.adminUsers())}>
  Show Admins
</button>
<button onClick={() => applyFilter(filterExamples.activeUsers())}>
  Show Active Users
</button>
<button onClick={() => applyFilter(filterExamples.advancedFilter())}>
  Advanced Filter
</button>

Props

Required Props

PropTypeDescription
formStructureFormField[]Array of field configurations
collectionNamestringName of the MongoDB collection
connectionStringstringMongoDB connection string

Optional Props

PropTypeDefaultDescription
layoutLayoutSettingsSee belowLayout and text customizations
permissionsPermissionsSee belowAccess control settings
initialFilterRecord<string, unknown>{}Initial filter for the list view
rowActionsRowAction[][]Custom actions for each row
onAfterAdd(entity: any) => void-Callback after adding an entity
onAfterEdit(entity: any) => void-Callback after editing an entity
onAfterDelete(id: string) => void-Callback after deleting an entity
onAfterGroupDelete(ids: string[]) => void-Callback after group deletion

Layout Settings

const layout = {
  direction: "rtl", // or "ltr"
  width: "100%",
  texts: {
    addButton: "Add",
    editButton: "Edit",
    deleteButton: "Delete",
    cancelButton: "Cancel",
    clearButton: "Clear",
    searchButton: "Search",
    advancedSearchButton: "Advanced Search",
    applyFiltersButton: "Apply Filters",
    addModalTitle: "Add New Entry",
    editModalTitle: "Edit Entry",
    deleteModalTitle: "Delete Confirmation",
    advancedSearchModalTitle: "Advanced Search",
    deleteConfirmationMessage: "Are you sure?",
    noResultsMessage: "No results found",
    loadingMessage: "Loading...",
    processingMessage: "Processing...",
    actionsColumnTitle: "Actions",
    showEntriesText: "Show",
    pageText: "Page",
    ofText: "of",
    searchPlaceholder: "Search...",
    selectPlaceholder: "Select an option",
    filtersAppliedText: "Filters applied",
    clearFiltersText: "Clear filters",
  },
};

Permissions

const permissions = {
  canList: true, // View the list
  canAdd: true, // Add new entries
  canEdit: true, // Edit existing entries
  canDelete: true, // Delete entries
  canGroupDelete: true, // Delete multiple entries
  canAdvancedSearch: true, // Use advanced search
  canSearchAllFields: true, // Search across all fields
};

Row Actions

const rowActions = [
  {
    label: "View",
    link: "/view",
    icon: ViewIcon,
  },
  {
    label: "Share",
    action: (rowId: string) => {
      // Custom action
      console.log("Share clicked for row:", rowId);
    },
    icon: ShareIcon,
  },
];

Styling

The component uses Tailwind CSS for styling and supports custom class names through the className prop. You can also customize the appearance of specific elements using the style props in the field configurations.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.