Snippets Collections
-- MONTHLY BREACHES & CASHBACK & FRAUD %GE
SELECT substr(cast(txn_date as varchar(30)), 1, 7) as year_month
, COUNT(transactionid) AS breach_cnt
, SUM(txn_amount) as breach_amt
, COUNT(DISTINCT small_vpa) as breach_card_cnt
, SUM(CAST(cashback_amount AS DOUBLE)) AS cashback_amount
, SUM(CAST(fraud_amount AS DOUBLE)) AS fraud_amount
FROM
    (SELECT DISTINCT A.txn_date, A.transactionid, A.txn_amount, A.small_vpa
    , B.amount AS cashback_amount
    , C.fraud_amount as fraud_amount
    FROM
        (SELECT * FROM team_kingkong.offus_CCUPI_vpa_mid_daily_limit_breaches)A 
    LEFT JOIN
        (select * from
            (select a.*, ROW_NUMBER() OVER (PARTITION BY txn_id ORDER BY cb_level desc) as row_num from
                (select distinct txn_id, amount, cb_level
                from charge_team_data.cb_data_analytics_snapshot_v3
                where dl_last_updated >= date '2000-01-01')a)a
        where row_num = 1)B
    on B.txn_id = A.transactionid
    LEFT JOIN
        (select * from
            (select distinct pg_txn_id as txn_id, txn_amount as fraud_amount,
            substr(txn_date,1,10) as txn_date, mid,
            min(source_reporting_date) as reporting_date
            from cdp_risk_transform.fraud_master_snapshot_v3
            where source_table <> 'freshdesk.cst_case_snapshot'
            and txn_status = 'SUCCESS'
            and dl_last_updated >= date '2025-01-01'
            group by 1,2,3,4) i
        left join
            (SELECT distinct acq_id, CAST(refund_amount AS DOUBLE) / 100 AS fraud_refund
            FROM pgaws_datalake_prod.acq_refund_snapshot_v3
            WHERE dl_last_updated >= date'2025-01-01' AND refund_status = 'SUCCESS') j
        ON i.txn_id = j.acq_id)C
    ON A.transactionid = C.txn_id)
GROUP BY 1 ORDER BY 1;
-- CREATE TABLE team_kingkong.onus_CCBP_sucTxn_user_1d_7d_30d_breaches AS 
INSERT INTO team_kingkong.onus_CCBP_sucTxn_user_1d_7d_30d_breaches
with onus_txn_base as
    (SELECT DISTINCT A.*, case when m1.mid is not null then category else 'Others' end as business_category FROM 
        (select userid, transactionid,
        cast(eventAmount as double) / 100 as amt,
        dateinserted,
        substr(cast(dateinserted as varchar(30)), 1, 7) as mnth,
        paymethod, paytmmerchantid, velocitytimestamp
        FROM cdp_risk_transform.maquette_flattened_onus_snapshot_v3
        WHERE DATE(dl_last_updated) BETWEEN DATE(DATE'2025-04-01' - INTERVAL '30' DAY) AND DATE'2025-04-30'
        AND SOURCE = 'PG'
        AND paytmmerchantid IN ('PTMCBP84799392178473','PTMVIS48435535949128','PTMCBP11428987150800')
        AND eventid IN (SELECT eventlinkid
        FROM risk_maquette_data_async.pplus_payment_result_prod_async_snapshot_v3
        WHERE dl_last_updated BETWEEN DATE(DATE'2025-04-01' - INTERVAL '30' DAY) AND DATE'2025-04-30')
        AND responsestatus IN ('SUCCESS') AND actionrecommended = 'PASS') a
    left join
        (select * from team_kingkong.voc_mid_categorization where mid != '') m1
    on a.paytmmerchantid = m1.mid)

SELECT * FROM 
    (SELECT A.*
    -- No.of successful txns per user per calendar day > 8 (consider only the CCBP transactions)
    , COUNT(IF(DATE(B.dateinserted) = DATE(A.dateinserted), B.transactionid, NULL)) AS txn_attempted_same_day
    , 8 AS txn_attempted_same_day_threshold
    -- No.of successful txns last 7 days > 15 (consider only the CCBP transactions)
    , COUNT(IF(DATE(B.dateinserted) BETWEEN DATE(DATE(A.dateinserted) - INTERVAL '7' DAY) AND DATE(A.dateinserted), B.transactionid, NULL)) AS txn_attempted_7_day
    , 15 AS txn_attempted_7_day_threshold
    -- No.of successful txns per calendar month > 25 (consider only the CCBP transactions)
    , COUNT(IF(DATE(B.dateinserted) BETWEEN date_trunc('month', DATE(A.dateinserted)) AND DATE(A.dateinserted), B.transactionid, NULL)) AS txn_attempted_cal_month
    , 25 AS txn_attempted_cal_month_threshold
    FROM
        (SELECT * FROM onus_txn_base
        WHERE DATE(dateinserted) BETWEEN DATE'2025-04-01' AND DATE'2025-04-30')A
    INNER JOIN
        (SELECT * FROM onus_txn_base)B
    ON A.userid = B.userid AND A.transactionid <> B.transactionid AND B.velocitytimestamp < A.velocitytimestamp
    AND DATE(B.dateinserted) BETWEEN DATE(A.dateinserted - INTERVAL '30' DAY) AND DATE(A.dateinserted)
    GROUP BY 1,2,3,4,5,6,7,8,9)
WHERE (txn_attempted_same_day >= txn_attempted_same_day_threshold) OR (txn_attempted_7_day >= txn_attempted_7_day_threshold) OR (txn_attempted_cal_month >= txn_attempted_cal_month_threshold)
;
-- DROP TABLE team_kingkong.onus_CCBP_attempt_Txn_user_1d_7d_30d_breaches;

-- CREATE TABLE team_kingkong.onus_CCBP_attempt_Txn_user_1d_7d_30d_breaches AS
INSERT INTO team_kingkong.onus_CCBP_attempt_Txn_user_1d_7d_30d_breaches
with onus_txn_base as
    (SELECT A.*, case when m1.mid is not null then category else 'Others' end as business_category FROM 
        (
        select distinct userid, transactionid,
        cast(eventAmount as double) / 100 as amt,
        dateinserted,
        substr(cast(dateinserted as varchar(30)), 1, 7) as mnth,
        paymethod, paytmmerchantid, responsestatus, actionrecommended, velocitytimestamp
        FROM cdp_risk_transform.maquette_flattened_onus_snapshot_v3
        WHERE DATE(dl_last_updated) BETWEEN DATE(DATE'2025-04-01' - INTERVAL '30' DAY) AND DATE'2025-04-30'
        AND SOURCE = 'PG'
        AND paytmmerchantid IN ('PTMCBP84799392178473','PTMVIS48435535949128','PTMCBP11428987150800')
        AND eventid IN (SELECT eventlinkid
        FROM risk_maquette_data_async.pplus_payment_result_prod_async_snapshot_v3
        WHERE dl_last_updated BETWEEN DATE(DATE'2025-04-01' - INTERVAL '30' DAY) AND DATE'2025-04-30')
        ) a
    left join
        (select * from team_kingkong.voc_mid_categorization where mid != '') m1
    on a.paytmmerchantid = m1.mid)

SELECT * FROM 
    (SELECT A.*
    -- No.of attempted txns per user per calendar day  > 12 (consider only the CCBP transactions)
    , COUNT(IF(DATE(B.dateinserted) = DATE(A.dateinserted), B.transactionid, NULL)) AS txn_attempted_same_day
    , 12 AS txn_attempted_same_day_threshold
    -- No.of attempted txns last 7 days > 20 (consider only the CCBP transactions)
    , COUNT(IF(DATE(B.dateinserted) BETWEEN DATE(DATE(A.dateinserted) - INTERVAL '7' DAY) AND DATE(A.dateinserted), B.transactionid, NULL)) AS txn_attempted_7_day
    , 20 AS txn_attempted_7_day_threshold
    -- No.of attempted txns per calendar month > 30 (consider only the CCBP transactions)
    , COUNT(IF(DATE(B.dateinserted) BETWEEN date_trunc('month', DATE(A.dateinserted)) AND DATE(A.dateinserted), B.transactionid, NULL)) AS txn_attempted_cal_month
    , 30 AS txn_attempted_cal_month_threshold
    FROM
        (SELECT * FROM onus_txn_base
        WHERE DATE(dateinserted) BETWEEN DATE'2025-04-01' AND DATE'2025-04-30'
        AND responsestatus IN ('SUCCESS') AND actionrecommended = 'PASS')A
    INNER JOIN
        (SELECT * FROM onus_txn_base)B
    ON A.userid = B.userid AND A.transactionid <> B.transactionid AND B.velocitytimestamp < A.velocitytimestamp
    AND DATE(B.dateinserted) BETWEEN DATE(A.dateinserted - INTERVAL '30' DAY) AND DATE(A.dateinserted)
    GROUP BY 1,2,3,4,5,6,7,8,9,10,11)
WHERE (txn_attempted_same_day >= txn_attempted_same_day_threshold) OR (txn_attempted_7_day >= txn_attempted_7_day_threshold) OR (txn_attempted_cal_month >= txn_attempted_cal_month_threshold)
;
DROP TABLE team_kingkong.onus_on_us_sbi_nb_limit_breaches;

-- CREATE TABLE team_kingkong.onus_on_us_sbi_nb_limit_breaches AS 
INSERT INTO team_kingkong.onus_on_us_sbi_nb_limit_breaches 
SELECT A.*, case when m1.mid is not null then category else 'Others' end as business_category
, 100000 as per_txn_threshold
FROM 
    (select distinct userid, transactionid,
    cast(eventAmount as double) / 100 as amt,
    dateinserted,
    substr(cast(dateinserted as varchar(30)), 1, 7) as mnth,
    paymethod, paytmmerchantid, responsestatus, actionrecommended
    FROM cdp_risk_transform.maquette_flattened_onus_snapshot_v3
    WHERE DATE(dl_last_updated) BETWEEN DATE(DATE'2025-03-01' - INTERVAL '30' DAY) AND DATE'2025-03-31'
    AND SOURCE = 'PG' AND actionrecommended <> 'BLOCK' AND responsestatus = 'SUCCESS'
    AND paymethod = 'NET_BANKING' AND apicodeoption = 'SBINC1IN_NET_BANKING_PAYMENT'
    AND paytmmerchantid IN ('PTMVIS48435535949128','PTMCBP11428987150800','PTMCBP84799392178473')
    AND (cast(eventAmount as double) / 100) > 100000
    AND eventid IN (SELECT eventlinkid
    FROM risk_maquette_data_async.pplus_payment_result_prod_async_snapshot_v3
    WHERE dl_last_updated BETWEEN DATE(DATE'2025-03-01' - INTERVAL '30' DAY) AND DATE'2025-03-31')) a
left join
    (select * from team_kingkong.voc_mid_categorization where mid != '') m1
on a.paytmmerchantid = m1.mid;
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;
-- RECHCECKING
-- 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-05-01' - INTERVAL '1' DAY) AND DATE'2025-05-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-05-01' AND DATE(DATE'2025-05-31' + INTERVAL '1' DAY)
    and txn_started_at BETWEEN  DATE'2025-05-01' AND DATE(DATE'2025-05-31' + INTERVAL '1' DAY)) d
on a.transactionid = d.pg_txn_id)

SELECT * 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-05-01' AND  DATE'2025-05-31'
        AND isindian = 'false')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' -- BETWEEN date'2025-01-31' AND
    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'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)
    and txn_status = 'SUCCESS') d
on a.transactionid = d.pg_txn_id)
 
SELECT * 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)
-- ICA_OddTime_PerCard_PerMID_EDC
CREATE TABLE team_kingkong.offus_ICA_OddTime_PerCard_PerMID_EDC_breaches AS
-- INSERT INTO team_kingkong.offus_ICA_OddTime_PerCard_PerMID_EDC_breaches
with offus_txn as
(SELECT transactionid, txn_amount, txn_date, paytmmerchantid, txn_timestamp, globalcardindex
, case when edc_mid is not null then 'EDC' else 'QR' end as mid_type
, merchantcategory, merchantsubcategory, isindian 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
    , DATE(dl_last_updated) AS txn_date
    , CAST(velocitytimestamp AS DOUBLE) AS txn_timestamp
    , globalcardindex
    , merchantcategory, merchantsubcategory, isindian
    from cdp_risk_transform.maquette_flattened_offus_snapshot_v3
    where dl_last_updated BETWEEN DATE(DATE'2025-02-01' - INTERVAL '1' DAY) AND DATE'2025-02-28'
    and paymethod in ('CREDIT_CARD','DEBIT_CARD')
    AND actionrecommended <> 'BLOCK') 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)

SELECT * FROM
    (SELECT A.transactionid, A.txn_amount, A.txn_date, A.paytmmerchantid, A.globalcardindex, A.mid_type, 'ICA_OddTime_PerCard_PerMID_EDC' AS rule_name, A.txn_timestamp
    , 5000 as per_txn_limit
    , COUNT(B.transactionid) as txn1_day
    , 2 as txn1_day_threshold
    FROM
        (SELECT * FROM offus_txn
        WHERE txn_date BETWEEN date '2025-02-01' AND DATE'2025-02-28'
        AND isindian = 'false'
        AND HOUR(FROM_UNIXTIME(txn_timestamp / 1000)) BETWEEN 0 AND 4
        AND (merchantsubcategory NOT IN ('Restaurant', 'Foodcourt','Restaurants and Bars', 'Fast Food and QSR' , 'Hotel', 'Aviation','Tours and Travel Agency' , 'Pharmacy', 'Hospital','Taxi','Pharmacy', 'Hospital', 'Taxi')
        OR merchantcategory NOT IN ('Airport','Gas and Petrol')))A
    LEFT JOIN
        (SELECT * FROM offus_txn)B
    ON A.globalcardindex = B.globalcardindex AND A.paytmmerchantid = B.paytmmerchantid AND (A.txn_timestamp - B.txn_timestamp) BETWEEN 0 AND 86400000 -- <= 1day
    AND A.transactionid <> B.transactionid
    GROUP BY 1,2,3,4,5,6,7,8)
WHERE (txn_amount > per_txn_limit) OR (txn1_day>= txn1_day_threshold)
-- LIMIT 100
;
-- 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
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
    from cdp_risk_transform.maquette_flattened_offus_snapshot_v3
    where dl_last_updated BETWEEN DATE(DATE'2025-05-01' - INTERVAL '1' DAY) AND DATE'2025-05-31'
    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(CAST(limit_date AS double) / 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:"
			}
		}
	]
}
star

Thu May 29 2025 10:02:01 GMT+0000 (Coordinated Universal Time) https://maticz.com/poker-game-development

@abiramid ##pokergame #pokergamedevelopment #pokergamedevelopmentcompany #maticz #maticzpokergamedevelopment #pokerappdevelopment

star

Thu May 29 2025 05:24:28 GMT+0000 (Coordinated Universal Time)

@shubhangi.b

star

Wed May 28 2025 09:13:07 GMT+0000 (Coordinated Universal Time) https://www.coinsclone.com/crypto-leverage-trading-platform/

@CharleenStewar #crypto leverage trading platform

star

Wed May 28 2025 08:57:43 GMT+0000 (Coordinated Universal Time)

@shubhangi.b

star

Wed May 28 2025 08:56:35 GMT+0000 (Coordinated Universal Time)

@shubhangi.b

star

Wed May 28 2025 08:56:01 GMT+0000 (Coordinated Universal Time)

@shubhangi.b

star

Wed May 28 2025 07:30:13 GMT+0000 (Coordinated Universal Time) https://www.talentrack.in/voice-over-artist-job-in-india

@Talentrack

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:45 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

Save snippets that work with our extensions

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