Snippets Collections
import pandas as pd
import pyarrow as pa
import pyarrow.orc as orc
from glob import glob

dataset = pd.read_csv()

# Read the Pandas dataset as a PyArrow Table
pa_table = pa.Table.from_pandas(dataset)

# Write the PyArrow Table to an ORC file
with pa.OSFile("/home/saravana/Saravana/s3_maintain/s3-maintenance/Download_S3_Files/Datavisiooh/month_03.orc", "wb") as sink:
    with orc.ORCWriter(sink) as writer:
        writer.write(pa_table)

# Read the ORC file back into a Pandas DataFrame
orc_file_path = "/home/saravana/Saravana/s3_maintain/s3-maintenance/Download_S3_Files/Datavisiooh/month_03.orc"
df = orc.read_table(orc_file_path).to_pandas()
# Display the DataFrame
print(df.head())
/*========================================================
  Rise 360 compulsory CSS
  For use in all Digital Learning and Development Rise 360 courses.
  Version 1.0
  Last updated 04/11/2024
==========================================================*/

/*Global variables – edit these variables to suit your course design.
==========================================================*/

:root {
  --custom-theme-colour-button-hover-opacity: .9; /*This sets the opacity of buttons and checkboxes that use the theme colour, such as continue buttons.Lower value equals ligher hover colour.*/
  --custom-carousel-prev-next-hover-colour: #000; /*This sets the hover state colour of the previous and next buttons on the quote carousel and image carousel blocks.*/
}

/*Global CSS edits
==========================================================*/

/*Links > Hover state: Add a background colour and border.*/
.brand--linkColor a:hover {
  outline: solid 3px rgba(0, 0, 0, .1); /*Using transparancy prevents surrounding text, such as full stops, from vanishing.*/
  background-color: rgba(0, 0, 0, .1) !important;
}

/*Cover page
==========================================================*/

/*Cover page > Start module button: Remove all caps, increase font size, decrease font weight, adjust padding.*/
.cover__header-content-action-link-text{
  text-transform: none;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: .1rem;
}
.cover__header-content-action-link {
  padding: 0.8rem 2.9rem !important;
}

/*Cover page > body text: Increase font size and change colour.*/ 
.cover__details-content-description.brand--linkColor {
  color: #000;
  font-size: 1.7rem;
}

/*Cover page > Section titles: Increase font size, remove all caps, darken border line.*/
.overview-list__section-title {
  border-bottom: .1rem solid #717376; /*Colour of the lesson icons.*/
  font-size: 1.4rem;
  text-transform: none;
}

/*Cover page > lesson list: Increase font size.*/
.overview-list-item__title {
  font-size: 1.4rem;
}

/*Navigation menu
==========================================================*/

/*Navigation menu > % progress indicator: Remove all caps, increase font size.*/
.nav-sidebar-header__progress-text {
  text-transform: none !important;
  font-size: 1.4rem !important;
}

/*Navigation menu > Section titles: Remove all caps, increase font size.*/
.nav-sidebar__outline-section-toggle-text {
  text-transform: none;
  font-size: 1.4rem;
}
.nav-sidebar__outline-section-toggle:after {
  border-bottom: 1px solid #717376 !important;
}

/*Navigation menu > Lesson titles: Increase font size.*/
.nav-sidebar__outline-section-item__link {
  font-size: 1.4rem !important;
}

/*Lesson header
==========================================================*/

/*Lesson header > Lesson counter: Increase font size, remove italics.*/
.lesson-header__counter {
  font-size: 1.4rem;
  font-style: normal;
  margin-bottom: 1.3rem !important;
}

/*Text blocks
==========================================================*/

/*Paragraph
----------------------------------------------------------*/

/*Paragraph with heading
----------------------------------------------------------*/

/*Paragraph with subheading
----------------------------------------------------------*/

/*Heading
----------------------------------------------------------*/

/*Subheading
----------------------------------------------------------*/

/*Columns
----------------------------------------------------------*/

/*Table
----------------------------------------------------------*/


/*Statement blocks
==========================================================*/

/*Statement A
----------------------------------------------------------*/

/*Statement B
----------------------------------------------------------*/

/*Statement C
----------------------------------------------------------*/

/*Statement D
----------------------------------------------------------*/

/*Note
----------------------------------------------------------*/


/*Quote blocks
==========================================================*/

/*Quote A
----------------------------------------------------------*/

/*Quote B
----------------------------------------------------------*/

/*Quote C
----------------------------------------------------------*/

/*Quote D
----------------------------------------------------------*/

/*Quote on image
----------------------------------------------------------*/

/*Quote carousel
----------------------------------------------------------*/

/*Quote carousel 
-------------------------------------------------------------*/



/*List blocks
==========================================================*/

/*Numbered list
----------------------------------------------------------*/

/*Checkbox list
----------------------------------------------------------*/

/*Checkbox list > Checkboxes: Move the checkboxes to the front and change the hover colour.*/
.block-list__checkbox {
  z-index: 2;
  transition: all .15s ease-in-out;
}
.block-list__checkbox:hover {
  background-color: var(--color-theme-decorative);
  opacity: var(--custom-theme-colour-button-hover-opacity);
}

/*Bulleted list
----------------------------------------------------------*/


/*Image blocks
==========================================================*/

/*Image blocks > Caption: Increase font size. This can be changed by manually adjusting the font size in Rise.*/
.block-image__caption, .block-gallery__caption {
  font-size: 1.4rem !important;
}

/*Image centered
----------------------------------------------------------*/

/*Image full width
----------------------------------------------------------*/

/*Image & text
----------------------------------------------------------*/

/*Text on image
----------------------------------------------------------*/


/*Gallery blocks
==========================================================*/

/*Carousel
----------------------------------------------------------*/

/* Gallery Carousel 
--------------------------------------------------*/

/*Note that the hover state of the progression circles is modified in the Quote carousel section.*/

/*Two column grid
----------------------------------------------------------*/

/*Three column grid
----------------------------------------------------------*/

/*Four column grid
----------------------------------------------------------*/


/*Multimedia blocks
==========================================================*/

/*Audio
----------------------------------------------------------*/

/*Audio > Play/puase button and scrub slider: Increase size and gap between. Change hover scale.*/
.audio-player__play {
  margin-right: 1.6rem;
}
.audio-player__play .svg-inline--fa {
  height: 1.7rem;
  transition: all 0.15s ease-in-out;
}
.audio-player__play .svg-inline--fa:hover {
  transform: scale(1.2);
}
.audio-player__tracker-handle{
  height: 100%;
}
.audio-player__tracker-handle-icon>svg {
  height: 1.5rem;
  width: 1.5rem;
}
.audio-player__tracker-handle-icon{
  transition: all 0.15s ease-in-out;
}
.audio-player__tracker-handle-icon:hover {
  transform: scale(1.2);
}
.audio-player__tracker-handle-icon:active {
  transform: scale(1.2);
}

/*Audio > track line: Make line thicker.*/
.audio-player__tracker-bar {
  border-top: .16rem solid var(--color-track);
}
.audio-player__tracker:after {
  border-top: .16rem solid var(--color-runner);
}

/*Audio > Timer: Increase font size.*/
.audio-player__timer {
  font-size: 1.2rem;
}

/*Audio > Caption: Increase font size. This can be changed by manually adjusting the font size in Rise.*/
.block-audio__caption {
  font-size: 1.4rem;
}

/*Video
----------------------------------------------------------*/

/*Video > Caption: Change font size to 14px.*/
.block-video__caption {
  font-size: 1.4rem;
}

/*Embed
----------------------------------------------------------*/

/*Attachement
----------------------------------------------------------*/

/*Attachement: Add hover colour.*/
.block-attachment:hover {
  background: #ebebeb;
}

/*Code snippet
----------------------------------------------------------*/

/*Code snippet > Caption: Increase font size.*/
.block-text__code-caption p {
  font-size: 1.4rem;
}

/*Interactive blocks
==========================================================*/

/*Accordion
----------------------------------------------------------*/

/*Tabs
----------------------------------------------------------*/

/*Tabs 
---------------------------------------------------*/


/*Labeled graphic
----------------------------------------------------------*/

/*Labeled graphic
------------------------------------------*/


/*Process
----------------------------------------------------------*/



/*Scenario
----------------------------------------------------------*/


/*Sorting activity
----------------------------------------------------------*/

/*Sorting activity > Restart button: Remove all caps, change font size, colour and letter spacing.*/
.block-sorting-activity .restart-button__content {
  color: var(--color-theme-decorative);
  border-radius: 5px;
  text-transform: none;
  font-size: 1.5rem;
  letter-spacing: .1rem;
  font-weight: 700;
}

/*Sorting activity > Restart button: Add a hover state.*/
.block-sorting-activity .deck__title {
  margin-bottom: 1rem;  
  padding-bottom: .6rem;
  border-bottom: .1rem solid rgba(0, 0, 0, .2);
}
.block-sorting-activity .restart-button {
  margin-top: 0rem;
  border: none;
  padding: 1rem;
  border-radius: 5px;
  min-height: 7.45rem;
}
.block-sorting-activity .restart-button {
  transition: background-color 0.3s;
}
.block-sorting-activity .restart-button:hover {
  background-color: #ebebeb;
}


/*Timeline
----------------------------------------------------------*/

/*Flashcard grid
----------------------------------------------------------*/

/*Flashbard stack
----------------------------------------------------------*/

/*Flashcard stack > Previous and Next button: Change hover state.*/
.block-flashcards-slider__arrow--next, .block-flashcards-slider__arrow--prev {
  transition: opacity .3s;
}
.block-flashcards-slider__arrow--next:hover, .block-flashcards-slider__arrow--prev:hover {
  opacity: var(--custom-theme-colour-button-hover-opacity);
}

/*Flashcard stack > Slide counter: Remove italics.*/
.block-flashcards-slider__progress-text {
  font-style: normal;
}

/*Flashcard stack > Progress line: Increase thickness.*/
.block-flashcards-slider__progress-line {
  border-bottom: max(.2rem, 2px) solid var(--color-progress-track);
  position: relative;
}
.block-flashcards-slider__progress-runner {
  border-bottom: max(.2rem, 2px) solid var(--color-theme-decorative);
}

/*Button
----------------------------------------------------------*/

/*Button and Button stack > Button: Remove all caps, increase font size and line height.*/
.blocks-button__button {
  transition: all .3s;
  text-transform: none;
  font-size: 1.5rem;
  line-height: 3.9rem;
}

/*Button and Button stack > Button: Change hover state.*/
.blocks-button__button:hover {
  opacity: var(--custom-theme-colour-button-hover-opacity);
}

/*Button and Button stack > Button: Offset the focus state outline.*/
.blocks-button__button:focus {
  outline-offset: .4rem;
}

/*Button stack
----------------------------------------------------------*/

/*Storyline
----------------------------------------------------------*/


/*Knowledge check blocks AND Quiz lesson
==========================================================*/

/*Knowledge check/Quiz > Options: remove extra space between question options and submit button/feedback box.*/
.block-knowledge .quiz-card__interactive {
  margin-bottom: 0rem;
}

/*Knowledge check/Quiz > Submit/Next buttons: Remove all caps and increase font size.*/
.quiz-card__button{
  transition: opacity .3s;
  text-transform: none;
  font-size: 1.5rem;
}
  
/*Knowledge check/Quiz > Submit/Next buttons: Change hover state.*/
.quiz-card__button:hover {
  opacity: var(--custom-theme-colour-button-hover-opacity);
}
  
/*Knowledge check/Quiz > Submit/Next buttons: Offset the focus state outline.*/
.quiz-card__button:focus {
  outline-offset: 0.4rem;
}

/*Knowledge check/Quiz > 'Correct/Incorrect' label: Increase font size.*/
.quiz-card__feedback-label {
  font-size: 1.4rem;
}

/*Knowledge check/Quiz > Feedback body text: Increase font size, align left and ensure color is black. */
.quiz-card__feedback-text {
  font-size: 1.6rem;
  text-align: left;
  color: #000;
}

/*Knowledge check > Try again button: Remove all caps, increase font size and change to theme colour. Note that the Rise 360 label text must also be lowercase.*/
.block-knowledge__retake-text {
  text-transform: none;
  font-size: 1.4rem;
}
.block-knowledge__retake {
  color: var(--color-theme-decorative)
}

/*Knowledge check > Try again button: Change hover state.*/
.block-knowledge__retake-content {
  transition: background-color 0.3s;
  border-radius: 5px;
  padding: 1rem;
  margin: -1rem /*Negative margin pushes the margin out into the padding area to create a larger hover state without having to change the padding for the normal state.*/
}
.block-knowledge__retake-content:hover {
  background-color: #ebebeb;
}

/*Multiple choice
----------------------------------------------------------*/

/*Multiple response
----------------------------------------------------------*/

/*Fill in the blank
----------------------------------------------------------*/

/*Fill in the blank > 'Acceptable responses' label: Increase font size and remove italics.*/
.quiz-fill__options {
  font-size: 1.4rem;
  font-style:normal;
}

/*Matching
----------------------------------------------------------*/

/*Matching: Increase font size to 16px.*/
.quiz-match__item-content {
  font-size: 1.5rem;
}

/*Quiz
----------------------------------------------------------*/

/*Quiz > 'Lesson X of Y' label: Increase font size, letter spacing and remove italics.*/
.quiz-header__counter {
  font-size: 1.4rem;
  font-style: normal;
  letter-spacing: .05rem;
}

/*Quiz > 'Start assessment' label: Remove all caps, increase font size, move icon to the left.*/
.quiz-header__start-quiz {
  transition: all .3s;
  text-transform: none;
  font-size: 1.5rem;
  border-radius: 5px;
  padding: 1rem;
  margin: -1rem;
}
.quiz-header__start-quiz [class*=icon-] {
  margin-left: .6rem;
}

/*Quiz > 'Start assessment' label: Add hover state.*/
.quiz-header__start-quiz:hover {
  background-color: #ebebeb;
}

/*Quiz > 'Question' label: Remove italics and increase font size.*/
.quiz-card__step-label {
  font-size: 1.4rem;
  font-style: normal;
  letter-spacing: .05rem;
  font-weight: 400;
}
@media (max-width: 47.9375em) {
  .quiz-card__counter {
    font-size: 2.2rem;
  }
}

/*Quiz > Quiz results odemeter: Increase font size on all elements.*/
.odometer__score-label, .odometer__passlabel, .odometer__passpercent  {
  font-size: 1.4rem;
  text-transform: none;
}

/*Quiz > Quiz results 'Try again' button: Remove all caps, change font colour, size, weight and letter spacing, adjust padding.*/
.quiz-results__footer .restart-button__content {
  transition: background-color 0.3s;
  color: var(--color-theme);
  text-transform: none;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: .1rem;
  padding: 1rem;
  margin: -1rem;
  border-radius: 5px;
}

/*Quiz > Quiz results 'Try again' button: Add hover state.*/
.quiz-results__footer .restart-button__content:hover {
  background-color: #ebebeb;
}


/*Draw from question bank
----------------------------------------------------------*/


/*Chart blocks
==========================================================*/

/*Bar chart
----------------------------------------------------------*/

/*Line chart
----------------------------------------------------------*/

/*Pie chart
----------------------------------------------------------*/


/*Divider blocks
==========================================================*/

/*Continue
----------------------------------------------------------*/
  
/*Continue: Change hover state.*/
.continue-btn {
  transition: opacity 0.3s;
}
.continue-btn:hover {
  opacity: var(--custom-theme-colour-button-hover-opacity);
}

/*Continue: Offset the focus state outline.*/
.continue-btn:focus {
  outline-offset: 0.4rem;
}

/*Divider
----------------------------------------------------------*/

/*Numbered divider
----------------------------------------------------------*/

/*Spacer
----------------------------------------------------------*/


/*CSS edits by Firstname Lastname on XX/XX/202X.*/

/*========================================================
  Optional CSS edits – Paste all optional CSS edits below this comment.
==========================================================*/
## Quick Defaults

```css
font-family: "Helvetica Now Display", "SF Pro Display", "Inter Display", "Inter Tight", Helvetica, Roboto, Arial, -apple-system, sans-serif;
font-family: "SF Pro Display", "Helvetica Now Display", "Inter Display", "Inter Tight", Helvetica, Arial, Roboto, -apple-system, sans-serif;
font-family: "Inter Display", "Inter Tight", "SF Pro Display", "Helvetica Now Display", Helvetica, Roboto, Arial, -apple-system, sans-serif;
font-family: "Inter Tight", "Inter Display", "SF Pro Display", "Helvetica Now Display", Helvetica, Roboto, Arial, -apple-system, sans-serif;
font-family: "Helvetica LT Pro", "Helvetica", "Helvetica Now Text", "SF Pro Text", Roboto, Inter, Arial, -apple-system, sans-serif;
font-family: "Helvetica Now Text", "Helvetica LT Pro", Helvetica, "Helvetica Neue LT Pro", "SF Pro Text", Roboto, Arial, -apple-system, sans-serif;
font-family: "SF Pro Text", "Helvetica Now Text", Roboto, "Helvetica LT Pro", Helvetica, "Helvetica Neue LT Pro", Arial, Roboto, -apple-system, sans-serif;
font-family: Inter, "SF Pro Text", Roboto, "Helvetica Now Text", "Helvetica LT Pro", Helvetica, Arial, -apple-system, sans-serif;
font-family: Roboto, "SF Pro Text", "Helvetica Now Text", "Helvetica LT Pro", Inter, Helvetica, Arial, -apple-system, sans-serif;
font-family: "Helvetica Now Display", "SF Pro Display", "Inter Display", "Inter Tight", Helvetica, Roboto, Arial, -apple-system, sans-serif;
font-family: "Inter Display", "Inter Tight", "SF Pro Display", "Helvetica Now Display", Helvetica, Roboto, Arial, -apple-system, sans-serif;
font-family: "Inter Tight", "Inter Display", "SF Pro Display", "Helvetica Now Display", Helvetica, Roboto, Arial, -apple-system, sans-serif;
```

## Variable Definitions

```css
:root {
--font-disp-helvnow: "Helvetica Now Display", "SF Pro Display", "Inter Display", "Inter Tight", Helvetica, Roboto, Arial, -apple-system, sans-serif;
--font-disp-sfpro: "SF Pro Display", "Helvetica Now Display", "Inter Display", "Inter Tight", Helvetica, Arial, Roboto, -apple-system, sans-serif;
--font-disp-inter: "Inter Display", "Inter Tight", "SF Pro Display", "Helvetica Now Display", Helvetica, Roboto, Arial, -apple-system, sans-serif;
--font-disp-intert: "Inter Tight", "Inter Display", "SF Pro Display", "Helvetica Now Display", Helvetica, Roboto, Arial, -apple-system, sans-serif;
--font-text-helv: "Helvetica LT Pro", "Helvetica", "Helvetica Now Text", "SF Pro Text", Roboto, Inter, Arial, -apple-system, sans-serif;
--font-text-helvnow: "Helvetica Now Text", "Helvetica LT Pro", Helvetica, "Helvetica Neue LT Pro", "SF Pro Text", Roboto, Arial, -apple-system, sans-serif;
--font-text-sfpro: "SF Pro Text", "Helvetica Now Text", Roboto, "Helvetica LT Pro", Helvetica, "Helvetica Neue LT Pro", Arial, Roboto, -apple-system, sans-serif;
--font-text-inter: Inter, "SF Pro Text", Roboto, "Helvetica Now Text", "Helvetica LT Pro", Helvetica, Arial, -apple-system, sans-serif;
--font-text-roboto: Roboto, "SF Pro Text", "Helvetica Now Text", "Helvetica LT Pro", Inter, Helvetica, Arial, -apple-system, sans-serif;
--font-mono-roboto: "Roboto Mono", "Input Mono", "SF Mono", "Cascadia Mono", "Segoe UI Mono", "Cousine", ui-monospace, monospace;
--font-mono-input: "Input Mono", "Roboto Mono", "SF Mono", "Cascadia Mono", "Segoe UI Mono", "Cousine", ui-monospace, monospace;
--font-mono-sfmono: "SF Mono", "Input Mono", "Roboto Mono", "SF Mono", "Cascadia Mono", "Segoe UI Mono", "Cousine", ui-monospace, monospace;
}
```
.post__content a img { 
  display: block; 
}
/* invert elements sort order w/o changing markup: */
.container {
    direction: rtl;
    }
    .container > * { 
        direction: ltr; 
    }

/* or */
ul {
    -webkit-transform: rotate(180deg);
            transform: rotate(180deg);
}
    ul > li {
        -webkit-transform: rotate(-180deg);
                transform: rotate(-180deg);
    }
img {
  image-rendering: auto; /* default */
  image-rendering: crisp-edges; /* fuer Pixel-Art */
  image-rendering: pixelated; /* fuer QR-Codes */
}
.triangle {
	border-color: yellow blue red green;
	border-style: solid;
	border-width: 0px 200px 200px 200px;
	height: 0px;
	width: 0px;
}
/* Resetting the background will solve this issue: */

button {
  appearance: none;
  background: transparent;
  /* Other styles */
}
a[href^="tel"] {
  white-space: nowrap;
  pointer-events: none;
  text-decoration: none;
  color: inherit;
}
@media (max-width: 30em) {
  a[href^="tel"] {
    pointer-events: auto;
    text-decoration: underline;
  }
}
@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
  }
}
.image-contain {
	object-fit: contain;
	object-position: center;
}
.image-cover {
	object-fit: cover;
	object-position: right top;
}
.classname {
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
}
<?php
/**
 * Create a ZIP of every attachment in a "Documents" CPT,
 * organized into main folders and sub-folders using your exact labels,
 * with "/" replaced by "-" in folder names,
 * and named after the document’s title.
 */
function create_documents_zip( $post_id ) {
    if ( ! $post_id ) {
        return false;
    }

    // Build ZIP filename from post title
    $post_title = get_the_title( $post_id );
    $slug       = sanitize_title( $post_title );
    $zip_name   = "{$slug}.zip";

    // 1) Define main folders (sections) and sub-folders (fields)
    $sections = [
        'Personal Information' => [
            'passport-sized-photo'                     => 'Passport Sized Photo',
            'valid-passport'                           => 'Valid Passport',
            'national-identity-cards'                  => 'National Identity Cards',
            'employment-pass-s-pass-dependent-pass'    => 'Employment Pass/ S Pass/ Dependent Pass',
            'birth-certificates-household-registration' => 'Birth Certificates/ Household Registration',
            'official-marriage-certificate'            => 'Official Marriage Certificate',
        ],
        'Education & Professional Information' => [
            'education-certificates-transcripts'                    => 'Education Certificates/ Transcripts',
            'child039schildren039s-school-admission-letter-results' => "Child's/Children's School Admission Letter/ Results",
            'additional-certifications-courses-workshops'           => 'Additional Certifications/ Courses/ Workshops',
        ],
        'Social Contributions' => [
            'media-article-s-in-company--newsletters-websites--magazines--showcasing-contributions'
                => 'Media article (s) in Company / Newsletters / Websites / Magazines / Showcasing Contributions',
            'membership-in-clubs--societies'                        => 'Membership in Clubs / Societies',
            'charity-contribution-receipts--letters'               => 'Charity Contribution Receipt(s) / Letter(s)',
            'corporate-social-responsibility--community-participated-events'
                => 'Corporate Social Responsibility / Community Participated Events',
        ],
        'Employment Documents' => [
            'latest-6-months-payslips'                              => 'Latest 6 Months Payslips',
            'valid-business-registration-certificate-acrabizfile'    => 'Valid Business Registration Certificate (ACRABizfile)',
            'current-employer039s-letter-stating-date-of-employment-position-held-and-salary-per-month-for-past-6-months'
                => "Current Employer's Letter Stating Date of Employment, Position Held and Salary Per Month for Past 6 Months",
            'up-to-date-resume'                                     => 'Up To Date Resume',
            'salary-increment-letters--promotion-letter-s'          => 'Salary Increment Letter(s) / Promotion Letter (s)',
            'annex-a-of-form-4a'                                    => 'Annex A of Form 4A',
        ],
        'Economic Contributions' => [
            'summary-of-personal-insurance'                         => 'Summary of Personal Insurance',
            'summary-of-portfolio-of-personal-investments'          => 'Summary of Portfolio of Personal Investments',
            'summary-of-property-investments'                       => 'Summary of Property Investments',
        ],
        'Testimonials' => [
            'testimonial-from-current--previous-employer'           => 'Testimonial from Current / Previous Employer',
            'testimonial-from-others'                               => 'Testimonial from Others',
        ],
        // "Additional Documents" repeater handled below only if it has files
    ];

    // Prepare the ZIP archive
    $upload_dir = wp_upload_dir();
    $zip_path   = trailingslashit( $upload_dir['basedir'] ) . $zip_name;
    $zip        = new ZipArchive();
    if ( $zip->open( $zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE ) !== true ) {
        return false;
    }

    // Helper: add one file into Main/Sub
    $add_file = function( $att_id, $main_label, $sub_label ) use ( $zip ) {
        $file = get_attached_file( $att_id );
        if ( ! $file || ! file_exists( $file ) ) {
            return;
        }
        // Clean up labels, replace "/" with "-"
        $safe_main = sanitize_file_name( str_replace( '/', '-', $main_label ) );
        $safe_sub  = sanitize_file_name( str_replace( '/', '-', $sub_label ) );

        // Create the folders and add the file
        $zip->addEmptyDir( $safe_main );
        $zip->addEmptyDir( "{$safe_main}/{$safe_sub}" );
        $zip->addFile( $file, "{$safe_main}/{$safe_sub}/" . basename( $file ) );
    };

    // 2) Loop each defined section and its fields
    foreach ( $sections as $main_label => $fields ) {
        foreach ( $fields as $meta_key => $sub_label ) {
            $raw = get_post_meta( $post_id, $meta_key, true );
            if ( empty( $raw ) ) {
                continue;
            }

            // Normalize to array of items
            $items = is_array( $raw )
                ? $raw
                : ( strpos( $raw, ',' ) !== false
                    ? array_map( 'trim', explode( ',', $raw ) )
                    : [ $raw ] );

            foreach ( $items as $item ) {
                if ( is_array( $item ) && ! empty( $item['id'] ) ) {
                    $item = $item['id'];
                }
                if ( is_string( $item ) && filter_var( $item, FILTER_VALIDATE_URL ) ) {
                    $item = attachment_url_to_postid( $item );
                }
                $att_id = intval( $item );
                if ( $att_id ) {
                    $add_file( $att_id, $main_label, $sub_label );
                }
            }
        }
    }

    // 3) Handle the repeater field "upload-additional-documents" only if it has items
    $repeater = get_post_meta( $post_id, 'upload-additional-documents', true );
    if ( is_array( $repeater ) && ! empty( $repeater ) ) {
        foreach ( $repeater as $row ) {
            $sub_label_raw = ! empty( $row['document-about'] )
                ? $row['document-about']
                : 'Miscellaneous';

            $raw_items = $row['select-documents'];
            if ( empty( $raw_items ) ) {
                continue;
            }

            // Normalize repeater gallery values
            $items = is_array( $raw_items )
                ? $raw_items
                : ( strpos( $raw_items, ',' ) !== false
                    ? array_map( 'trim', explode( ',', $raw_items ) )
                    : [ $raw_items ] );

            foreach ( $items as $item ) {
                if ( is_array( $item ) && ! empty( $item['id'] ) ) {
                    $item = $item['id'];
                }
                if ( is_string( $item ) && filter_var( $item, FILTER_VALIDATE_URL ) ) {
                    $item = attachment_url_to_postid( $item );
                }
                $att_id = intval( $item );
                if ( $att_id ) {
                    $add_file( $att_id, 'Additional Documents', $sub_label_raw );
                }
            }
        }
    }

    $zip->close();
    return $zip_path;
}

/**
 * Stream the ZIP when ?download_all=1&post_id=… is requested.
 */
add_action( 'template_redirect', function() {
    if ( isset( $_GET['download_all'], $_GET['post_id'] ) ) {
        $post_id = intval( $_GET['post_id'] );
        // Optional: verify nonce or user capability here

        $zip_file = create_documents_zip( $post_id );
        if ( ! $zip_file || ! file_exists( $zip_file ) ) {
            wp_die( 'Unable to generate ZIP.' );
        }

        header( 'Content-Type: application/zip' );
        header( 'Content-Disposition: attachment; filename="' . basename( $zip_file ) . '"' );
        header( 'Content-Length: ' . filesize( $zip_file ) );
        readfile( $zip_file );
        exit;
    }
});
wget -nc https://dl.winehq.org/wine-builds/winehq.key
import React, { useState, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { McButton, McInput, McMultiSelect, McSelect } from '@maersk-global/mds-react-wrapper';
import { McOption } from '@maersk-global/mds-react-wrapper/components-core/mc-option';
import styles from '../styles/CreateRule.module.css';
import data from '../data/PnLGroup.json';

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  render() {
    if (this.state.hasError) {
      return <div>Something went wrong. Please refresh the page.</div>;
    }
    return this.props.children;
  }
}

const CreateRules = () => {
  const navigate = useNavigate();
  const [activeTab, setActiveTab] = useState('ruleInfo');
  const [isLoading, setIsLoading] = useState(false);
  const [ruleData, setRuleData] = useState({
    num: '',
    name: '',
    desc: '',
    custRefID: '',
    ruleGroup: '',
    isActive: 'Y',
    pnlGroup: '',
  });
  const [steps, setSteps] = useState([
    {
      stepNo: '',
      stepName: 'Single Step',
      stepDesc: '',
      stepType: 'S',
      preAggregatorColumns: [],
      sourceTableID: '',
      sourceFilterSets: [{ filters: [], operator: '', values: [] }],
      joinColumns: [],
      allocationColumns: [],
      driverTableID: '',
      driverWeightColumn: '',
      driverFilterSets: [{ filters: [], operator: '', values: [] }],
    },
  ]);
  const [errors, setErrors] = useState({ rule: {}, steps: [{ sourceFilterSets: [{}], driverFilterSets: [{}] }] });

  const pnLGroups = data.PnLGroups && typeof data.PnLGroups === 'object'
    ? Object.keys(data.PnLGroups)
    : [];
  const ruleGroups = ruleData.pnlGroup && data.PnLGroups[ruleData.pnlGroup]
    ? data.PnLGroups[ruleData.pnlGroup].RuleGroups || []
    : [];

  const sourceFilterOptions = [
    { value: 'Source_Filter_1', label: 'Source Filter 1' },
    { value: 'Source_Filter_2', label: 'Source Filter 2' },
    { value: 'Source_Filter_3', label: 'Source Filter 3' },
  ];
  const sourceValueOptions = [
    { value: 'Source_Value_1', label: 'Source Value 1' },
    { value: 'Source_Value_2', label: 'Source Value 2' },
    { value: 'Source_Value_3', label: 'Source Value 3' },
  ];
  const preAggregatorOptions = [
    { value: 'column1', label: 'Column 1' },
    { value: 'column2', label: 'Column 2' },
    { value: 'column3', label: 'Column 3' },
  ];
  const joinColumnsOptions = [
    { value: 'join_col1', label: 'Join Column 1' },
    { value: 'join_col2', label: 'Join Column 2' },
    { value: 'join_col3', label: 'Join Column 3' },
  ];
  const allocationColumnsOptions = [
    { value: 'alloc_col1', label: 'Allocation Column 1' },
    { value: 'alloc_col2', label: 'Allocation Column 2' },
    { value: 'alloc_col3', label: 'Allocation Column 3' },
  ];
  const driverFilterOptions = [
    { value: 'Driver_Type_1', label: 'Driver Type: Type 1' },
    { value: 'Driver_Type_2', label: 'Driver Type: Type 2' },
    { value: 'Driver_Status_Active', label: 'Driver Status: Active' },
  ];
  const driverValueOptions = [
    { value: 'Driver_Value_1', label: 'Driver Value 1' },
    { value: 'Driver_Value_2', label: 'Driver Value 2' },
    { value: 'Driver_Value_3', label: 'Driver Value 3' },
  ];

  const operatorOptions = useMemo(() => [
    { value: 'IN', label: 'IN' },
    { value: 'NOT IN', label: 'NOT IN' },
    { value: 'EQ', label: 'EQ' },
    { value: 'NTEQ', label: 'NTEQ' },
    { value: 'IS NULL', label: 'IS NULL' },
    { value: 'GT', label: 'GT' },
    { value: 'LT', label: 'LT' },
    { value: 'GTEQ', label: 'GTEQ' },
    { value: 'LTEQ', label: 'LTEQ' },
    { value: 'BETWEEN', label: 'BETWEEN' },
    { value: 'NOT BETWEEN', label: 'NOT BETWEEN' },
    { value: 'LIKE', label: 'LIKE' },
  ], []);

  const addStep = useCallback(() => {
    setSteps((prevSteps) => [
      ...prevSteps,
      {
        stepNo: '',
        stepName: 'Single Step',
        stepDesc: '',
        stepType: 'S',
        preAggregatorColumns: [],
        sourceTableID: '',
        sourceFilterSets: [{ filters: [], operator: '', values: [] }],
        joinColumns: [],
        allocationColumns: [],
        driverTableID: '',
        driverWeightColumn: '',
        driverFilterSets: [{ filters: [], operator: '', values: [] }],
      },
    ]);
    setErrors((prevErrors) => ({
      ...prevErrors,
      steps: [...prevErrors.steps, { sourceFilterSets: [{}], driverFilterSets: [{}] }],
    }));
  }, []);

  const removeStep = useCallback((index) => {
    if (steps.length === 1) {
      alert('At least one step is required.');
      return;
    }
    setSteps((prevSteps) => prevSteps.filter((_, i) => i !== index));
    setErrors((prevErrors) => ({
      ...prevErrors,
      steps: prevErrors.steps.filter((_, i) => i !== index),
    }));
  }, [steps.length]);

  const addFilterSet = useCallback((stepIndex, type) => {
    setSteps((prevSteps) => {
      const newSteps = [...prevSteps];
      const filterKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
      newSteps[stepIndex] = {
        ...newSteps[stepIndex],
        [filterKey]: [...newSteps[stepIndex][filterKey], { filters: [], operator: '', values: [] }],
      };
      return newSteps;
    });
    setErrors((prevErrors) => {
      const newStepsErrors = [...prevErrors.steps];
      const filterErrorsKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
      newStepsErrors[stepIndex] = {
        ...newStepsErrors[stepIndex],
        [filterErrorsKey]: [...newStepsErrors[stepIndex][filterErrorsKey], {}],
      };
      return { ...prevErrors, steps: newStepsErrors };
    });
  }, []);

  const removeFilterSet = useCallback((stepIndex, filterIndex, type) => {
    setSteps((prevSteps) => {
      const newSteps = [...prevSteps];
      const filterKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
      newSteps[stepIndex] = {
        ...newSteps[stepIndex],
        [filterKey]: newSteps[stepIndex][filterKey].filter((_, i) => i !== filterIndex),
      };
      return newSteps;
    });
    setErrors((prevErrors) => {
      const newStepsErrors = [...prevErrors.steps];
      const filterErrorsKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
      newStepsErrors[stepIndex] = {
        ...newStepsErrors[stepIndex],
        [filterErrorsKey]: newStepsErrors[stepIndex][filterErrorsKey].filter((_, i) => i !== filterIndex),
      };
      return { ...prevErrors, steps: newStepsErrors };
    });
  }, []);

  const validateForm = useCallback(() => {
    try {
      const newErrors = { rule: {}, steps: steps.map(() => ({ sourceFilterSets: [], driverFilterSets: [] })) };
      let isValid = true;

      if (!ruleData.num) {
        newErrors.rule.num = 'Rule Number is required';
        isValid = false;
      } else if (!/^[a-zA-Z0-9]+$/.test(ruleData.num)) {
        newErrors.rule.num = 'Rule Number must be alphanumeric';
        isValid = false;
      }
      if (!ruleData.name) {
        newErrors.rule.name = 'Rule Name is required';
        isValid = false;
      }
      if (!ruleData.desc) {
        newErrors.rule.desc = 'Description is required';
        isValid = false;
      }
      if (!ruleData.custRefID) {
        newErrors.rule.custRefID = 'Customer Reference ID is required';
        isValid = false;
      }
      if (!ruleData.pnlGroup) {
        newErrors.rule.pnlGroup = 'PnL Group is required';
        isValid = false;
      }
      if (!ruleData.ruleGroup) {
        newErrors.ruleGroup = 'Rule Group is required';
        isValid = false;
      }
      if (!ruleData.isActive) {
        newErrors.rule.isActive = 'Active status is required';
        isValid = false;
      }

      const stepNumbers = new Set();
      steps.forEach((step, index) => {
        const stepErrors = { sourceFilterSets: step.sourceFilterSets.map(() => ({})),
                            driverFilterSets: step.driverFilterSets.map(() => ({})) };
        if (!step.stepNo) {
          stepErrors.stepNo = 'Step Number is required';
          isValid = false;
        } else if (stepNumbers.has(step.stepNo)) {
          stepErrors.stepNo = 'Step Number must be unique';
          isValid = false;
        } else {
          stepNumbers.add(step.stepNo);
        }
        if (!step.stepName) {
          stepErrors.stepName = 'Step Name is required';
          isValid = false;
        }
        if (!step.stepDesc) {
          stepErrors.stepDesc = 'Step Description is required';
          isValid = false;
        }
        if (!step.stepType) {
          stepErrors.stepType = 'Step Type is required';
          isValid = false;
        }
        if (!step.preAggregatorColumns.length) {
          stepErrors.preAggregatorColumns = 'Pre-Aggregator Columns is required';
          isValid = false;
        }
        if (!step.sourceTableID) {
          stepErrors.sourceTableID = 'Source Table ID is required';
          isValid = false;
        }
        if (!step.sourceFilterSets.some(set => set.filters.length && set.operator && set.values.length)) {
          stepErrors.sourceFilterSets[0].filters = 'At least one complete Source Filter set is required';
          isValid = false;
        }
        step.sourceFilterSets.forEach((set, filterIndex) => {
          if (set.filters.length || set.operator || set.values.length) {
            if (!set.filters.length) {
              stepErrors.sourceFilterSets[filterIndex].filters = 'Source Filters is required';
              isValid = false;
            }
            if (!set.operator) {
              stepErrors.sourceFilterSets[filterIndex].operator = 'Source Operator is required';
              isValid = false;
            }
            if (!set.values.length) {
              stepErrors.sourceFilterSets[filterIndex].values = 'Source Values is required';
              isValid = false;
            }
          }
        });
        if (!step.joinColumns.length) {
          stepErrors.joinColumns = 'Join Columns is required';
          isValid = false;
        }
        if (!step.allocationColumns.length) {
          stepErrors.allocationColumns = 'Allocation Columns is required';
          isValid = false;
        }
        if (!step.driverTableID) {
          stepErrors.driverTableID = 'Driver Table ID is required';
          isValid = false;
        }
        if (!step.driverWeightColumn) {
          stepErrors.driverWeightColumn = 'Driver Weight Column is required';
          isValid = false;
        }
        if (!step.driverFilterSets.some(set => set.filters.length && set.operator && set.values.length)) {
          stepErrors.driverFilterSets[0].filters = 'At least one complete Driver Filter set is required';
          isValid = false;
        }
        step.driverFilterSets.forEach((set, filterIndex) => {
          if (set.filters.length || set.operator || set.values.length) {
            if (!set.filters.length) {
              stepErrors.driverFilterSets[filterIndex].filters = 'Driver Filters is required';
              isValid = false;
            }
            if (!set.operator) {
              stepErrors.driverFilterSets[filterIndex].operator = 'Driver Operator is required';
              isValid = false;
            }
            if (!set.values.length) {
              stepErrors.driverFilterSets[filterIndex].values = 'Driver Values is required';
              isValid = false;
            }
          }
        });
        newErrors.steps[index] = stepErrors;
      });

      setErrors(newErrors);
      return isValid;
    } catch (error) {
      alert('An error occurred during form validation. Please try again.');
      return false;
    }
  }, [ruleData, steps]);

  const parseColumns = (input) => input;

  const parseFilters = (filterSets) => {
    return filterSets
      .filter(set => set.filters.length && set.operator && set.values.length)
      .map(set => ({
        name: set.filters,
        filterType: set.operator,
        values: set.values,
      }));
  };

  const handleInputChange = useCallback((e, stepIndex = null) => {
    const { name, value } = e.target;
    if (stepIndex !== null) {
      setSteps((prevSteps) => {
        const newSteps = [...prevSteps];
        newSteps[stepIndex] = { ...newSteps[stepIndex], [name]: value };
        return newSteps;
      });
      setErrors((prevErrors) => {
        const newStepsErrors = [...prevErrors.steps];
        newStepsErrors[stepIndex] = {
          ...newStepsErrors[stepIndex],
          [name]: '',
        };
        return { ...prevErrors, steps: newStepsErrors };
      });
    } else {
      setRuleData((prevData) => ({
        ...prevData,
        [name]: value,
        ...(name === 'pnlGroup' ? { ruleGroup: '' } : {}),
      }));
      setErrors((prevErrors) => ({
        ...prevErrors,
        rule: { ...prevErrors.rule, [name]: '' },
      }));
    }
  }, []);

  const handleFilterSetChange = useCallback((e, stepIndex, filterIndex, type, field) => {
    const filterKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
    const value = field === 'operator' ? e.target.value : e.detail.map(option => option.value);
    setSteps((prevSteps) => {
      const newSteps = [...prevSteps];
      newSteps[stepIndex] = {
        ...newSteps[stepIndex],
        [filterKey]: newSteps[stepIndex][filterKey].map((set, i) =>
          i === filterIndex ? { ...set, [field]: value } : set
        ),
      };
      return newSteps;
    });
    setErrors((prevErrors) => {
      const newStepsErrors = [...prevErrors.steps];
      const filterErrorsKey = type === 'source' ? 'sourceFilterSets' : 'driverFilterSets';
      newStepsErrors[stepIndex] = {
        ...newStepsErrors[stepIndex],
        [filterErrorsKey]: newStepsErrors[stepIndex][filterErrorsKey].map((setErrors, i) =>
          i === filterIndex ? { ...setErrors, [field]: '' } : setErrors
        ),
      };
      return { ...prevErrors, steps: newStepsErrors };
    });
  }, []);

  const resetForm = useCallback(() => {
    setRuleData({
      num: '',
      name: '',
      desc: '',
      custRefID: '',
      ruleGroup: '',
      isActive: 'Y',
      pnlGroup: '',
    });
    setSteps([
      {
        stepNo: '',
        stepName: 'Single Step',
        stepDesc: '',
        stepType: 'S',
        preAggregatorColumns: [],
        sourceTableID: '',
        sourceFilterSets: [{ filters: [], operator: '', values: [] }],
        joinColumns: [],
        allocationColumns: [],
        driverTableID: '',
        driverWeightColumn: '',
        driverFilterSets: [{ filters: [], operator: '', values: [] }],
      },
    ]);
    setErrors({ rule: {}, steps: [{ sourceFilterSets: [{}], driverFilterSets: [{}] }] });
    setActiveTab('ruleInfo');
  }, []);

  const handleSave = useCallback(async () => {
    if (!validateForm()) {
      alert('Please fill out all required fields.');
      return;
    }

    setIsLoading(true);

    const ruleJson = {
      rules: {
        rule: [
          {
            num: ruleData.num,
            name: ruleData.name,
            desc: ruleData.desc,
            custRefID: ruleData.custRefID,
            ruleGroup: ruleData.ruleGroup,
            isActive: ruleData.isActive,
            Step: steps.map((step, index) => ({
              stepNo: step.stepNo || `${ruleData.num}.${index + 1}`,
              stepName: step.stepName === 'Single Step' ? 'single' : 'multi',
              stepDesc: step.stepDesc,
              stepType: step.stepType,
              isActive: 'Y',
              SourceTable: {
                id: step.sourceTableID,
                Name: step.sourceTableID,
              },
              sourceFilters: {
                columns: parseFilters(step.sourceFilterSets),
              },
              preAggregator: {
                columns: parseColumns(step.preAggregatorColumns),
              },
              join: {
                columns: parseColumns(step.joinColumns),
              },
              allocation: {
                columns: parseColumns(step.allocationColumns),
              },
              driver: {
                driverTableID: step.driverTableID,
                driverWeightColumn: step.driverWeightColumn,
                driverFilters: {
                  columns: parseFilters(step.driverFilterSets),
                },
              },
            })),
          },
        ],
      },
    };

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 10000);

    try {
      const response = await fetch('/api/rules', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
        body: JSON.stringify(ruleJson),
        signal: controller.signal,
      });

      clearTimeout(timeoutId);

      if (response.ok) {
        alert('Rule created successfully!');
        resetForm();
        navigate('/');
      } else {
        const errorData = await response.json().catch(() => ({ message: response.statusText }));
        alert(`Failed to create rule: ${errorData.message || response.statusText}`);
      }
    } catch (error) {
      if (error.name === 'AbortError') {
        alert('Request timed out. Please try again.');
      } else {
        alert('An error occurred while saving the rule. Please try again.');
      }
    } finally {
      setIsLoading(false);
    }
  }, [validateForm, ruleData, steps, resetForm, navigate]);

  const handleCancel = useCallback(() => {
    navigate('/');
  }, [navigate]);

  const renderTabContent = () => {
    switch (activeTab) {
      case 'ruleInfo':
        return (
          <div className={styles.tabContent}>
            {Object.values(errors.rule).some((error) => error) && (
              <div className={styles.errorSummary}>
                <h4>Please fix the following errors:</h4>
                <ul>
                  {Object.entries(errors.rule).map(([key, error]) => error && (
                    <li key={key}>{error}</li>
                  ))}
                </ul>
              </div>
            )}
            <h3 className={styles.sectionTitle}>Rule Information</h3>
            <div className={styles.formGrid}>
              <div className={styles.gridItem}>
                <McSelect
                  label="PnL Group"
                  name="pnlGroup"
                  value={ruleData.pnlGroup}
                  input={handleInputChange}
                  placeholder="Select a PnL Group"
                  required
                  invalid={!!errors.rule.pnlGroup}
                  invalidmessage={errors.rule.pnlGroup}
                >
                  {pnLGroups.map((group) => (
                    <McOption key={group} value={group}>
                      {group}
                    </McOption>
                  ))}
                </McSelect>
              </div>
              <div className={styles.gridItem}>
                <McSelect
                  label="Rule Group"
                  name="ruleGroup"
                  value={ruleData.ruleGroup}
                  input={handleInputChange}
                  placeholder={ruleGroups.length ? "Select a Rule Group" : "Select a PnL Group first"}
                  required
                  disabled={!ruleData.pnlGroup || !ruleGroups.length}
                  invalid={!!errors.rule.ruleGroup}
                  invalidmessage={errors.rule.ruleGroup}
                >
                  {ruleGroups.map((group) => (
                    <McOption key={group} value={group}>
                      {group}
                    </McOption>
                  ))}
                </McSelect>
              </div>
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Rule Number"
                name="num"
                value={ruleData.num}
                input={handleInputChange}
                placeholder="Enter rule number"
                required
                invalid={!!errors.rule.num}
                invalidmessage={errors.rule.num}
              />
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Rule Name"
                name="name"
                value={ruleData.name}
                input={handleInputChange}
                placeholder="Enter rule name"
                required
                invalid={!!errors.rule.name}
                invalidmessage={errors.rule.name}
              />
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Description"
                name="desc"
                value={ruleData.desc}
                input={handleInputChange}
                placeholder="Enter rule description"
                multiline
                rows={3}
                required
                invalid={!!errors.rule.desc}
                invalidmessage={errors.rule.desc}
              />
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Customer Reference ID"
                name="custRefID"
                value={ruleData.custRefID}
                input={handleInputChange}
                placeholder="Enter customer reference ID"
                required
                invalid={!!errors.rule.custRefID}
                invalidmessage={errors.rule.custRefID}
              />
            </div>
          </div>
        );
      case 'step':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Step Information</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Step Number"
                    name="stepNo"
                    value={step.stepNo}
                    input={(e) => handleInputChange(e, index)}
                    placeholder={`Enter step number (e.g., ${ruleData.num}.${index + 1})`}
                    required
                    invalid={!!errors.steps[index].stepNo}
                    invalidmessage={errors.steps[index].stepNo}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McSelect
                    label="Step Name"
                    name="stepName"
                    value={step.stepName}
                    input={(e) => handleInputChange(e, index)}
                    required
                    placeholder="Select Step Name"
                    invalid={!!errors.steps[index].stepName}
                    invalidmessage={errors.steps[index].stepName}
                  >
                    <McOption value="Single Step">Single Step</McOption>
                    <McOption value="Multi Step">Multi Step</McOption>
                  </McSelect>
                </div>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Step Description"
                    name="stepDesc"
                    value={step.stepDesc}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter step description"
                    multiline
                    rows={3}
                    required
                    invalid={!!errors.steps[index].stepDesc}
                    invalidmessage={errors.steps[index].stepDesc}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McSelect
                    label="Step Type"
                    name="stepType"
                    value={step.stepType}
                    input={(e) => handleInputChange(e, index)}
                    required
                    placeholder="Select Step Type"
                    invalid={!!errors.steps[index].stepType}
                    invalidmessage={errors.steps[index].stepType}
                  >
                    <McOption value="S">S</McOption>
                    <McOption value="M">M</McOption>
                  </McSelect>
                </div>
              </div>
            ))}
            <div className={styles.stepButtonContainer}>
              <McButton
                label="Add Step"
                appearance="secondary"
                click={addStep}
                className={styles.actionButton}
              />
              {steps.length > 1 && (
                <McButton
                  label="Remove Step"
                  appearance="neutral"
                  click={() => removeStep(steps.length - 1)}
                  className={styles.actionButton}
                />
              )}
            </div>
          </div>
        );
      case 'source':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Source Information</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Source Table ID"
                    name="sourceTableID"
                    value={step.sourceTableID}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter source table ID"
                    required
                    invalid={!!errors.steps[index].sourceTableID}
                    invalidmessage={errors.steps[index].sourceTableID}
                  />
                </div>
                {step.sourceFilterSets.map((filterSet, filterIndex) => (
                  <div key={filterIndex} className={styles.filterRow}>
                    <McMultiSelect
                      label="Source Filters"
                      name="sourceFilters"
                      value={filterSet.filters}
                      optionselected={(e) => handleFilterSetChange(e, index, filterIndex, 'source', 'filters')}
                      placeholder="Select source filters"
                      required
                      invalid={!!errors.steps[index].sourceFilterSets[filterIndex]?.filters}
                      invalidmessage={errors.steps[index].sourceFilterSets[filterIndex]?.filters}
                    >
                      {sourceFilterOptions.map((option) => (
                        <McOption key={option.value} value={option.value}>
                          {option.label}
                        </McOption>
                      ))}
                    </McMultiSelect>
                    <McSelect
                      label="Source Operator"
                      name="sourceOperator"
                      value={filterSet.operator}
                      input={(e) => handleFilterSetChange(e, index, filterIndex, 'source', 'operator')}
                      placeholder="Select an operator"
                      required
                      invalid={!!errors.steps[index].sourceFilterSets[filterIndex]?.operator}
                      invalidmessage={errors.steps[index].sourceFilterSets[filterIndex]?.operator}
                    >
                      {operatorOptions.map((option) => (
                        <McOption key={option.value} value={option.value}>
                          {option.label}
                        </McOption>
                      ))}
                    </McSelect>
                    <div className={styles.filterValueContainer}>
                      <McMultiSelect
                        label="Source Values"
                        name="sourceValues"
                        value={filterSet.values}
                        optionselected={(e) => handleFilterSetChange(e, index, filterIndex, 'source', 'values')}
                        placeholder="Select source values"
                        required
                        invalid={!!errors.steps[index].sourceFilterSets[filterIndex]?.values}
                        invalidmessage={errors.steps[index].sourceFilterSets[filterIndex]?.values}
                      >
                        {sourceValueOptions.map((option) => (
                          <McOption key={option.value} value={option.value}>
                            {option.label}
                          </McOption>
                        ))}
                      </McMultiSelect>
                      {step.sourceFilterSets.length > 1 && (
                        <McButton
                          label="Remove"
                          appearance="neutral"
                          click={() => removeFilterSet(index, filterIndex, 'source')}
                          className={styles.removeButton}
                        />
                      )}
                    </div>
                  </div>
                ))}
                <div className={styles.stepButtonContainer}>
                  <McButton
                    label="Add More"
                    appearance="secondary"
                    click={() => addFilterSet(index, 'source')}
                    className={styles.actionButton}
                  />
                </div>
              </div>
            ))}
          </div>
        );
      case 'preAggregate':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Pre-Aggregate Columns</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McMultiSelect
                    label="Pre-Aggregator Columns"
                    name="preAggregatorColumns"
                    value={step.preAggregatorColumns}
                    optionselected={(e) => handleMultiSelectChange(e, index, 'preAggregatorColumns')}
                    placeholder="Select pre-aggregator columns"
                    required
                    invalid={!!errors.steps[index].preAggregatorColumns}
                    invalidmessage={errors.steps[index].preAggregatorColumns}
                  >
                    {preAggregatorOptions.map((option) => (
                      <McOption key={option.value} value={option.value}>
                        {option.label}
                      </McOption>
                    ))}
                  </McMultiSelect>
                </div>
              </div>
            ))}
          </div>
        );
      case 'join':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Join Columns</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McMultiSelect
                    label="Join Columns"
                    name="joinColumns"
                    value={step.joinColumns}
                    optionselected={(e) => handleMultiSelectChange(e, index, 'joinColumns')}
                    placeholder="Select join columns"
                    required
                    invalid={!!errors.steps[index].joinColumns}
                    invalidmessage={errors.steps[index].joinColumns}
                  >
                    {joinColumnsOptions.map((option) => (
                      <McOption key={option.value} value={option.value}>
                        {option.label}
                      </McOption>
                    ))}
                  </McMultiSelect>
                </div>
              </div>
            ))}
          </div>
        );
      case 'allocation':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Allocation Columns</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McMultiSelect
                    label="Allocation Columns"
                    name="allocationColumns"
                    value={step.allocationColumns}
                    optionselected={(e) => handleMultiSelectChange(e, index, 'allocationColumns')}
                    placeholder="Select allocation columns"
                    required
                    invalid={!!errors.steps[index].allocationColumns}
                    invalidmessage={errors.steps[index].allocationColumns}
                  >
                    {allocationColumnsOptions.map((option) => (
                      <McOption key={option.value} value={option.value}>
                        {option.label}
                      </McOption>
                    ))}
                  </McMultiSelect>
                </div>
              </div>
            ))}
          </div>
        );
      case 'driver':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Driver Information</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Driver Table ID"
                    name="driverTableID"
                    value={step.driverTableID}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter driver table ID"
                    required
                    invalid={!!errors.steps[index].driverTableID}
                    invalidmessage={errors.steps[index].driverTableID}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Driver Weight Column"
                    name="driverWeightColumn"
                    value={step.driverWeightColumn}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter driver weight column"
                    required
                    invalid={!!errors.steps[index].driverWeightColumn}
                    invalidmessage={errors.steps[index].driverWeightColumn}
                  />
                </div>
                {step.driverFilterSets.map((filterSet, filterIndex) => (
                  <div key={filterIndex} className={styles.filterRow}>
                    <McMultiSelect
                      label="Driver Filters"
                      name="driverFilters"
                      value={filterSet.filters}
                      optionselected={(e) => handleFilterSetChange(e, index, filterIndex, 'driver', 'filters')}
                      placeholder="Select driver filters"
                      required
                      invalid={!!errors.steps[index].driverFilterSets[filterIndex]?.filters}
                      invalidmessage={errors.steps[index].driverFilterSets[filterIndex]?.filters}
                    >
                      {driverFilterOptions.map((option) => (
                        <McOption key={option.value} value={option.value}>
                          {option.label}
                        </McOption>
                      ))}
                    </McMultiSelect>
                    <McSelect
                      label="Driver Operator"
                      name="driverOperator"
                      value={filterSet.operator}
                      input={(e) => handleFilterSetChange(e, index, filterIndex, 'driver', 'operator')}
                      placeholder="Select an operator"
                      required
                      invalid={!!errors.steps[index].driverFilterSets[filterIndex]?.operator}
                      invalidmessage={errors.steps[index].driverFilterSets[filterIndex]?.operator}
                    >
                      {operatorOptions.map((option) => (
                        <McOption key={option.value} value={option.value}>
                          {option.label}
                        </McOption>
                      ))}
                    </McSelect>
                    <div className={styles.filterValueContainer}>
                      <McMultiSelect
                        label="Driver Values"
                        name="driverValues"
                        value={filterSet.values}
                        optionselected={(e) => handleFilterSetChange(e, index, filterIndex, 'driver', 'values')}
                        placeholder="Select driver values"
                        required
                        invalid={!!errors.steps[index].driverFilterSets[filterIndex]?.values}
                        invalidmessage={errors.steps[index].driverFilterSets[filterIndex]?.values}
                      >
                        {driverValueOptions.map((option) => (
                          <McOption key={option.value} value={option.value}>
                            {option.label}
                          </McOption>
                        ))}
                      </McMultiSelect>
                      {step.driverFilterSets.length > 1 && (
                        <McButton
                          label="Remove"
                          appearance="neutral"
                          click={() => removeFilterSet(index, filterIndex, 'driver')}
                          className={styles.removeButton}
                        />
                      )}
                    </div>
                  </div>
                ))}
                <div className={styles.stepButtonContainer}>
                  <McButton
                    label="Add More"
                    appearance="secondary"
                    click={() => addFilterSet(index, 'driver')}
                    className={styles.actionButton}
                  />
                </div>
              </div>
            ))}
          </div>
        );
      default:
        return <div className={styles.tabContent}>No Tab Selected</div>;
    }
  };

  return (
    <ErrorBoundary>
      <div className={styles.pageWrapper}>
        {isLoading && (
          <div className={styles.loader}>Loading...</div>
        )}
        <div className={styles.container}>
          <div className={styles.card}>
            <div className={styles.buttonContainer}>
              <McButton
                label="Back"
                appearance="neutral"
                click={handleCancel}
                className={styles.actionButton}
              />
              <McButton
                label="Save"
                appearance="primary"
                click={handleSave}
                className={styles.actionButton}
                loading={isLoading}
                disabled={isLoading}
              />
            </div>

            <div className={styles.tabs}>
              <button
                className={`${styles.tabButton} ${activeTab === 'ruleInfo' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('ruleInfo')}
              >
                Rule Info
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'step' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('step')}
              >
                Step
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'source' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('source')}
              >
                Source
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'preAggregate' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('preAggregate')}
              >
                Pre-Aggregate
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'join' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('join')}
              >
                Join
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'allocation' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('allocation')}
              >
                Allocation
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'driver' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('driver')}
              >
                Driver
              </button>
            </div>

            {renderTabContent()}
          </div>
        </div>
      </div>
    </ErrorBoundary>
  );
};

export default CreateRules;
C:\Users\"Benutzerordner"\AppData\Local\Packages\5319275A.WhatsAppDesktop_cv1g1gvanyjgm\LocalState\shared\transfers\
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { McButton, McInput, McMultiSelect } from '@maersk-global/mds-react-wrapper';
import { McSelect } from '@maersk-global/mds-react-wrapper/components-core/mc-select';
import { McOption } from '@maersk-global/mds-react-wrapper/components-core/mc-option';
import styles from '../styles/CreateRule.module.css';
import data from '../data/PnLGroup.json';

const CreateRules = () => {
  const navigate = useNavigate();
  const [activeTab, setActiveTab] = useState('ruleInfo');
  const [isLoading, setIsLoading] = useState(false);
  const [ruleData, setRuleData] = useState({
    num: '',
    name: '',
    desc: '',
    custRefID: '',
    ruleGroup: '',
    isActive: 'Y',
    pnlGroup: '',
  });
  const [steps, setSteps] = useState([
    {
      stepNo: '',
      stepName: 'Single Step',
      StepDesc: '',
      stepType: 'S',
      preAggregatorColumns: '',
      sourceTable: '',
      sourceFilters: '',
      joinColumns: '',
      allocationColumns: '',
      driverTableID: '',
      driverWeightColumn: '',
      driverFilters: '',
    },
  ]);
  const [errors, setErrors] = useState({ rule: {}, steps: [{}] });

  const pnlGroups = data.PnLGroups ? Object.keys(data.PnLGroups) : [];
  const ruleGroups = ruleData.pnlGroup && data.PnLGroups[ruleData.pnlGroup]
    ? data.PnLGroups[ruleData.pnlGroup].RuleGroups || []
    : [];

  console.log('pnlGroups:', pnlGroups);
  console.log('ruleGroups:', ruleGroups);

  const addStep = () => {
    setSteps((prevSteps) => [
      ...prevSteps,
      {
        stepNo: '',
        stepName: 'Single Step',
        StepDesc: '',
        stepType: 'S',
        preAggregatorColumns: '',
        sourceTable: '',
        sourceFilters: '',
        joinColumns: '',
        allocationColumns: '',
        driverTableID: '',
        driverWeightColumn: '',
        driverFilters: '',
      },
    ]);
    setErrors((prevErrors) => ({
      ...prevErrors,
      steps: [...prevErrors.steps, {}],
    }));
  };

  const removeStep = (index) => {
    if (steps.length === 1) {
      alert('At least one step is required.');
      return;
    }
    setSteps((prevSteps) => prevSteps.filter((_, i) => i !== index));
    setErrors((prevErrors) => ({
      ...prevErrors,
      steps: prevErrors.steps.filter((_, i) => i !== index),
    }));
  };

  const validateForm = () => {
    const newErrors = { rule: {}, steps: steps.map(() => ({})) };
    let isValid = true;

    if (!ruleData.num) {
      newErrors.rule.num = 'Rule Number is required';
      isValid = false;
    } else if (!/^[a-zA-Z0-9]+$/.test(ruleData.num)) {
      newErrors.rule.num = 'Rule Number must be alphanumeric';
      isValid = false;
    }
    if (!ruleData.name) {
      newErrors.rule.name = 'Rule Name is required';
      isValid = false;
    }
    if (!ruleData.desc) {
      newErrors.rule.desc = 'Description is required';
      isValid = false;
    }
    if (!ruleData.custRefID) {
      newErrors.rule.custRefID = 'Customer Reference ID is required';
      isValid = false;
    }
    if (!ruleData.pnlGroup) {
      newErrors.rule.pnlGroup = 'PnL Group is required';
      isValid = false;
    }
    if (!ruleData.ruleGroup) {
      newErrors.rule.ruleGroup = 'Rule Group is required';
      isValid = false;
    }
    if (!ruleData.isActive) {
      newErrors.rule.isActive = 'Active status is required';
      isValid = false;
    }

    const stepNumbers = new Set();
    steps.forEach((step, index) => {
      const stepErrors = {};
      if (!step.stepNo) {
        stepErrors.stepNo = 'Step Number is required';
        isValid = false;
      } else if (stepNumbers.has(step.stepNo)) {
        stepErrors.stepNo = 'Step Number must be unique';
        isValid = false;
      } else {
        stepNumbers.add(step.stepNo);
      }
      if (!step.stepName) {
        stepErrors.stepName = 'Step Name is required';
        isValid = false;
      }
      if (!step.StepDesc) {
        stepErrors.StepDesc = 'Step Description is required';
        isValid = false;
      }
      if (!step.stepType) {
        stepErrors.stepType = 'Step Type is required';
        isValid = false;
      }
      if (!step.preAggregatorColumns) {
        stepErrors.preAggregatorColumns = 'Pre-Aggregator Columns are required';
        isValid = false;
      }
      if (!step.sourceTable) {
        stepErrors.sourceTable = 'Source Table is required';
        isValid = false;
      }
      if (!step.sourceFilters) {
        stepErrors.sourceFilters = 'Source Filters are required';
        isValid = false;
      } else {
        try {
          parseFilters(step.sourceFilters);
        } catch (e) {
          stepErrors.sourceFilters = 'Invalid Source Filter format';
          isValid = false;
        }
      }
      if (!step.joinColumns) {
        stepErrors.joinColumns = 'Join Columns are required';
        isValid = false;
      }
      if (!step.allocationColumns) {
        stepErrors.allocationColumns = 'Allocation Columns are required';
        isValid = false;
      }
      if (!step.driverTableID) {
        stepErrors.driverTableID = 'Driver Table ID is required';
        isValid = false;
      }
      if (!step.driverWeightColumn) {
        stepErrors.driverWeightColumn = 'Driver Weight Column is required';
        isValid = false;
      }
      if (!step.driverFilters) {
        stepErrors.driverFilters = 'Driver Filters are required';
        isValid = false;
      } else {
        try {
          parseFilters(step.driverFilters);
        } catch (e) {
          stepErrors.driverFilters = 'Invalid Driver Filter format';
          isValid = false;
        }
      }
      newErrors.steps[index] = stepErrors;
    });

    setErrors(newErrors);
    return isValid;
  };

  const parseColumns = (input) => input.split(',').map((item) => item.trim()).filter((item) => item);

  const parseFilters = (input) => {
    if (!input) return [];
    const filters = input.split(';').map((item) => item.trim()).filter((item) => item);
    return filters.map((filter) => {
      const parts = filter.split(':').map((item) => item.trim());
      if (parts.length !== 3) {
        throw new Error('Invalid filter format');
      }
      const [name, filterType, values] = parts;
      if (!name || !filterType || !values) {
        throw new Error('Invalid filter format');
      }
      return { name, filterType, values };
    });
  };

  const handleInputChange = (e, stepIndex = null) => {
    const { name, value } = e.target;
    console.log(`Input changed: ${name} = ${value}${stepIndex !== null ? ` (Step ${stepIndex + 1})` : ''}`);

    if (stepIndex !== null) {
      setSteps((prevSteps) => {
        const newSteps = [...prevSteps];
        newSteps[stepIndex] = { ...newSteps[stepIndex], [name]: value };
        return newSteps;
      });
      setErrors((prevErrors) => ({
        ...prevErrors,
        steps: prevErrors.steps.map((stepErrors, i) =>
          i === stepIndex ? { ...stepErrors, [name]: '' } : stepErrors
        ),
      }));
    } else {
      setRuleData((prevData) => {
        const newData = {
          ...prevData,
          [name]: value,
          ...(name === 'pnlGroup' ? { ruleGroup: '' } : {}),
        };
        console.log('Updated ruleData:', newData);
        return newData;
      });
      setErrors((prevErrors) => ({
        ...prevErrors,
        rule: { ...prevErrors.rule, [name]: '' },
      }));
    }
  };

  const resetForm = () => {
    setRuleData({
      num: '',
      name: '',
      desc: '',
      custRefID: '',
      ruleGroup: '',
      isActive: 'Y',
      pnlGroup: '',
    });
    setSteps([
      {
        stepNo: '',
        stepName: 'Single Step',
        StepDesc: '',
        stepType: 'S',
        preAggregatorColumns: '',
        sourceTable: '',
        sourceFilters: '',
        joinColumns: '',
        allocationColumns: '',
        driverTableID: '',
        driverWeightColumn: '',
        driverFilters: '',
      },
    ]);
    setErrors({ rule: {}, steps: [{}] });
    setActiveTab('ruleInfo');
  };

  const handleSave = async () => {
    if (!validateForm()) {
      console.log('Validation failed:', JSON.stringify(errors, null, 2));
      alert('Please fill out all required fields.');
      return;
    }

    setIsLoading(true);

    const ruleJson = {
      rules: {
        rule: [
          {
            num: ruleData.num,
            name: ruleData.name,
            desc: ruleData.desc,
            custRefID: ruleData.custRefID,
            ruleGroup: ruleData.ruleGroup,
            isActive: ruleData.isActive,
            Step: steps.map((step, index) => ({
              stepNo: step.stepNo || `${ruleData.num}.${index + 1}`,
              stepName: step.stepName === 'Single Step' ? 'single' : 'multi',
              StepDesc: step.StepDesc,
              stepType: step.stepType,
              isActive: 'Y',
              SourceTable: {
                id: '1',
                Name: step.sourceTable,
              },
              sourceFilters: {
                columns: parseFilters(step.sourceFilters),
              },
              preAggregator: {
                columns: parseColumns(step.preAggregatorColumns),
              },
              join: {
                columns: parseColumns(step.joinColumns),
              },
              allocation: {
                columns: parseColumns(step.allocationColumns),
              },
              driver: {
                driverTableID: step.driverTableID,
                driverWeightColumn: step.driverWeightColumn,
                driverFilters: {
                  columns: parseFilters(step.driverFilters),
                },
              },
            })),
          },
        ],
      },
    };

    console.log('Saving Rule Data:', JSON.stringify(ruleJson, null, 2));

    try {
      const response = await fetch('/api/rules', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
        body: JSON.stringify(ruleJson),
      });

      if (response.ok) {
        console.log('Rule created successfully');
        alert('Rule created successfully!');
        resetForm();
        navigate('/');
      } else {
        const errorData = await response.json().catch(() => ({ message: response.statusText }));
        console.error('Failed to create rule:', response.status, errorData);
        alert(`Failed to create rule: ${errorData.message || response.statusText}`);
      }
    } catch (error) {
      console.error('Error during API call:', error.message);
      alert('An error occurred while saving the rule. Please try again.');
    } finally {
      setIsLoading(false);
    }
  };

  const handleCancel = () => {
    console.log('Cancelling rule creation');
    navigate('/');
  };

  const renderTabContent = () => {
    console.log('Rendering tab:', activeTab);
    switch (activeTab) {
      case 'ruleInfo':
        return (
          <div className={styles.tabContent}>
            {Object.values(errors.rule).some((error) => error) && (
              <div className={styles.errorSummary}>
                <h4>Please fix the following errors:</h4>
                <ul>
                  {Object.entries(errors.rule).map(([key, error]) => error && (
                    <li key={key}>{error}</li>
                  ))}
                </ul>
              </div>
            )}
            <h3 className={styles.sectionTitle}>Rule Information</h3>
            <div className={styles.formGrid}>
              <div className={styles.gridItem}>
                <McSelect
                  label="PnL Group"
                  name="pnlGroup"
                  value={ruleData.pnlGroup}
                  input={handleInputChange}
                  placeholder="Select a PnL Group"
                  required
                  invalid={!!errors.rule.pnlGroup}
                  invalidmessage={errors.rule.pnlGroup}
                >
                  {pnlGroups.map((group) => (
                    <McOption key={group} value={group}>
                      {group}
                    </McOption>
                  ))}
                </McSelect>
              </div>
              <div className={styles.gridItem}>
                <McSelect
                  label="Rule Group"
                  name="ruleGroup"
                  value={ruleData.ruleGroup}
                  input={handleInputChange}
                  placeholder={ruleGroups.length ? "Select a Rule Group" : "Select a PnL Group first"}
                  required
                  disabled={!ruleData.pnlGroup || !ruleGroups.length}
                  invalid={!!errors.rule.ruleGroup}
                  invalidmessage={errors.rule.ruleGroup}
                >
                  {ruleGroups.map((group) => (
                    <McOption key={group} value={group}>
                      {group}
                    </McOption>
                  ))}
                </McSelect>
              </div>
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Rule Number"
                name="num"
                value={ruleData.num}
                input={handleInputChange}
                placeholder="Enter rule number"
                required
                invalid={!!errors.rule.num}
                invalidmessage={errors.rule.num}
              />
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Rule Name"
                name="name"
                value={ruleData.name}
                input={handleInputChange}
                placeholder="Enter rule name"
                required
                invalid={!!errors.rule.name}
                invalidmessage={errors.rule.name}
              />
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Description"
                name="desc"
                value={ruleData.desc}
                input={handleInputChange}
                placeholder="Enter rule description"
                multiline
                rows={3}
                required
                invalid={!!errors.rule.desc}
                invalidmessage={errors.rule.desc}
              />
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Customer Reference ID"
                name="custRefID"
                value={ruleData.custRefID}
                input={handleInputChange}
                placeholder="Enter customer reference ID"
                required
                invalid={!!errors.rule.custRefID}
                invalidmessage={errors.rule.custRefID}
              />
            </div>
          </div>
        );
      case 'step':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Step Information</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Step Number"
                    name="stepNo"
                    value={step.stepNo}
                    input={(e) => handleInputChange(e, index)}
                    placeholder={`Enter step number (e.g., ${ruleData.num}.${index + 1})`}
                    required
                    invalid={!!errors.steps[index].stepNo}
                    invalidmessage={errors.steps[index].stepNo}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McSelect
                    label="Step Name"
                    name="stepName"
                    value={step.stepName}
                    input={(e) => handleInputChange(e, index)}
                    required
                    placeholder="Select Step Name"
                    invalid={!!errors.steps[index].stepName}
                    invalidmessage={errors.steps[index].stepName}
                  >
                    <McOption value="Single Step">Single Step</McOption>
                    <McOption value="Multi Step">Multi Step</McOption>
                  </McSelect>
                </div>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Step Description"
                    name="StepDesc"
                    value={step.StepDesc}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter step description"
                    multiline
                    rows={3}
                    required
                    invalid={!!errors.steps[index].StepDesc}
                    invalidmessage={errors.steps[index].StepDesc}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McSelect
                    label="Step Type"
                    name="stepType"
                    value={step.stepType}
                    input={(e) => handleInputChange(e, index)}
                    required
                    placeholder="Select Step Type"
                    invalid={!!errors.steps[index].stepType}
                    invalidmessage={errors.steps[index].stepType}
                  >
                    <McOption value="S">S</McOption>
                    <McOption value="M">M</McOption>
                  </McSelect>
                </div>
              </div>
            ))}
            <div className={styles.stepButtonContainer}>
              <McButton
                label="Add Step"
                appearance="secondary"
                click={addStep}
                className={styles.actionButton}
              />
              {steps.length > 1 && (
                <McButton
                  label="Remove Step"
                  appearance="neutral"
                  click={() => removeStep(steps.length - 1)}
                  className={styles.actionButton}
                />
              )}
            </div>
          </div>
        );
      case 'source':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Source Information</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Source Table"
                    name="sourceTable"
                    value={step.sourceTable}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter source table name"
                    required
                    invalid={!!errors.steps[index].sourceTable}
                    invalidmessage={errors.steps[index].sourceTable}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Source Filters"
                    name="sourceFilters"
                    value={step.sourceFilters}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter filters (e.g., PNL_LINE:IN:PnL.DVC.214,PnL.DVC.215;MOVE_TYPE:EQ:EX)"
                    multiline
                    rows={3}
                    required
                    invalid={!!errors.steps[index].sourceFilters}
                    invalidmessage={errors.steps[index].sourceFilters}
                  />
                </div>
              </div>
            ))}
          </div>
        );
      case 'preAggregate':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Pre-Aggregate Columns</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Pre-Aggregator Columns"
                    name="preAggregatorColumns"
                    value={step.preAggregatorColumns}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter columns (comma-separated)"
                    multiline
                    rows={3}
                    required
                    invalid={!!errors.steps[index].preAggregatorColumns}
                    invalidmessage={errors.steps[index].preAggregatorColumns}
                  />
                </div>
              </div>
            ))}
          </div>
        );
      case 'join':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Join Columns</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Join Columns"
                    name="joinColumns"
                    value={step.joinColumns}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter columns (comma-separated)"
                    multiline
                    rows={3}
                    required
                    invalid={!!errors.steps[index].joinColumns}
                    invalidmessage={errors.steps[index].joinColumns}
                  />
                </div>
              </div>
            ))}
          </div>
        );
      case 'allocation':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Allocation Columns</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Allocation Columns"
                    name="allocationColumns"
                    value={step.allocationColumns}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter columns (comma-separated)"
                    multiline
                    rows={3}
                    required
                    invalid={!!errors.steps[index].allocationColumns}
                    invalidmessage={errors.steps[index].allocationColumns}
                  />
                </div>
              </div>
            ))}
          </div>
        );
      case 'driver':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Driver Information</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Driver Table ID"
                    name="driverTableID"
                    value={step.driverTableID}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter driver table ID"
                    required
                    invalid={!!errors.steps[index].driverTableID}
                    invalidmessage={errors.steps[index].driverTableID}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Driver Weight Column"
                    name="driverWeightColumn"
                    value={step.driverWeightColumn}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter driver weight column"
                    required
                    invalid={!!errors.steps[index].driverWeightColumn}
                    invalidmessage={errors.steps[index].driverWeightColumn}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Driver Filters"
                    name="driverFilters"
                    value={step.driverFilters}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter filters"
                    multiline
                    rows={3}
                    required
                    invalid={!!errors.steps[index].driverFilters}
                    invalidmessage={errors.steps[index].driverFilters}
                  />
                </div>
              </div>
            ))}
          </div>
        );
      default:
        return <div className={styles.tabContent}>No Tab Selected</div>;
    }
  };

  return (
    <div className={styles.pageWrapper}>
      <div className={styles.container}>
        <div className={styles.card}>
          <div className={styles.buttonContainer}>
            <McButton
              label="Back"
              appearance="neutral"
              click={handleCancel}
              className={styles.actionButton}
            />
            <McButton
              label="Save"
              appearance="primary"
              click={handleSave}
              className={styles.actionButton}
              loading={isLoading}
              disabled={isLoading}
            />
          </div>

          <div className={styles.tabs}>
            <button
              className={`${styles.tabButton} ${activeTab === 'ruleInfo' ? styles.activeTab : ''}`}
              onClick={() => setActiveTab('ruleInfo')}
            >
              Rule Info
            </button>
            <button
              className={`${styles.tabButton} ${activeTab === 'step' ? styles.activeTab : ''}`}
              onClick={() => setActiveTab('step')}
            >
              Step
            </button>
            <button
              className={`${styles.tabButton} ${activeTab === 'source' ? styles.activeTab : ''}`}
              onClick={() => setActiveTab('source')}
            >
              Source
            </button>
            <button
              className={`${styles.tabButton} ${activeTab === 'preAggregate' ? styles.activeTab : ''}`}
              onClick={() => setActiveTab('preAggregate')}
            >
              Pre-Aggregate
            </button>
            <button
              className={`${styles.tabButton} ${activeTab === 'join' ? styles.activeTab : ''}`}
              onClick={() => setActiveTab('join')}
            >
              Join
            </button>
            <button
              className={`${styles.tabButton} ${activeTab === 'allocation' ? styles.activeTab : ''}`}
              onClick={() => setActiveTab('allocation')}
            >
              Allocation
            </button>
            <button
              className={`${styles.tabButton} ${activeTab === 'driver' ? styles.activeTab : ''}`}
              onClick={() => setActiveTab('driver')}
            >
              Driver
            </button>
          </div>

          {renderTabContent()}
        </div>
      </div>
    </div>
  );
};

export default CreateRules;

import React, { useState, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { McButton, McInput, McMultiSelect, McSelect } from '@maersk-global/mds-react-wrapper';
import { McOption } from '@maersk-global/mds-react-wrapper/components-core/mc-option';
import styles from '../styles/CreateRule.module.css';
import data from '../data/PnLGroup.json';

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  render() {
    if (this.state.hasError) {
      return <div>Something went wrong. Please refresh the page.</div>;
    }
    return this.props.children;
  }
}

const CreateRules = () => {
  const navigate = useNavigate();
  const [activeTab, setActiveTab] = useState('ruleInfo');
  const [isLoading, setIsLoading] = useState(false);
  const [ruleData, setRuleData] = useState({
    num: '',
    name: '',
    desc: '',
    custRefID: '',
    ruleGroup: '',
    isActive: 'Y',
    pnlGroup: '',
  });
  const [steps, setSteps] = useState([
    {
      stepNo: '',
      stepName: 'Single Step',
      stepDesc: '',
      stepType: 'S',
      preAggregatorColumns: [],
      sourceTableID: '',
      sourceFilters: [],
      sourceOperator: '',
      joinColumns: [],
      allocationColumns: [],
      driverTableID: '',
      driverWeightColumn: '',
      driverFilters: [],
      driverOperator: '',
    },
  ]);
  const [errors, setErrors] = useState({ rule: {}, steps: [{}] });

  const pnLGroups = data.PnLGroups && typeof data.PnLGroups === 'object'
    ? Object.keys(data.PnLGroups)
    : [];
  const ruleGroups = ruleData.pnlGroup && data.PnLGroups[ruleData.pnlGroup]
    ? data.PnLGroups[ruleData.pnlGroup].RuleGroups || []
    : [];

  const sourceFilterOptions = [
    { value: 'Source_Filter_1', label: 'Source Filter 1' },
    { value: 'Source_Filter_2', label: 'Source Filter 2' },
    { value: 'Source_Filter_3', label: 'Source Filter 3' },
  ];
  const preAggregatorOptions = [
    { value: 'column1', label: 'Column 1' },
    { value: 'column2', label: 'Column 2' },
    { value: 'column3', label: 'Column 3' },
  ];
  const joinColumnsOptions = [
    { value: 'join_col1', label: 'Join Column 1' },
    { value: 'join_col2', label: 'Join Column 2' },
    { value: 'join_col3', label: 'Join Column 3' },
  ];
  const allocationColumnsOptions = [
    { value: 'alloc_col1', label: 'Allocation Column 1' },
    { value: 'alloc_col2', label: 'Allocation Column 2' },
    { value: 'alloc_col3', label: 'Allocation Column 3' },
  ];
  const driverFilterOptions = [
    { value: 'Driver_Type_1', label: 'Driver Type: Type 1' },
    { value: 'Driver_Type_2', label: 'Driver Type: Type 2' },
    { value: 'Driver_Status_Active', label: 'Driver Status: Active' },
  ];

  const operatorOptions = useMemo(() => [
    { value: 'IN', label: 'IN' },
    { value: 'NOT IN', label: 'NOT IN' },
    { value: 'EQ', label: 'EQ' },
    { value: 'NTEQ', label: 'NTEQ' },
    { value: 'IS NULL', label: 'IS NULL' },
    { value: 'GT', label: 'GT' },
    { value: 'LT', label: 'LT' },
    { value: 'GTEQ', label: 'GTEQ' },
    { value: 'LTEQ', label: 'LTEQ' },
    { value: 'BETWEEN', label: 'BETWEEN' },
    { value: 'NOT BETWEEN', label: 'NOT BETWEEN' },
    { value: 'LIKE', label: 'LIKE' },
  ], []);

  const addStep = useCallback(() => {
    setSteps((prevSteps) => [
      ...prevSteps,
      {
        stepNo: '',
        stepName: 'Single Step',
        stepDesc: '',
        stepType: 'S',
        preAggregatorColumns: [],
        sourceTableID: '',
        sourceFilters: [],
        sourceOperator: '',
        joinColumns: [],
        allocationColumns: [],
        driverTableID: '',
        driverWeightColumn: '',
        driverFilters: [],
        driverOperator: '',
      },
    ]);
    setErrors((prevErrors) => ({
      ...prevErrors,
      steps: [...prevErrors.steps, {}],
    }));
  }, []);

  const removeStep = useCallback((index) => {
    if (steps.length === 1) {
      alert('At least one step is required.');
      return;
    }
    setSteps((prevSteps) => prevSteps.filter((_, i) => i !== index));
    setErrors((prevErrors) => ({
      ...prevErrors,
      steps: prevErrors.steps.filter((_, i) => i !== index),
    }));
  }, [steps.length]);

  const validateForm = useCallback(() => {
    try {
      const newErrors = { rule: {}, steps: steps.map(() => ({})) };
      let isValid = true;

      if (!ruleData.num) {
        newErrors.rule.num = 'Rule Number is required';
        isValid = false;
      } else if (!/^[a-zA-Z0-9]+$/.test(ruleData.num)) {
        newErrors.rule.num = 'Rule Number must be alphanumeric';
        isValid = false;
      }
      if (!ruleData.name) {
        newErrors.rule.name = 'Rule Name is required';
        isValid = false;
      }
      if (!ruleData.desc) {
        newErrors.rule.desc = 'Description is required';
        isValid = false;
      }
      if (!ruleData.custRefID) {
        newErrors.rule.custRefID = 'Customer Reference ID is required';
        isValid = false;
      }
      if (!ruleData.pnlGroup) {
        newErrors.rule.pnlGroup = 'PnL Group is required';
        isValid = false;
      }
      if (!ruleData.ruleGroup) {
        newErrors.rule.ruleGroup = 'Rule Group is required';
        isValid = false;
      }
      if (!ruleData.isActive) {
        newErrors.rule.isActive = 'Active status is required';
        isValid = false;
      }

      const stepNumbers = new Set();
      steps.forEach((step, index) => {
        const stepErrors = {};
        if (!step.stepNo) {
          stepErrors.stepNo = 'Step Number is required';
          isValid = false;
        } else if (stepNumbers.has(step.stepNo)) {
          stepErrors.stepNo = 'Step Number must be unique';
          isValid = false;
        } else {
          stepNumbers.add(step.stepNo);
        }
        if (!step.stepName) {
          stepErrors.stepName = 'Step Name is required';
          isValid = false;
        }
        if (!step.stepDesc) {
          stepErrors.stepDesc = 'Step Description is required';
          isValid = false;
        }
        if (!step.stepType) {
          stepErrors.stepType = 'Step Type is required';
          isValid = false;
        }
        if (!step.preAggregatorColumns.length) {
          stepErrors.preAggregatorColumns = 'Pre-Aggregator Columns are required';
          isValid = false;
        }
        if (!step.sourceTableID) {
          stepErrors.sourceTableID = 'Source Table ID is required';
          isValid = false;
        }
        if (!step.sourceFilters.length) {
          stepErrors.sourceFilters = 'Source Filters are required';
          isValid = false;
        }
        if (!step.sourceOperator) {
          stepErrors.sourceOperator = 'Source Operator is required';
          isValid = false;
        }
        if (!step.joinColumns.length) {
          stepErrors.joinColumns = 'Join Columns are required';
          isValid = false;
        }
        if (!step.allocationColumns.length) {
          stepErrors.allocationColumns = 'Allocation Columns are required';
          isValid = false;
        }
        if (!step.driverTableID) {
          stepErrors.driverTableID = 'Driver Table ID is required';
          isValid = false;
        }
        if (!step.driverWeightColumn) {
          stepErrors.driverWeightColumn = 'Driver Weight Column is required';
          isValid = false;
        }
        if (!step.driverFilters.length) {
          stepErrors.driverFilters = 'Driver Filters are required';
          isValid = false;
        }
        if (!step.driverOperator) {
          stepErrors.driverOperator = 'Driver Operator is required';
          isValid = false;
        }
        newErrors.steps[index] = stepErrors;
      });

      setErrors(newErrors);
      return isValid;
    } catch (error) {
      alert('An error occurred during form validation. Please try again.');
      return false;
    }
  }, [ruleData, steps]);

  const parseColumns = (input) => input;

  const parseFilters = (filters, operator) => {
    if (!filters.length || !operator) return [];
    return filters.map((filter) => ({
      name: filter,
      filterType: operator,
      values: filter,
    }));
  };

  const handleInputChange = useCallback((e, stepIndex = null) => {
    const { name, value } = e.target;
    if (stepIndex !== null) {
      setSteps((prevSteps) => {
        const newSteps = [...prevSteps];
        newSteps[stepIndex] = { ...newSteps[stepIndex], [name]: value };
        return newSteps;
      });
      setErrors((prevErrors) => ({
        ...prevErrors,
        steps: prevErrors.steps.map((stepErrors, i) =>
          i === stepIndex ? { ...stepErrors, [name]: '' } : stepErrors
        ),
      }));
    } else {
      setRuleData((prevData) => ({
        ...prevData,
        [name]: value,
        ...(name === 'pnlGroup' ? { ruleGroup: '' } : {}),
      }));
      setErrors((prevErrors) => ({
        ...prevErrors,
        rule: { ...prevErrors.rule, [name]: '' },
      }));
    }
  }, []);

  const handleMultiSelectChange = useCallback((e, stepIndex, fieldName) => {
    const selectedValues = e.detail.map((option) => option.value);
    setSteps((prevSteps) => {
      const newSteps = [...prevSteps];
      newSteps[stepIndex] = { ...newSteps[stepIndex], [fieldName]: selectedValues };
      return newSteps;
    });
    setErrors((prevErrors) => ({
      ...prevErrors,
      steps: prevErrors.steps.map((stepErrors, i) =>
        i === stepIndex ? { ...stepErrors, [fieldName]: '' } : stepErrors
      ),
    }));
  }, []);

  const resetForm = useCallback(() => {
    setRuleData({
      num: '',
      name: '',
      desc: '',
      custRefID: '',
      ruleGroup: '',
      isActive: 'Y',
      pnlGroup: '',
    });
    setSteps([
      {
        stepNo: '',
        stepName: 'Single Step',
        stepDesc: '',
        stepType: 'S',
        preAggregatorColumns: [],
        sourceTableID: '',
        sourceFilters: [],
        sourceOperator: '',
        joinColumns: [],
        allocationColumns: [],
        driverTableID: '',
        driverWeightColumn: '',
        driverFilters: [],
        driverOperator: '',
      },
    ]);
    setErrors({ rule: {}, steps: [{}] });
    setActiveTab('ruleInfo');
  }, []);

  const handleSave = useCallback(async () => {
    if (!validateForm()) {
      alert('Please fill out all required fields.');
      return;
    }

    setIsLoading(true);

    const ruleJson = {
      rules: {
        rule: [
          {
            num: ruleData.num,
            name: ruleData.name,
            desc: ruleData.desc,
            custRefID: ruleData.custRefID,
            ruleGroup: ruleData.ruleGroup,
            isActive: ruleData.isActive,
            Step: steps.map((step, index) => ({
              stepNo: step.stepNo || `${ruleData.num}.${index + 1}`,
              stepName: step.stepName === 'Single Step' ? 'single' : 'multi',
              stepDesc: step.stepDesc,
              stepType: step.stepType,
              isActive: 'Y',
              SourceTable: {
                id: step.sourceTableID,
                Name: step.sourceTableID,
              },
              sourceFilters: {
                columns: parseFilters(step.sourceFilters, step.sourceOperator),
                operator: step.sourceOperator,
              },
              preAggregator: {
                columns: parseColumns(step.preAggregatorColumns),
              },
              join: {
                columns: parseColumns(step.joinColumns),
              },
              allocation: {
                columns: parseColumns(step.allocationColumns),
              },
              driver: {
                driverTableID: step.driverTableID,
                driverWeightColumn: step.driverWeightColumn,
                driverFilters: {
                  columns: parseFilters(step.driverFilters, step.driverOperator),
                  operator: step.driverOperator,
                },
              },
            })),
          },
        ],
      },
    };

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 10000);

    try {
      const response = await fetch('/api/rules', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
        body: JSON.stringify(ruleJson),
        signal: controller.signal,
      });

      clearTimeout(timeoutId);

      if (response.ok) {
        alert('Rule created successfully!');
        resetForm();
        navigate('/');
      } else {
        const errorData = await response.json().catch(() => ({ message: response.statusText }));
        alert(`Failed to create rule: ${errorData.message || response.statusText}`);
      }
    } catch (error) {
      if (error.name === 'AbortError') {
        alert('Request timed out. Please try again.');
      } else {
        alert('An error occurred while saving the rule. Please try again.');
      }
    } finally {
      setIsLoading(false);
    }
  }, [validateForm, ruleData, steps, resetForm, navigate]);

  const handleCancel = useCallback(() => {
    navigate('/');
  }, [navigate]);

  const renderTabContent = () => {
    switch (activeTab) {
      case 'ruleInfo':
        return (
          <div className={styles.tabContent}>
            {Object.values(errors.rule).some((error) => error) && (
              <div className={styles.errorSummary}>
                <h4>Please fix the following errors:</h4>
                <ul>
                  {Object.entries(errors.rule).map(([key, error]) => error && (
                    <li key={key}>{error}</li>
                  ))}
                </ul>
              </div>
            )}
            <h3 className={styles.sectionTitle}>Rule Information</h3>
            <div className={styles.formGrid}>
              <div className={styles.gridItem}>
                <McSelect
                  label="PnL Group"
                  name="pnlGroup"
                  value={ruleData.pnlGroup}
                  input={handleInputChange}
                  placeholder="Select a PnL Group"
                  required
                  invalid={!!errors.rule.pnlGroup}
                  invalidmessage={errors.rule.pnlGroup}
                >
                  {pnLGroups.map((group) => (
                    <McOption key={group} value={group}>
                      {group}
                    </McOption>
                  ))}
                </McSelect>
              </div>
              <div className={styles.gridItem}>
                <McSelect
                  label="Rule Group"
                  name="ruleGroup"
                  value={ruleData.ruleGroup}
                  input={handleInputChange}
                  placeholder={ruleGroups.length ? "Select a Rule Group" : "Select a PnL Group first"}
                  required
                  disabled={!ruleData.pnlGroup || !ruleGroups.length}
                  invalid={!!errors.rule.ruleGroup}
                  invalidmessage={errors.rule.ruleGroup}
                >
                  {ruleGroups.map((group) => (
                    <McOption key={group} value={group}>
                      {group}
                    </McOption>
                  ))}
                </McSelect>
              </div>
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Rule Number"
                name="num"
                value={ruleData.num}
                input={handleInputChange}
                placeholder="Enter rule number"
                required
                invalid={!!errors.rule.num}
                invalidmessage={errors.rule.num}
              />
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Rule Name"
                name="name"
                value={ruleData.name}
                input={handleInputChange}
                placeholder="Enter rule name"
                required
                invalid={!!errors.rule.name}
                invalidmessage={errors.rule.name}
              />
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Description"
                name="desc"
                value={ruleData.desc}
                input={handleInputChange}
                placeholder="Enter rule description"
                multiline
                rows={3}
                required
                invalid={!!errors.rule.desc}
                invalidmessage={errors.rule.desc}
              />
            </div>
            <div className={styles.inputGroup}>
              <McInput
                label="Customer Reference ID"
                name="custRefID"
                value={ruleData.custRefID}
                input={handleInputChange}
                placeholder="Enter customer reference ID"
                required
                invalid={!!errors.rule.custRefID}
                invalidmessage={errors.rule.custRefID}
              />
            </div>
          </div>
        );
      case 'step':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Step Information</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Step Number"
                    name="stepNo"
                    value={step.stepNo}
                    input={(e) => handleInputChange(e, index)}
                    placeholder={`Enter step number (e.g., ${ruleData.num}.${index + 1})`}
                    required
                    invalid={!!errors.steps[index].stepNo}
                    invalidmessage={errors.steps[index].stepNo}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McSelect
                    label="Step Name"
                    name="stepName"
                    value={step.stepName}
                    input={(e) => handleInputChange(e, index)}
                    required
                    placeholder="Select Step Name"
                    invalid={!!errors.steps[index].stepName}
                    invalidmessage={errors.steps[index].stepName}
                  >
                    <McOption value="Single Step">Single Step</McOption>
                    <McOption value="Multi Step">Multi Step</McOption>
                  </McSelect>
                </div>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Step Description"
                    name="stepDesc"
                    value={step.stepDesc}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter step description"
                    multiline
                    rows={3}
                    required
                    invalid={!!errors.steps[index].stepDesc}
                    invalidmessage={errors.steps[index].stepDesc}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McSelect
                    label="Step Type"
                    name="stepType"
                    value={step.stepType}
                    input={(e) => handleInputChange(e, index)}
                    required
                    placeholder="Select Step Type"
                    invalid={!!errors.steps[index].stepType}
                    invalidmessage={errors.steps[index].stepType}
                  >
                    <McOption value="S">S</McOption>
                    <McOption value="M">M</McOption>
                  </McSelect>
                </div>
              </div>
            ))}
            <div className={styles.stepButtonContainer}>
              <McButton
                label="Add Step"
                appearance="secondary"
                click={addStep}
                className={styles.actionButton}
              />
              {steps.length > 1 && (
                <McButton
                  label="Remove Step"
                  appearance="neutral"
                  click={() => removeStep(steps.length - 1)}
                  className={styles.actionButton}
                />
              )}
            </div>
          </div>
        );
      case 'source':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Source Information</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Source Table ID"
                    name="sourceTableID"
                    value={step.sourceTableID}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter source table ID"
                    required
                    invalid={!!errors.steps[index].sourceTableID}
                    invalidmessage={errors.steps[index].sourceTableID}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McMultiSelect
                    label="Source Filters"
                    name="sourceFilters"
                    value={step.sourceFilters}
                    optionselected={(e) => handleMultiSelectChange(e, index, 'sourceFilters')}
                    placeholder="Select source filters"
                    required
                    invalid={!!errors.steps[index].sourceFilters}
                    invalidmessage={errors.steps[index].sourceFilters}
                  >
                    {sourceFilterOptions.map((option) => (
                      <McOption key={option.value} value={option.value}>
                        {option.label}
                      </McOption>
                    ))}
                  </McMultiSelect>
                </div>
                <div className={styles.inputGroup}>
                  <McSelect
                    label="Source Operator"
                    name="sourceOperator"
                    value={step.sourceOperator}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Select an operator"
                    required
                    invalid={!!errors.steps[index].sourceOperator}
                    invalidmessage={errors.steps[index].sourceOperator}
                  >
                    {operatorOptions.map((option) => (
                      <McOption key={option.value} value={option.value}>
                        {option.label}
                      </McOption>
                    ))}
                  </McSelect>
                </div>
              </div>
            ))}
          </div>
        );
      case 'preAggregate':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Pre-Aggregate Columns</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McMultiSelect
                    label="Pre-Aggregator Columns"
                    name="preAggregatorColumns"
                    value={step.preAggregatorColumns}
                    optionselected={(e) => handleMultiSelectChange(e, index, 'preAggregatorColumns')}
                    placeholder="Select pre-aggregator columns"
                    required
                    invalid={!!errors.steps[index].preAggregatorColumns}
                    invalidmessage={errors.steps[index].preAggregatorColumns}
                  >
                    {preAggregatorOptions.map((option) => (
                      <McOption key={option.value} value={option.value}>
                        {option.label}
                      </McOption>
                    ))}
                  </McMultiSelect>
                </div>
              </div>
            ))}
          </div>
        );
      case 'join':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Join Columns</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McMultiSelect
                    label="Join Columns"
                    name="joinColumns"
                    value={step.joinColumns}
                    optionselected={(e) => handleMultiSelectChange(e, index, 'joinColumns')}
                    placeholder="Select join columns"
                    required
                    invalid={!!errors.steps[index].joinColumns}
                    invalidmessage={errors.steps[index].joinColumns}
                  >
                    {joinColumnsOptions.map((option) => (
                      <McOption key={option.value} value={option.value}>
                        {option.label}
                      </McOption>
                    ))}
                  </McMultiSelect>
                </div>
              </div>
            ))}
          </div>
        );
      case 'allocation':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Allocation Columns</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McMultiSelect
                    label="Allocation Columns"
                    name="allocationColumns"
                    value={step.allocationColumns}
                    optionselected={(e) => handleMultiSelectChange(e, index, 'allocationColumns')}
                    placeholder="Select allocation columns"
                    required
                    invalid={!!errors.steps[index].allocationColumns}
                    invalidmessage={errors.steps[index].allocationColumns}
                  >
                    {allocationColumnsOptions.map((option) => (
                      <McOption key={option.value} value={option.value}>
                        {option.label}
                      </McOption>
                    ))}
                  </McMultiSelect>
                </div>
              </div>
            ))}
          </div>
        );
      case 'driver':
        return (
          <div className={styles.tabContent}>
            <h3 className={styles.sectionTitle}>Driver Information</h3>
            {steps.map((step, index) => (
              <div key={index} className={styles.stepCase}>
                <h4 style={{ color: '#35B0CB' }}>STEP {index + 1}</h4>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Driver Table ID"
                    name="driverTableID"
                    value={step.driverTableID}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter driver table ID"
                    required
                    invalid={!!errors.steps[index].driverTableID}
                    invalidmessage={errors.steps[index].driverTableID}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McInput
                    label="Driver Weight Column"
                    name="driverWeightColumn"
                    value={step.driverWeightColumn}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Enter driver weight column"
                    required
                    invalid={!!errors.steps[index].driverWeightColumn}
                    invalidmessage={errors.steps[index].driverWeightColumn}
                  />
                </div>
                <div className={styles.inputGroup}>
                  <McMultiSelect
                    label="Driver Filters"
                    name="driverFilters"
                    value={step.driverFilters}
                    optionselected={(e) => handleMultiSelectChange(e, index, 'driverFilters')}
                    placeholder="Select driver filters"
                    required
                    invalid={!!errors.steps[index].driverFilters}
                    invalidmessage={errors.steps[index].driverFilters}
                  >
                    {driverFilterOptions.map((option) => (
                      <McOption key={option.value} value={option.value}>
                        {option.label}
                      </McOption>
                    ))}
                  </McMultiSelect>
                </div>
                <div className={styles.inputGroup}>
                  <McSelect
                    label="Driver Operator"
                    name="driverOperator"
                    value={step.driverOperator}
                    input={(e) => handleInputChange(e, index)}
                    placeholder="Select an operator"
                    required
                    invalid={!!errors.steps[index].driverOperator}
                    invalidmessage={errors.steps[index].driverOperator}
                  >
                    {operatorOptions.map((option) => (
                      <McOption key={option.value} value={option.value}>
                        {option.label}
                      </McOption>
                    ))}
                  </McSelect>
                </div>
              </div>
            ))}
          </div>
        );
      default:
        return <div className={styles.tabContent}>No Tab Selected</div>;
    }
  };

  return (
    <ErrorBoundary>
      <div className={styles.pageWrapper}>
        {isLoading && (
          <div className={styles.loader}>Loading...</div>
        )}
        <div className={styles.container}>
          <div className={styles.card}>
            <div className={styles.buttonContainer}>
              <McButton
                label="Back"
                appearance="neutral"
                click={handleCancel}
                className={styles.actionButton}
              />
              <McButton
                label="Save"
                appearance="primary"
                click={handleSave}
                className={styles.actionButton}
                loading={isLoading}
                disabled={isLoading}
              />
            </div>

            <div className={styles.tabs}>
              <button
                className={`${styles.tabButton} ${activeTab === 'ruleInfo' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('ruleInfo')}
              >
                Rule Info
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'step' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('step')}
              >
                Step
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'source' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('source')}
              >
                Source
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'preAggregate' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('preAggregate')}
              >
                Pre-Aggregate
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'join' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('join')}
              >
                Join
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'allocation' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('allocation')}
              >
                Allocation
              </button>
              <button
                className={`${styles.tabButton} ${activeTab === 'driver' ? styles.activeTab : ''}`}
                onClick={() => setActiveTab('driver')}
              >
                Driver
              </button>
            </div>

            {renderTabContent()}
          </div>
        </div>
      </div>
    </ErrorBoundary>
  );
};

export default CreateRules;
-- ICA_Bank_Decline_Threshold_Block
DROP TABLE team_kingkong.offus_ICA_Bank_Decline_Threshold_Block_breaches;

-- CREATE TABLE team_kingkong.offus_ICA_Bank_Decline_Threshold_Block_breaches AS
INSERT INTO team_kingkong.offus_ICA_Bank_Decline_Threshold_Block_breaches
with offus_txn as
(SELECT globalcardindex, transactionid, txn_amount, txn_date, paytmmerchantid, txn_timestamp, paymethod
, case when edc_mid is not null then 'EDC' else 'QR' end as mid_type
, isindian, txn_status
FROM
    (SELECT DISTINCT pg_mid from cdo.total_offline_merchant_base_snapshot_v3) f
INNER join
    (select distinct transactionid
    , cast(eventamount as double)/100 as txn_amount
    , paytmmerchantid
    , globalcardindex
    , DATE(dl_last_updated) AS txn_date
    , CAST(velocitytimestamp AS DOUBLE) AS txn_timestamp
    , paymethod
    , isindian
    from cdp_risk_transform.maquette_flattened_offus_snapshot_v3
    where dl_last_updated BETWEEN DATE(DATE'2025-01-01' - INTERVAL '1' DAY) AND DATE'2025-01-31'
    and paymethod in ('CREDIT_CARD','DEBIT_CARD')
    AND actionrecommended <> 'BLOCK' AND responsestatus = 'SUCCESS') a
on a.paytmmerchantid = f.pg_mid
LEFT JOIN
    (SELECT DISTINCT mid AS edc_mid FROM paytmpgdb.entity_edc_info_snapshot_v3
    WHERE terminal_status = 'ACTIVE' AND dl_last_updated >= DATE '2010-01-01') b
ON a.paytmmerchantid = b.edc_mid
INNER JOIN
    (select distinct txn_id as pg_txn_id, txn_status
    from dwh.pg_olap
    where ingest_date BETWEEN DATE'2025-01-01' AND DATE(DATE'2025-01-31' + INTERVAL '1' DAY)
    and txn_started_at BETWEEN  DATE'2025-01-01' AND DATE(DATE'2025-01-31' + INTERVAL '1' DAY)) d
on a.transactionid = d.pg_txn_id)

SELECT *, CASE
  WHEN txn1_hr >= txn1_hr_threshold 
       AND txn6_hr >= txn6_hr_threshold AND txn_amount > 10000 
       AND txn24_hr >= txn24_hr_threshold
    THEN 'txn1_hr breach, txn6_hr + high txn_amount breach, txn24_hr breach'

  WHEN txn1_hr >= txn1_hr_threshold 
       AND txn6_hr >= txn6_hr_threshold AND txn_amount > 10000
    THEN 'txn1_hr breach, txn6_hr + high txn_amount breach'

  WHEN txn1_hr >= txn1_hr_threshold 
       AND txn24_hr >= txn24_hr_threshold
    THEN 'txn1_hr breach, txn24_hr breach'

  WHEN txn6_hr >= txn6_hr_threshold AND txn_amount > 10000 
       AND txn24_hr >= txn24_hr_threshold
    THEN 'txn6_hr + high txn_amount breach, txn24_hr breach'

  WHEN txn1_hr >= txn1_hr_threshold THEN 'txn1_hr breach'
  WHEN txn6_hr >= txn6_hr_threshold AND txn_amount > 10000 THEN 'txn6_hr + high txn_amount breach'
  WHEN txn24_hr >= txn24_hr_threshold THEN 'txn24_hr breach'
  ELSE NULL END AS breach_reason FROM
    (SELECT a.globalcardindex, A.transactionid, A.txn_amount, A.txn_date, A.paytmmerchantid, A.txn_timestamp
    , A.mid_type, A.paymethod
    , COUNT(IF((A.txn_timestamp - B.txn_timestamp) BETWEEN 0 AND 3600000, B.transactionid, NULL)) AS txn1_hr
    , 3 txn1_hr_threshold

    , COUNT(IF((A.txn_timestamp - B.txn_timestamp) BETWEEN 0 AND 21600000, B.transactionid, NULL)) AS txn6_hr
    , 3 txn6_hr_threshold

    , COUNT(IF((A.txn_timestamp - B.txn_timestamp) BETWEEN 0 AND 122400000, B.transactionid, NULL)) AS txn24_hr
    , 5 AS txn24_hr_threshold

    , 'ICA_Bank_Decline_Threshold_Block' AS rule_name
    FROM
        (SELECT * FROM offus_txn
        WHERE txn_date BETWEEN DATE'2025-01-01' AND  DATE'2025-01-31'
        AND isindian = 'false' and txn_status = 'SUCCESS')A
    INNER JOIN
        (SELECT * FROM offus_txn
        WHERE txn_status = 'CLOSED')B
    ON A.globalcardindex = b.globalcardindex AND A.paytmmerchantid = B.paytmmerchantid
    AND A.transactionid <> B.transactionid AND A.txn_timestamp > B.txn_timestamp
    GROUP BY 1,2,3,4,5,6,7,8)
WHERE (txn1_hr >= txn1_hr_threshold) OR 
(txn6_hr >= txn6_hr_threshold AND txn_amount > 10000) OR
(txn24_hr >= txn24_hr_threshold) 
;
-- ICA_PerCard_PerMID_TXN_Limit
DROP TABLE team_kingkong.offus_ICA_PerCard_PerMID_TXN_Limit_breaches;
 
-- CREATE TABLE team_kingkong.offus_ICA_PerCard_PerMID_TXN_Limit_breaches AS
INSERT INTO team_kingkong.offus_ICA_PerCard_PerMID_TXN_Limit_breaches
with offus_txn as
(SELECT globalcardindex, transactionid, txn_amount, txn_date, paytmmerchantid, txn_timestamp, paymethod
, case when edc_mid is not null then 'EDC' else 'QR' end as mid_type
, mcc, isindian, isedcrequest
, CASE WHEN mcc IN (5411, 5812, 9399, 8211, 7999, 7011, 5813, 4511, 8071, 8062) THEN 1 ELSE 0 END AS non_risky_high_mcc
, CASE WHEN mcc IN (5962, 7273, 7995, 5122, 6051, 6012, 5993, 5968, 5966, 5912, 6211, 5816, 4816, 5967) THEN 1 ELSE 0 END AS risky_mcc
FROM
    (SELECT DISTINCT pg_mid from cdo.total_offline_merchant_base_snapshot_v3) f
INNER join
    (select distinct transactionid
    , cast(eventamount as double)/100 as txn_amount
    , paytmmerchantid
    , globalcardindex
    , DATE(dl_last_updated) AS txn_date
    , CAST(velocitytimestamp AS DOUBLE) AS txn_timestamp
    , paymethod
    , isindian
    , isedcrequest
    from cdp_risk_transform.maquette_flattened_offus_snapshot_v3
    where dl_last_updated BETWEEN DATE(DATE'2025-01-01' - INTERVAL '30' DAY) AND DATE'2025-01-31'
    and paymethod in ('CREDIT_CARD','DEBIT_CARD')
    AND actionrecommended <> 'BLOCK' AND responsestatus = 'SUCCESS') a
on a.paytmmerchantid = f.pg_mid
LEFT JOIN
    (SELECT DISTINCT mid AS edc_mid FROM paytmpgdb.entity_edc_info_snapshot_v3
    WHERE terminal_status = 'ACTIVE' AND dl_last_updated >= DATE '2010-01-01') b
ON a.paytmmerchantid = b.edc_mid
INNER JOIN
    (select distinct txn_id as pg_txn_id, mcc
    from dwh.pg_olap
    where ingest_date BETWEEN DATE(DATE'2025-01-01' - INTERVAL '30' DAY) AND DATE'2025-01-31'
    and txn_started_at BETWEEN DATE(DATE'2025-01-01' - INTERVAL '30' DAY) AND DATE'2025-01-31'
    and txn_status = 'SUCCESS'
    AND mcc IS NOT NULL) d
on a.transactionid = d.pg_txn_id)
 
SELECT *, CASE
  WHEN txn1_min >= txn1_min_threshold 
       AND txn1_day >= txn1_day_threshold 
       AND txn7_day >= txn7_day_threshold 
       AND txn30_day >= txn30_day_threshold 
    THEN 'txn1_min, txn1_day, txn7_day, txn30_day breach'

  WHEN txn1_min >= txn1_min_threshold 
       AND txn1_day >= txn1_day_threshold 
       AND txn7_day >= txn7_day_threshold 
    THEN 'txn1_min, txn1_day, txn7_day breach'

  WHEN txn1_min >= txn1_min_threshold 
       AND txn1_day >= txn1_day_threshold 
       AND txn30_day >= txn30_day_threshold 
    THEN 'txn1_min, txn1_day, txn30_day breach'

  WHEN txn1_min >= txn1_min_threshold 
       AND txn7_day >= txn7_day_threshold 
       AND txn30_day >= txn30_day_threshold 
    THEN 'txn1_min, txn7_day, txn30_day breach'

  WHEN txn1_day >= txn1_day_threshold 
       AND txn7_day >= txn7_day_threshold 
       AND txn30_day >= txn30_day_threshold 
    THEN 'txn1_day, txn7_day, txn30_day breach'

  WHEN txn1_min >= txn1_min_threshold AND txn1_day >= txn1_day_threshold THEN 'txn1_min, txn1_day breach'
  WHEN txn1_min >= txn1_min_threshold AND txn7_day >= txn7_day_threshold THEN 'txn1_min, txn7_day breach'
  WHEN txn1_min >= txn1_min_threshold AND txn30_day >= txn30_day_threshold THEN 'txn1_min, txn30_day breach'
  WHEN txn1_day >= txn1_day_threshold AND txn7_day >= txn7_day_threshold THEN 'txn1_day, txn7_day breach'
  WHEN txn1_day >= txn1_day_threshold AND txn30_day >= txn30_day_threshold THEN 'txn1_day, txn30_day breach'
  WHEN txn7_day >= txn7_day_threshold AND txn30_day >= txn30_day_threshold THEN 'txn7_day, txn30_day breach'

  WHEN txn1_min >= txn1_min_threshold THEN 'txn1_min breach'
  WHEN txn1_day >= txn1_day_threshold THEN 'txn1_day breach'
  WHEN txn7_day >= txn7_day_threshold THEN 'txn7_day breach'
  WHEN txn30_day >= txn30_day_threshold THEN 'txn30_day breach'

  ELSE NULL END AS breach_reason FROM
    (SELECT a.globalcardindex, A.transactionid, A.txn_amount, A.txn_date, A.paytmmerchantid, A.txn_timestamp
    , A.mid_type, A.paymethod, A.mcc, A.non_risky_high_mcc, A.risky_mcc
    , COUNT(IF((A.txn_timestamp - B.txn_timestamp) BETWEEN 0 AND 60000, B.transactionid, NULL)) AS txn1_min
    , CASE WHEN A.risky_mcc > 0 THEN 1
    WHEN A.non_risky_high_mcc > 0 THEN 1
    WHEN A.risky_mcc = 0 AND A.non_risky_high_mcc = 0 THEN 1
    END AS txn1_min_threshold
    , COUNT(IF((A.txn_timestamp - B.txn_timestamp) BETWEEN 0 AND 86400000, B.transactionid, NULL)) AS txn1_day
    , CASE WHEN A.risky_mcc > 0 THEN 3
    WHEN A.non_risky_high_mcc > 0 THEN 5
    WHEN A.risky_mcc = 0 AND A.non_risky_high_mcc = 0 THEN 3
    END AS txn1_day_threshold
    , COUNT(IF((A.txn_timestamp - B.txn_timestamp) BETWEEN 0 AND 604800000, B.transactionid, NULL)) AS txn7_day
    , CASE WHEN A.risky_mcc > 0 THEN 5
    WHEN A.non_risky_high_mcc > 0 THEN 10
    WHEN A.risky_mcc = 0 AND A.non_risky_high_mcc = 0 THEN 5
    END AS txn7_day_threshold
    , COUNT(IF((A.txn_timestamp - B.txn_timestamp) BETWEEN 0 AND 2592000000, B.transactionid, NULL)) AS txn30_day
    , CASE WHEN A.risky_mcc > 0 THEN 15
    WHEN A.non_risky_high_mcc > 0 THEN 25
    WHEN A.risky_mcc = 0 AND A.non_risky_high_mcc = 0 THEN 15 
    END AS txn30_day_threshold
 
    , 'ICA_PerCard_PerMID_TXN_Limit' AS rule_name
     FROM
        (SELECT * FROM offus_txn
        WHERE txn_date BETWEEN DATE'2025-01-01' AND  DATE'2025-01-31'
        AND isindian = 'false' AND isedcrequest = 'true')A
    INNER JOIN
        (SELECT * FROM offus_txn)B
    ON A.globalcardindex = b.globalcardindex AND A.paytmmerchantid = B.paytmmerchantid
    AND A.transactionid <> B.transactionid
    AND A.txn_timestamp > B.txn_timestamp
    GROUP BY 1,2,3,4,5,6,7,8,9,10,11)
WHERE (txn1_min >= txn1_min_threshold) OR 
(txn1_day >= txn1_day_threshold) OR
(txn7_day >= txn7_day_threshold) OR
(txn30_day >= txn30_day_threshold);
-- Merchant_PerTxnLimit_Check
-- CREATE TABLE team_kingkong.offus_Merchant_PerTxnLimit_Check_breaches AS
INSERT INTO team_kingkong.offus_Merchant_PerTxnLimit_Check_breaches
SELECT transactionid, txn_amount, txn_date, paytmmerchantid, txn_timestamp, paymethod
, case when edc_mid is not null then 'EDC' else 'QR' end as mid_type
, C.per_txn_limit
, C.limit_date
, 'Per txn amt threshold breach' AS breach_reason
FROM
    (SELECT DISTINCT pg_mid from cdo.total_offline_merchant_base_snapshot_v3) f
INNER join
    (select distinct transactionid
    , cast(IF(eventamount <> '', eventamount, '0') as double)/100 as txn_amount
    , paytmmerchantid
    , globalcardindex
    , DATE(dl_last_updated) AS txn_date
    , CAST(velocitytimestamp AS DOUBLE) AS txn_timestamp
    , paymethod
    from cdp_risk_transform.maquette_flattened_offus_snapshot_v3
    where dl_last_updated BETWEEN DATE(DATE'2025-07-01' - INTERVAL '1' DAY) AND DATE'2025-08-18'
    and paymethod in ('UPI')
    AND actionrecommended <> 'BLOCK' AND responsestatus = 'SUCCESS') a
on a.paytmmerchantid = f.pg_mid
LEFT JOIN
    (SELECT DISTINCT mid AS edc_mid FROM paytmpgdb.entity_edc_info_snapshot_v3
    WHERE terminal_status = 'ACTIVE' AND dl_last_updated >= DATE '2010-01-01') b
ON a.paytmmerchantid = b.edc_mid
INNER JOIN
    (SELECT content as mid, CAST(comment AS DOUBLE) as per_txn_limit, "timestamp" as limit_date
    FROM team_kingkong.merchant_limit_list)C
ON a.paytmmerchantid = C.mid 
AND a.txn_date > DATE(FROM_UNIXTIME(IF(limit_date <> '', CAST(limit_date AS DOUBLE), to_unixtime(CAST(CURRENT_DATE - INTERVAL '2' DAY AS timestamp))) / 1000))
WHERE a.txn_amount > C.per_txn_limit;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class CombatActionUI : MonoBehaviour
{
    [SerializeField] private GameObject visualContainer;
    [SerializeField] private Button[] combatActionButtons;

    void OnEnable ()
    {
        TurnManager.Instance.OnBeginTurn += OnBeginTurn;
        TurnManager.Instance.OnEndTurn += OnEndTurn;
    }

    void OnDisable ()
    {
        TurnManager.Instance.OnBeginTurn -= OnBeginTurn;
        TurnManager.Instance.OnEndTurn -= OnEndTurn;
    }

    void OnBeginTurn (Character character)
    {
        if(!character.IsPlayer)
            return;

        visualContainer.SetActive(true);

        for(int i = 0; i < combatActionButtons.Length; i++)
        {
            if(i < character.CombatActions.Count)
            {
                combatActionButtons[i].gameObject.SetActive(true);
                CombatAction ca = character.CombatActions[i];

                combatActionButtons[i].GetComponentInChildren<TextMeshProUGUI>().text = ca.DisplayName;
                combatActionButtons[i].onClick.RemoveAllListeners();
                combatActionButtons[i].onClick.AddListener(() => OnClickCombatAction(ca));
            }
            else
            {
                combatActionButtons[i].gameObject.SetActive(false);
            }
        }
    }

    void OnEndTurn (Character character)
    {
        visualContainer.SetActive(false);
    }

    public void OnClickCombatAction (CombatAction combatAction)
    {
        TurnManager.Instance.CurrentCharacter.CastCombatAction(combatAction);
    }
}
{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":star: Xero Boost Days! :star:"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Canberra! Please see below for what's on this week! "
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-21: Wednesday, 21st July",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "\n:Lunch: *Lunch*: Provided in our suite from *12pm*."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Stay tuned to this channel for more details, check out the <https://calendar.google.com/calendar/u/0?cid=Y19jYzU3YWJkZTE4ZTE0YzVlYTYxMGU4OThjZjRhYWQ0MTNhYmIzMDBjZjBkMzVlNDg0M2M5NDQ4NDk3NDAyYjkyQGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20|*Canberra Social Calendar*>, and get ready to Boost your workdays!\n\nLove,\nWX Team :party-wx:"
			}
		}
	]
}
{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":star: Xero Boost Days! :star:"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Canberra! Please see below for what's on this week! "
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-14: Wednesday, 14th July",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "\n:Lunch: *Lunch*: Provided in our suite from *12pm*."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Stay tuned to this channel for more details, check out the <https://calendar.google.com/calendar/u/0?cid=Y19jYzU3YWJkZTE4ZTE0YzVlYTYxMGU4OThjZjRhYWQ0MTNhYmIzMDBjZjBkMzVlNDg0M2M5NDQ4NDk3NDAyYjkyQGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20|*Canberra Social Calendar*>, and get ready to Boost your workdays!\n\nLove,\nWX Team :party-wx:"
			}
		}
	]
}
{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":star: Xero Boost Days! :star:"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Canberra! Please see below for what's on this week! "
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-7: Wednesday, 7th July",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "\n:Lunch: *Lunch*: Provided in our suite from *12pm*."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Stay tuned to this channel for more details, check out the <https://calendar.google.com/calendar/u/0?cid=Y19jYzU3YWJkZTE4ZTE0YzVlYTYxMGU4OThjZjRhYWQ0MTNhYmIzMDBjZjBkMzVlNDg0M2M5NDQ4NDk3NDAyYjkyQGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20|*Canberra Social Calendar*>, and get ready to Boost your workdays!\n\nLove,\nWX Team :party-wx:"
			}
		}
	]
}
{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":star: Xero Boost Days! :star:"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Canberra! Please see below for what's on this week! "
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-30: Wednesday, 30th June",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "\n:Lunch: *Lunch*: Provided in our suite from *12pm*."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Stay tuned to this channel for more details, check out the <https://calendar.google.com/calendar/u/0?cid=Y19jYzU3YWJkZTE4ZTE0YzVlYTYxMGU4OThjZjRhYWQ0MTNhYmIzMDBjZjBkMzVlNDg0M2M5NDQ4NDk3NDAyYjkyQGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20|*Canberra Social Calendar*>, and get ready to Boost your workdays!\n\nLove,\nWX Team :party-wx:"
			}
		}
	]
}
{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":star: Xero Boost Days! :star:"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Canberra! Please see below for what's on this week! "
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-23: Wednesday, 23rd June",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "\n:Lunch: *Lunch*: Provided in our suite from *12pm*."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Stay tuned to this channel for more details, check out the <https://calendar.google.com/calendar/u/0?cid=Y19jYzU3YWJkZTE4ZTE0YzVlYTYxMGU4OThjZjRhYWQ0MTNhYmIzMDBjZjBkMzVlNDg0M2M5NDQ4NDk3NDAyYjkyQGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20|*Canberra Social Calendar*>, and get ready to Boost your workdays!\n\nLove,\nWX Team :party-wx:"
			}
		}
	]
}
{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":star: Xero Boost Days! :star:"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Canberra! Please see below for what's on this week! "
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-16: Wednesday, 16th June",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "\n:Lunch: *Lunch*: Provided in our suite from *12pm*."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Stay tuned to this channel for more details, check out the <https://calendar.google.com/calendar/u/0?cid=Y19jYzU3YWJkZTE4ZTE0YzVlYTYxMGU4OThjZjRhYWQ0MTNhYmIzMDBjZjBkMzVlNDg0M2M5NDQ4NDk3NDAyYjkyQGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20|*Canberra Social Calendar*>, and get ready to Boost your workdays!\n\nLove,\nWX Team :party-wx:"
			}
		}
	]
}
{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":star: Xero Boost Days! :star:"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Canberra! Please see below for what's on this week! "
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-9: Wednesday, 9th June",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "\n:Lunch: *Lunch*: Provided in our suite from *12pm*."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Stay tuned to this channel for more details, check out the <https://calendar.google.com/calendar/u/0?cid=Y19jYzU3YWJkZTE4ZTE0YzVlYTYxMGU4OThjZjRhYWQ0MTNhYmIzMDBjZjBkMzVlNDg0M2M5NDQ4NDk3NDAyYjkyQGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20|*Canberra Social Calendar*>, and get ready to Boost your workdays!\n\nLove,\nWX Team :party-wx:"
			}
		}
	]
}
{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":star: Xero Boost Days! :star:"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Canberra! Please see below for what's on this week! "
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-2: Wednesday, 2nd June",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "\n:Lunch: *Lunch*: Provided in our suite from *12pm*."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Stay tuned to this channel for more details, check out the <https://calendar.google.com/calendar/u/0?cid=Y19jYzU3YWJkZTE4ZTE0YzVlYTYxMGU4OThjZjRhYWQ0MTNhYmIzMDBjZjBkMzVlNDg0M2M5NDQ4NDk3NDAyYjkyQGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20|*Canberra Social Calendar*>, and get ready to Boost your workdays!\n\nLove,\nWX Team :party-wx:"
			}
		}
	]
}
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
filebrowser -r /path/to/your/files
sudo add-apt-repository ppa:christian-boxdoerfer/fsearch-stable
sudo apt update
        
{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":aboriginal_flag: Xero Boost Days - What's On :aboriginal_flag:"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Good Morning Warrane, and welcome to Reconciliation Week!\n\n *Reconciliation Week (27 May – 3 June)* is a time for all Australians to learn about our shared histories, cultures, and achievements, and to explore how we can contribute to reconciliation in Australia. \n\n For Aboriginal and Torres Strait Islander peoples, it’s a powerful reminder of ongoing resilience, the importance of truth-telling, and the work still needed to achieve genuine equity and justice."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-28: Wednesday, 28th May",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "\n:coffee: *Café Partnership*: Enjoy free coffee and café-style beverages from our Cafe partner *Naked Duck*.\n:breakfast: *Reconciliation breakfast*: from *9am* in the kitchen.\n\n :microphone: We will also have a video of our very own *Tony Davison* talking on behalf of the #reconciliation-au ERG around Xero's Reconciliation plan and what this week means! \n\n :slack: In the thread we will also have a special background for you to use on Google Meet if you would like to participate."
			}
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-29: Thursday, 29th May",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": ":coffee: *Café Partnership*: Enjoy free coffee and café-style beverages from our Cafe partner *Naked Duck*.\n:lunch: *Lunch* provided by Naked Duck from *9pm* in the kitchen!."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Stay tuned to this channel for more details, check out the <https://calendar.google.com/calendar/u/0?cid=Y185aW90ZWV0cXBiMGZwMnJ0YmtrOXM2cGFiZ0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t|*Sydney Social Calendar*>, and get ready to Boost your workdays!\n\nLove,\nWX Team :party-wx:"
			}
		}
	]
}
star

Wed May 28 2025 07:18:37 GMT+0000 (Coordinated Universal Time) https://www.firekirin.xyz:8888/Store.aspx

@cholillo18

star

Wed May 28 2025 07:17:57 GMT+0000 (Coordinated Universal Time) https://www.firekirin.xyz:8888/Store.aspx

@cholillo18

star

Wed May 28 2025 06:48:09 GMT+0000 (Coordinated Universal Time)

@Saravana_Kumar #python

star

Wed May 28 2025 04:23:35 GMT+0000 (Coordinated Universal Time)

@tara.hamedani

star

Wed May 28 2025 01:24:03 GMT+0000 (Coordinated Universal Time)

@futuremotiondev #css #font #font-family #font-stack

star

Tue May 27 2025 12:28:47 GMT+0000 (Coordinated Universal Time)

@Sebhart #css #link

star

Tue May 27 2025 12:27:40 GMT+0000 (Coordinated Universal Time)

@Sebhart #css #list #order #navigation

star

Tue May 27 2025 12:26:45 GMT+0000 (Coordinated Universal Time)

@Sebhart #css #image #optimization

star

Tue May 27 2025 12:25:53 GMT+0000 (Coordinated Universal Time)

@Sebhart #css #ui

star

Tue May 27 2025 12:24:59 GMT+0000 (Coordinated Universal Time)

@Sebhart #css #button #bug

star

Tue May 27 2025 12:22:43 GMT+0000 (Coordinated Universal Time)

@Sebhart #css #selector #phone

star

Tue May 27 2025 12:21:26 GMT+0000 (Coordinated Universal Time)

@Sebhart #css #a11y #media-query

star

Tue May 27 2025 12:20:32 GMT+0000 (Coordinated Universal Time)

@Sebhart #css #image

star

Tue May 27 2025 12:19:29 GMT+0000 (Coordinated Universal Time)

@Sebhart #css #shadow

star

Tue May 27 2025 06:40:17 GMT+0000 (Coordinated Universal Time) https://www.addustechnologies.com/blog/coinmarketcap-clone-script

@Seraphina

star

Tue May 27 2025 05:57:35 GMT+0000 (Coordinated Universal Time) https://kanhasoft.com/ios-app-development.html

@kanhasoft #iosapp development #iosapp development company

star

Tue May 27 2025 05:56:48 GMT+0000 (Coordinated Universal Time) https://kanhasoft.com/product-development.html

@kanhasoft #productdevelopment #customproduct development

star

Tue May 27 2025 05:56:13 GMT+0000 (Coordinated Universal Time) https://kanhasoft.com/web-scraping-services.html

@kanhasoft #webdata scraping #datascraping services

star

Tue May 27 2025 05:55:21 GMT+0000 (Coordinated Universal Time) https://kanhasoft.com/ai-ml-development-company.html

@kanhasoft #aidevelopment #mldevelopment

star

Tue May 27 2025 05:54:39 GMT+0000 (Coordinated Universal Time) https://kanhasoft.com/cloud-saas-based-application-development.html

@kanhasoft #saasapplication #saasapplication development

star

Tue May 27 2025 05:53:35 GMT+0000 (Coordinated Universal Time) https://kanhasoft.com/erp-software-development.html

@kanhasoft #customerp software #erpsoftware development #softwaredevelopment

star

Tue May 27 2025 05:52:43 GMT+0000 (Coordinated Universal Time) https://kanhasoft.com/crm-software-development.html

@kanhasoft #customcrm software #crmsoftware development

star

Tue May 27 2025 05:51:50 GMT+0000 (Coordinated Universal Time) https://kanhasoft.com/custom-software-development.html

@kanhasoft #customsoftware development #softwaredevelopment company

star

Tue May 27 2025 05:51:09 GMT+0000 (Coordinated Universal Time) https://kanhasoft.com/custom-amazon-seller-tools.html

@kanhasoft #amazonsellers tools #customamazon sellers tools #sellerstools

star

Tue May 27 2025 05:48:53 GMT+0000 (Coordinated Universal Time) https://kanhasoft.com/mobile-app-development.html

@kanhasoft #mobileapplication development #mobileapplication #appdevelopment #ai-poweredapplications

star

Tue May 27 2025 05:45:04 GMT+0000 (Coordinated Universal Time) https://kanhasoft.com/web-app-development.html

@kanhasoft

star

Tue May 27 2025 00:40:59 GMT+0000 (Coordinated Universal Time)

@Y@sir

star

Mon May 26 2025 20:36:58 GMT+0000 (Coordinated Universal Time) https://www.linuxuntu.com/install-safari-linux/

@v1ral_ITS

star

Mon May 26 2025 20:32:51 GMT+0000 (Coordinated Universal Time) https://www.linuxuntu.com/install-safari-linux/

@v1ral_ITS

star

Mon May 26 2025 18:39:09 GMT+0000 (Coordinated Universal Time)

@krisha_joshi

star

Mon May 26 2025 17:43:19 GMT+0000 (Coordinated Universal Time) https://www.reddit.com/r/AskTechnology/comments/11la29u/whatsapp_desktop_taking_up_42_gb_of_disc_space/?tl=de

@2late #whatsapp

star

Mon May 26 2025 15:39:06 GMT+0000 (Coordinated Universal Time)

@krisha_joshi

star

Mon May 26 2025 10:31:13 GMT+0000 (Coordinated Universal Time) https://www.coinsclone.com/otc-trading-platform-development/

@CharleenStewar #otctradingplatform #otc trading platform development

star

Mon May 26 2025 10:23:30 GMT+0000 (Coordinated Universal Time) https://maticz.com/ico-development

@Abiraminounq

star

Mon May 26 2025 10:11:49 GMT+0000 (Coordinated Universal Time)

@shubhangi.b

star

Mon May 26 2025 10:11:17 GMT+0000 (Coordinated Universal Time)

@shubhangi.b

star

Mon May 26 2025 10:10:06 GMT+0000 (Coordinated Universal Time)

@shubhangi.b

star

Mon May 26 2025 08:49:21 GMT+0000 (Coordinated Universal Time)

@iliavial #c#

star

Mon May 26 2025 04:01:20 GMT+0000 (Coordinated Universal Time)

@FOHWellington

star

Mon May 26 2025 04:00:42 GMT+0000 (Coordinated Universal Time)

@FOHWellington

star

Mon May 26 2025 04:00:12 GMT+0000 (Coordinated Universal Time)

@FOHWellington

star

Mon May 26 2025 03:59:35 GMT+0000 (Coordinated Universal Time)

@FOHWellington

star

Mon May 26 2025 03:58:59 GMT+0000 (Coordinated Universal Time)

@FOHWellington

star

Mon May 26 2025 03:56:55 GMT+0000 (Coordinated Universal Time)

@FOHWellington

star

Mon May 26 2025 03:55:30 GMT+0000 (Coordinated Universal Time)

@FOHWellington

star

Mon May 26 2025 03:54:36 GMT+0000 (Coordinated Universal Time)

@FOHWellington

star

Mon May 26 2025 01:46:04 GMT+0000 (Coordinated Universal Time) https://filebrowser.org/installation

@v1ral_ITS

star

Mon May 26 2025 01:41:17 GMT+0000 (Coordinated Universal Time) https://launchpad.net/~christian-boxdoerfer/+archive/ubuntu/fsearch-stable

@v1ral_ITS

star

Mon May 26 2025 00:39:02 GMT+0000 (Coordinated Universal Time)

@FOHWellington

star

Sun May 25 2025 08:07:11 GMT+0000 (Coordinated Universal Time) https://github.com/theapache64/SoundCloud-Downloader

@Evil_Smile

Save snippets that work with our extensions

Available in the Chrome Web Store Get Firefox Add-on Get VS Code extension